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