1/* $OpenLDAP$ */
2/* This work is part of OpenLDAP Software <http://www.openldap.org/>.
3 *
4 * Copyright 2002-2011 The OpenLDAP Foundation.
5 * Portions Copyright 1997,2002-2003 IBM Corporation.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted only as authorized by the OpenLDAP
10 * Public License.
11 *
12 * A copy of this license is available in the file LICENSE in the
13 * top-level directory of the distribution or, alternatively, at
14 * <http://www.OpenLDAP.org/license.html>.
15 */
16/* ACKNOWLEDGEMENTS:
17 * This work was initially developed by IBM Corporation for use in
18 * IBM products and subsequently ported to OpenLDAP Software by
19 * Steve Omrani.  Additional significant contributors include:
20 *    Luke Howard
21 */
22
23#include "portable.h"
24#include "ldap_pvt_thread.h"
25#include "slap.h"
26#include "config.h"
27#include "slapi.h"
28#include "lutil.h"
29
30/*
31 * Note: if ltdl.h is not available, slapi should not be compiled
32 */
33#include <ltdl.h>
34
35static int slapi_int_load_plugin( Slapi_PBlock *, const char *, const char *, int,
36	SLAPI_FUNC *, lt_dlhandle * );
37
38/* pointer to link list of extended objects */
39static ExtendedOp *pGExtendedOps = NULL;
40
41/*********************************************************************
42 * Function Name:      plugin_pblock_new
43 *
44 * Description:        This routine creates a new Slapi_PBlock structure,
45 *                     loads in the plugin module and executes the init
46 *                     function provided by the module.
47 *
48 * Input:              type - type of the plugin, such as SASL, database, etc.
49 *                     path - the loadpath to load the module in
50 *                     initfunc - name of the plugin function to execute first
51 *                     argc - number of arguements
52 *                     argv[] - an array of char pointers point to
53 *                              the arguments passed in via
54 *                              the configuration file.
55 *
56 * Output:
57 *
58 * Return Values:      a pointer to a newly created Slapi_PBlock structrue or
59 *                     NULL - function failed
60 *
61 * Messages:           None
62 *********************************************************************/
63
64static Slapi_PBlock *
65plugin_pblock_new(
66	int type,
67	int argc,
68	char *argv[] )
69{
70	Slapi_PBlock	*pPlugin = NULL;
71	Slapi_PluginDesc *pPluginDesc = NULL;
72	lt_dlhandle	hdLoadHandle;
73	int		rc;
74	char		**av2 = NULL, **ppPluginArgv;
75	char		*path = argv[2];
76	char		*initfunc = argv[3];
77
78	pPlugin = slapi_pblock_new();
79	if ( pPlugin == NULL ) {
80		rc = LDAP_NO_MEMORY;
81		goto done;
82	}
83
84	slapi_pblock_set( pPlugin, SLAPI_PLUGIN_TYPE, (void *)&type );
85	slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGC, (void *)&argc );
86
87	av2 = ldap_charray_dup( argv );
88	if ( av2 == NULL ) {
89		rc = LDAP_NO_MEMORY;
90		goto done;
91	}
92
93	if ( argc > 0 ) {
94		ppPluginArgv = &av2[4];
95	} else {
96		ppPluginArgv = NULL;
97	}
98
99	slapi_pblock_set( pPlugin, SLAPI_PLUGIN_ARGV, (void *)ppPluginArgv );
100	slapi_pblock_set( pPlugin, SLAPI_X_CONFIG_ARGV, (void *)av2 );
101
102	rc = slapi_int_load_plugin( pPlugin, path, initfunc, 1, NULL, &hdLoadHandle );
103	if ( rc != 0 ) {
104		goto done;
105	}
106
107	if ( slapi_pblock_get( pPlugin, SLAPI_PLUGIN_DESCRIPTION, (void **)&pPluginDesc ) == 0 &&
108	     pPluginDesc != NULL ) {
109		slapi_log_error(SLAPI_LOG_TRACE, "plugin_pblock_new",
110				"Registered plugin %s %s [%s] (%s)\n",
111				pPluginDesc->spd_id,
112				pPluginDesc->spd_version,
113				pPluginDesc->spd_vendor,
114				pPluginDesc->spd_description);
115	}
116
117done:
118	if ( rc != 0 && pPlugin != NULL ) {
119		slapi_pblock_destroy( pPlugin );
120		pPlugin = NULL;
121		if ( av2 != NULL ) {
122			ldap_charray_free( av2 );
123		}
124	}
125
126	return pPlugin;
127}
128
129/*********************************************************************
130 * Function Name:      slapi_int_register_plugin
131 *
132 * Description:        insert the slapi_pblock structure to the end of the plugin
133 *                     list
134 *
135 * Input:              a pointer to a plugin slapi_pblock structure to be added to
136 *                     the list
137 *
138 * Output:             none
139 *
140 * Return Values:      LDAP_SUCCESS - successfully inserted.
141 *                     LDAP_LOCAL_ERROR.
142 *
143 * Messages:           None
144 *********************************************************************/
145int
146slapi_int_register_plugin(
147	Backend *be,
148	Slapi_PBlock *pPB )
149{
150	Slapi_PBlock	*pTmpPB;
151	Slapi_PBlock	*pSavePB;
152	int   		 rc = LDAP_SUCCESS;
153
154	assert( be != NULL );
155
156	pTmpPB = SLAPI_BACKEND_PBLOCK( be );
157	if ( pTmpPB == NULL ) {
158		SLAPI_BACKEND_PBLOCK( be ) = pPB;
159	} else {
160		while ( pTmpPB != NULL && rc == LDAP_SUCCESS ) {
161			pSavePB = pTmpPB;
162			rc = slapi_pblock_get( pTmpPB, SLAPI_IBM_PBLOCK, &pTmpPB );
163		}
164
165		if ( rc == LDAP_SUCCESS ) {
166			rc = slapi_pblock_set( pSavePB, SLAPI_IBM_PBLOCK, (void *)pPB );
167		}
168	}
169
170	return ( rc != LDAP_SUCCESS ) ? LDAP_OTHER : LDAP_SUCCESS;
171}
172
173/*********************************************************************
174 * Function Name:      slapi_int_get_plugins
175 *
176 * Description:        get the desired type of function pointers defined
177 *                     in all the plugins
178 *
179 * Input:              the type of the functions to get, such as pre-operation,etc.
180 *
181 * Output:             none
182 *
183 * Return Values:      this routine returns a pointer to an array of function
184 *                     pointers containing backend-specific plugin functions
185 *                     followed by global plugin functions
186 *
187 * Messages:           None
188 *********************************************************************/
189int
190slapi_int_get_plugins(
191	Backend *be,
192	int functype,
193	SLAPI_FUNC **ppFuncPtrs )
194{
195
196	Slapi_PBlock	*pCurrentPB;
197	SLAPI_FUNC	FuncPtr;
198	SLAPI_FUNC	*pTmpFuncPtr;
199	int		numPB = 0;
200	int		rc = LDAP_SUCCESS;
201
202	assert( ppFuncPtrs != NULL );
203
204	if ( be == NULL ) {
205		goto done;
206	}
207
208	pCurrentPB = SLAPI_BACKEND_PBLOCK( be );
209
210	while ( pCurrentPB != NULL && rc == LDAP_SUCCESS ) {
211		rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr );
212		if ( rc == LDAP_SUCCESS ) {
213			if ( FuncPtr != NULL )  {
214				numPB++;
215			}
216			rc = slapi_pblock_get( pCurrentPB,
217				SLAPI_IBM_PBLOCK, &pCurrentPB );
218		}
219	}
220
221	if ( numPB == 0 ) {
222		*ppFuncPtrs = NULL;
223		rc = LDAP_SUCCESS;
224		goto done;
225	}
226
227	/*
228	 * Now, build the function pointer array of backend-specific
229	 * plugins followed by global plugins.
230	 */
231	*ppFuncPtrs = pTmpFuncPtr =
232		(SLAPI_FUNC *)ch_malloc( ( numPB + 1 ) * sizeof(SLAPI_FUNC) );
233	if ( ppFuncPtrs == NULL ) {
234		rc = LDAP_NO_MEMORY;
235		goto done;
236	}
237
238	pCurrentPB = SLAPI_BACKEND_PBLOCK( be );
239
240	while ( pCurrentPB != NULL && rc == LDAP_SUCCESS )  {
241		rc = slapi_pblock_get( pCurrentPB, functype, &FuncPtr );
242		if ( rc == LDAP_SUCCESS ) {
243			if ( FuncPtr != NULL )  {
244				*pTmpFuncPtr = FuncPtr;
245				pTmpFuncPtr++;
246			}
247			rc = slapi_pblock_get( pCurrentPB,
248					SLAPI_IBM_PBLOCK, &pCurrentPB );
249		}
250	}
251
252	*pTmpFuncPtr = NULL;
253
254
255done:
256	if ( rc != LDAP_SUCCESS && *ppFuncPtrs != NULL ) {
257		ch_free( *ppFuncPtrs );
258		*ppFuncPtrs = NULL;
259	}
260
261	return rc;
262}
263
264/*********************************************************************
265 * Function Name:      createExtendedOp
266 *
267 * Description: Creates an extended operation structure and
268 *              initializes the fields
269 *
270 * Return value: A newly allocated structure or NULL
271 ********************************************************************/
272ExtendedOp *
273createExtendedOp()
274{
275	ExtendedOp *ret;
276
277	ret = (ExtendedOp *)slapi_ch_malloc(sizeof(ExtendedOp));
278	ret->ext_oid.bv_val = NULL;
279	ret->ext_oid.bv_len = 0;
280	ret->ext_func = NULL;
281	ret->ext_be = NULL;
282	ret->ext_next = NULL;
283
284	return ret;
285}
286
287
288/*********************************************************************
289 * Function Name:      slapi_int_unregister_extop
290 *
291 * Description:        This routine removes the ExtendedOp structures
292 *					   asscoiated with a particular extended operation
293 *					   plugin.
294 *
295 * Input:              pBE - pointer to a backend structure
296 *                     opList - pointer to a linked list of extended
297 *                              operation structures
298 *                     pPB - pointer to a slapi parameter block
299 *
300 * Output:
301 *
302 * Return Value:       none
303 *
304 * Messages:           None
305 *********************************************************************/
306void
307slapi_int_unregister_extop(
308	Backend *pBE,
309	ExtendedOp **opList,
310	Slapi_PBlock *pPB )
311{
312	ExtendedOp	*pTmpExtOp, *backExtOp;
313	char		**pTmpOIDs;
314	int		i;
315
316#if 0
317	assert( pBE != NULL); /* unused */
318#endif /* 0 */
319	assert( opList != NULL );
320	assert( pPB != NULL );
321
322	if ( *opList == NULL ) {
323		return;
324	}
325
326	slapi_pblock_get( pPB, SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs );
327	if ( pTmpOIDs == NULL ) {
328		return;
329	}
330
331	for ( i = 0; pTmpOIDs[i] != NULL; i++ ) {
332		backExtOp = NULL;
333		pTmpExtOp = *opList;
334		for ( ; pTmpExtOp != NULL; pTmpExtOp = pTmpExtOp->ext_next) {
335			int	rc;
336			rc = strcasecmp( pTmpExtOp->ext_oid.bv_val,
337					pTmpOIDs[ i ] );
338			if ( rc == 0 ) {
339				if ( backExtOp == NULL ) {
340					*opList = pTmpExtOp->ext_next;
341				} else {
342					backExtOp->ext_next
343						= pTmpExtOp->ext_next;
344				}
345
346				ch_free( pTmpExtOp );
347				break;
348			}
349			backExtOp = pTmpExtOp;
350		}
351	}
352}
353
354
355/*********************************************************************
356 * Function Name:      slapi_int_register_extop
357 *
358 * Description:        This routine creates a new ExtendedOp structure, loads
359 *                     in the extended op module and put the extended op function address
360 *                     in the structure. The function will not be executed in
361 *                     this routine.
362 *
363 * Input:              pBE - pointer to a backend structure
364 *                     opList - pointer to a linked list of extended
365 *                              operation structures
366 *                     pPB - pointer to a slapi parameter block
367 *
368 * Output:
369 *
370 * Return Value:       an LDAP return code
371 *
372 * Messages:           None
373 *********************************************************************/
374int
375slapi_int_register_extop(
376	Backend *pBE,
377	ExtendedOp **opList,
378	Slapi_PBlock *pPB )
379{
380	ExtendedOp	*pTmpExtOp = NULL;
381	SLAPI_FUNC	tmpFunc;
382	char		**pTmpOIDs;
383	int		rc = LDAP_OTHER;
384	int		i;
385
386	if ( (*opList) == NULL ) {
387		*opList = createExtendedOp();
388		if ( (*opList) == NULL ) {
389			rc = LDAP_NO_MEMORY;
390			goto error_return;
391		}
392		pTmpExtOp = *opList;
393
394	} else {                        /* Find the end of the list */
395		for ( pTmpExtOp = *opList; pTmpExtOp->ext_next != NULL;
396				pTmpExtOp = pTmpExtOp->ext_next )
397			; /* EMPTY */
398		pTmpExtOp->ext_next = createExtendedOp();
399		if ( pTmpExtOp->ext_next == NULL ) {
400			rc = LDAP_NO_MEMORY;
401			goto error_return;
402		}
403		pTmpExtOp = pTmpExtOp->ext_next;
404	}
405
406	rc = slapi_pblock_get( pPB,SLAPI_PLUGIN_EXT_OP_OIDLIST, &pTmpOIDs );
407	if ( rc != 0 ) {
408		rc = LDAP_OTHER;
409		goto error_return;
410	}
411
412	rc = slapi_pblock_get(pPB,SLAPI_PLUGIN_EXT_OP_FN, &tmpFunc);
413	if ( rc != 0 ) {
414		rc = LDAP_OTHER;
415		goto error_return;
416	}
417
418	if ( (pTmpOIDs == NULL) || (tmpFunc == NULL) ) {
419		rc = LDAP_OTHER;
420		goto error_return;
421	}
422
423	for ( i = 0; pTmpOIDs[i] != NULL; i++ ) {
424		pTmpExtOp->ext_oid.bv_val = pTmpOIDs[i];
425		pTmpExtOp->ext_oid.bv_len = strlen( pTmpOIDs[i] );
426		pTmpExtOp->ext_func = tmpFunc;
427		pTmpExtOp->ext_be = pBE;
428		if ( pTmpOIDs[i + 1] != NULL ) {
429			pTmpExtOp->ext_next = createExtendedOp();
430			if ( pTmpExtOp->ext_next == NULL ) {
431				rc = LDAP_NO_MEMORY;
432				break;
433			}
434			pTmpExtOp = pTmpExtOp->ext_next;
435		}
436	}
437
438error_return:
439	return rc;
440}
441
442/*********************************************************************
443 * Function Name:      slapi_int_get_extop_plugin
444 *
445 * Description:        This routine gets the function address for a given function
446 *                     name.
447 *
448 * Input:
449 *                     funcName - name of the extended op function, ie. an OID.
450 *
451 * Output:             pFuncAddr - the function address of the requested function name.
452 *
453 * Return Values:      a pointer to a newly created ExtendOp structrue or
454 *                     NULL - function failed
455 *
456 * Messages:           None
457 *********************************************************************/
458int
459slapi_int_get_extop_plugin(
460	struct berval *reqoid,
461	SLAPI_FUNC *pFuncAddr )
462{
463	ExtendedOp	*pTmpExtOp;
464
465	assert( reqoid != NULL );
466	assert( pFuncAddr != NULL );
467
468	*pFuncAddr = NULL;
469
470	if ( pGExtendedOps == NULL ) {
471		return LDAP_OTHER;
472	}
473
474	pTmpExtOp = pGExtendedOps;
475	while ( pTmpExtOp != NULL ) {
476		int	rc;
477
478		rc = strcasecmp( reqoid->bv_val, pTmpExtOp->ext_oid.bv_val );
479		if ( rc == 0 ) {
480			*pFuncAddr = pTmpExtOp->ext_func;
481			break;
482		}
483		pTmpExtOp = pTmpExtOp->ext_next;
484	}
485
486	return ( *pFuncAddr == NULL ? 1 : 0 );
487}
488
489/***************************************************************************
490 * This function is similar to slapi_int_get_extop_plugin above. except it returns one OID
491 * per call. It is called from root_dse_info (root_dse.c).
492 * The function is a modified version of get_supported_extop (file extended.c).
493 ***************************************************************************/
494struct berval *
495slapi_int_get_supported_extop( int index )
496{
497        ExtendedOp	*ext;
498
499        for ( ext = pGExtendedOps ; ext != NULL && --index >= 0;
500			ext = ext->ext_next) {
501                ; /* empty */
502        }
503
504        if ( ext == NULL ) {
505		return NULL;
506	}
507
508        return &ext->ext_oid ;
509}
510
511/*********************************************************************
512 * Function Name:      slapi_int_load_plugin
513 *
514 * Description:        This routine loads the specified DLL, gets and executes the init function
515 *                     if requested.
516 *
517 * Input:
518 *                     pPlugin - a pointer to a Slapi_PBlock struct which will be passed to
519 *                               the DLL init function.
520 *                     path - path name of the DLL to be load.
521 *                     initfunc - either the DLL initialization function or an OID of the
522 *                                loaded extended operation.
523 *                     doInit - if it is TRUE, execute the init function, otherwise, save the
524 *                              function address but not execute it.
525 *
526 * Output:             pInitFunc - the function address of the loaded function. This param
527 *                                 should be not be null if doInit is FALSE.
528 *                     pLdHandle - handle returned by lt_dlopen()
529 *
530 * Return Values:      LDAP_SUCCESS, LDAP_LOCAL_ERROR
531 *
532 * Messages:           None
533 *********************************************************************/
534
535static int
536slapi_int_load_plugin(
537	Slapi_PBlock	*pPlugin,
538	const char	*path,
539	const char	*initfunc,
540	int		doInit,
541	SLAPI_FUNC	*pInitFunc,
542	lt_dlhandle	*pLdHandle )
543{
544	int		rc = LDAP_SUCCESS;
545	SLAPI_FUNC	fpInitFunc = NULL;
546
547	assert( pLdHandle != NULL );
548
549	if ( lt_dlinit() ) {
550		return LDAP_LOCAL_ERROR;
551	}
552
553	/* load in the module */
554	*pLdHandle = lt_dlopen( path );
555	if ( *pLdHandle == NULL ) {
556		fprintf( stderr, "failed to load plugin %s: %s\n",
557			 path, lt_dlerror() );
558		return LDAP_LOCAL_ERROR;
559	}
560
561	fpInitFunc = (SLAPI_FUNC)lt_dlsym( *pLdHandle, initfunc );
562	if ( fpInitFunc == NULL ) {
563		fprintf( stderr, "failed to find symbol %s in plugin %s: %s\n",
564			 initfunc, path, lt_dlerror() );
565		lt_dlclose( *pLdHandle );
566		return LDAP_LOCAL_ERROR;
567	}
568
569	if ( doInit ) {
570		rc = ( *fpInitFunc )( pPlugin );
571		if ( rc != LDAP_SUCCESS ) {
572			lt_dlclose( *pLdHandle );
573		}
574
575	} else {
576		*pInitFunc = fpInitFunc;
577	}
578
579	return rc;
580}
581
582/*
583 * Special support for computed attribute plugins
584 */
585int
586slapi_int_call_plugins(
587	Backend		*be,
588	int		funcType,
589	Slapi_PBlock	*pPB )
590{
591
592	int rc = 0;
593	SLAPI_FUNC *pGetPlugin = NULL, *tmpPlugin = NULL;
594
595	if ( pPB == NULL ) {
596		return 1;
597	}
598
599	rc = slapi_int_get_plugins( be, funcType, &tmpPlugin );
600	if ( rc != LDAP_SUCCESS || tmpPlugin == NULL ) {
601		/* Nothing to do, front-end should ignore. */
602		return rc;
603	}
604
605	for ( pGetPlugin = tmpPlugin ; *pGetPlugin != NULL; pGetPlugin++ ) {
606		rc = (*pGetPlugin)(pPB);
607
608		/*
609		 * Only non-postoperation plugins abort processing on
610		 * failure (confirmed with SLAPI specification).
611		 */
612		if ( !SLAPI_PLUGIN_IS_POST_FN( funcType ) && rc != 0 ) {
613			/*
614			 * Plugins generally return negative error codes
615			 * to indicate failure, although in the case of
616			 * bind plugins they may return SLAPI_BIND_xxx
617			 */
618			break;
619		}
620	}
621
622	slapi_ch_free( (void **)&tmpPlugin );
623
624	return rc;
625}
626
627int
628slapi_int_read_config(
629	Backend		*be,
630	const char	*fname,
631	int		lineno,
632	int		argc,
633	char		**argv )
634{
635	int		iType = -1;
636	int		numPluginArgc = 0;
637
638	if ( argc < 4 ) {
639		fprintf( stderr,
640			"%s: line %d: missing arguments "
641			"in \"plugin <plugin_type> <lib_path> "
642			"<init_function> [<arguments>]\" line\n",
643			fname, lineno );
644		return 1;
645	}
646
647	/* automatically instantiate overlay if necessary */
648	if ( !slapi_over_is_inst( be ) ) {
649		ConfigReply cr = { 0 };
650		if ( slapi_over_config( be, &cr ) != 0 ) {
651			fprintf( stderr, "Failed to instantiate SLAPI overlay: "
652				"err=%d msg=\"%s\"\n", cr.err, cr.msg );
653			return -1;
654		}
655	}
656
657	if ( strcasecmp( argv[1], "preoperation" ) == 0 ) {
658		iType = SLAPI_PLUGIN_PREOPERATION;
659	} else if ( strcasecmp( argv[1], "postoperation" ) == 0 ) {
660		iType = SLAPI_PLUGIN_POSTOPERATION;
661	} else if ( strcasecmp( argv[1], "extendedop" ) == 0 ) {
662		iType = SLAPI_PLUGIN_EXTENDEDOP;
663	} else if ( strcasecmp( argv[1], "object" ) == 0 ) {
664		iType = SLAPI_PLUGIN_OBJECT;
665	} else {
666		fprintf( stderr, "%s: line %d: invalid plugin type \"%s\".\n",
667				fname, lineno, argv[1] );
668		return 1;
669	}
670
671	numPluginArgc = argc - 4;
672
673	if ( iType == SLAPI_PLUGIN_PREOPERATION ||
674		  	iType == SLAPI_PLUGIN_EXTENDEDOP ||
675			iType == SLAPI_PLUGIN_POSTOPERATION ||
676			iType == SLAPI_PLUGIN_OBJECT ) {
677		int rc;
678		Slapi_PBlock *pPlugin;
679
680		pPlugin = plugin_pblock_new( iType, numPluginArgc, argv );
681		if (pPlugin == NULL) {
682			return 1;
683		}
684
685		if (iType == SLAPI_PLUGIN_EXTENDEDOP) {
686			rc = slapi_int_register_extop(be, &pGExtendedOps, pPlugin);
687			if ( rc != LDAP_SUCCESS ) {
688				slapi_pblock_destroy( pPlugin );
689				return 1;
690			}
691		}
692
693		rc = slapi_int_register_plugin( be, pPlugin );
694		if ( rc != LDAP_SUCCESS ) {
695			if ( iType == SLAPI_PLUGIN_EXTENDEDOP ) {
696				slapi_int_unregister_extop( be, &pGExtendedOps, pPlugin );
697			}
698			slapi_pblock_destroy( pPlugin );
699			return 1;
700		}
701	}
702
703	return 0;
704}
705
706void
707slapi_int_plugin_unparse(
708	Backend *be,
709	BerVarray *out
710)
711{
712	Slapi_PBlock *pp;
713	int i, j;
714	char **argv, ibuf[32], *ptr;
715	struct berval idx, bv;
716
717	*out = NULL;
718	idx.bv_val = ibuf;
719	i = 0;
720
721	for ( pp = SLAPI_BACKEND_PBLOCK( be );
722	      pp != NULL;
723	      slapi_pblock_get( pp, SLAPI_IBM_PBLOCK, &pp ) )
724	{
725		slapi_pblock_get( pp, SLAPI_X_CONFIG_ARGV, &argv );
726		if ( argv == NULL ) /* could be dynamic plugin */
727			continue;
728		idx.bv_len = snprintf( idx.bv_val, sizeof( ibuf ), "{%d}", i );
729		if ( idx.bv_len >= sizeof( ibuf ) ) {
730			/* FIXME: just truncating by now */
731			idx.bv_len = sizeof( ibuf ) - 1;
732		}
733		bv.bv_len = idx.bv_len;
734		for (j=1; argv[j]; j++) {
735			bv.bv_len += strlen(argv[j]);
736			if ( j ) bv.bv_len++;
737		}
738		bv.bv_val = ch_malloc( bv.bv_len + 1 );
739		ptr = lutil_strcopy( bv.bv_val, ibuf );
740		for (j=1; argv[j]; j++) {
741			if ( j ) *ptr++ = ' ';
742			ptr = lutil_strcopy( ptr, argv[j] );
743		}
744		ber_bvarray_add( out, &bv );
745	}
746}
747
748