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