xref: /illumos-gate/usr/src/cmd/ldap/common/ldapmodify.c (revision e7cbe64f7a72dae5cb44f100db60ca88f3313c65)
1 /*
2  * Copyright 2003 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 #pragma ident	"%Z%%M%	%I%	%E% SMI"
7 
8 /*
9  * The contents of this file are subject to the Netscape Public
10  * License Version 1.1 (the "License"); you may not use this file
11  * except in compliance with the License. You may obtain a copy of
12  * the License at http://www.mozilla.org/NPL/
13  *
14  * Software distributed under the License is distributed on an "AS
15  * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
16  * implied. See the License for the specific language governing
17  * rights and limitations under the License.
18  *
19  * The Original Code is Mozilla Communicator client code, released
20  * March 31, 1998.
21  *
22  * The Initial Developer of the Original Code is Netscape
23  * Communications Corporation. Portions created by Netscape are
24  * Copyright (C) 1998-1999 Netscape Communications Corporation. All
25  * Rights Reserved.
26  *
27  * Contributor(s):
28  */
29 
30 /* ldapmodify.c - generic program to modify or add entries using LDAP */
31 
32 #include "ldaptool.h"
33 #include "fileurl.h"
34 
35 #ifdef SOLARIS_LDAP_CMD
36 #include <locale.h>
37 #include "ldif.h"
38 #endif	/* SOLARIS_LDAP_CMD */
39 
40 #ifndef SOLARIS_LDAP_CMD
41 #define gettext(s) s
42 #endif
43 
44 static int		newval, contoper, force, valsfromfiles, display_binary_values;
45 static int		ldif_version = -1;	/* -1 => unknown version */
46 static char		*rejfile = NULL;
47 static char		*bulkimport_suffix = NULL;
48 static int		ldapmodify_quiet = 0;
49 
50 #ifdef SOLARIS_LDAP_CMD
51 static int		error = 0, replace, nbthreads = 1;
52 static int		thr_create_errors = 0;
53 static pthread_mutex_t	read_mutex = {0};
54 static pthread_mutex_t	wait_mutex = {0};
55 static pthread_cond_t	wait_cond  = {0};
56 #else
57 /*
58  * For Solaris, ld is defined local to process() because
59  * multiple threads restricts Solaris from using a global
60  * ld variable.
61  * Solaris uses multiple threads to create multiple
62  * ldap connections if nbthreads > 1 (i.e -l option).
63  */
64 static LDAP		*ld;
65 #endif	/* SOLARIS_LDAP_CMD */
66 
67 #define LDAPMOD_MAXLINE		4096
68 
69 /* strings found in replog/LDIF entries (mostly lifted from slurpd/slurp.h) */
70 #define T_REPLICA_STR		"replica"
71 #define T_DN_STR		"dn"
72 #define T_VERSION_STR		"version"
73 #define T_CHANGETYPESTR         "changetype"
74 #define T_ADDCTSTR		"add"
75 #define T_MODIFYCTSTR		"modify"
76 #define T_DELETECTSTR		"delete"
77 #define T_RENAMECTSTR		"rename"	/* non-standard */
78 #define T_MODDNCTSTR		"moddn"
79 #define T_MODRDNCTSTR		"modrdn"
80 #define T_MODOPADDSTR		"add"
81 #define T_MODOPREPLACESTR	"replace"
82 #define T_MODOPDELETESTR	"delete"
83 #define T_MODSEPSTR		"-"
84 #define T_NEWRDNSTR		"newrdn"
85 #define	T_NEWSUPERIORSTR	"newsuperior"
86 #define	T_NEWPARENTSTR		"newparent"
87 #define T_DELETEOLDRDNSTR	"deleteoldrdn"
88 #define T_NEWSUPERIORSTR        "newsuperior"
89 #define T_NEWPARENTSTR          "newparent"	/* non-standard */
90 
91 /* bulk import */
92 #define	BULKIMPORT_START_OID	"2.16.840.1.113730.3.5.7"
93 #define	BULKIMPORT_STOP_OID	"2.16.840.1.113730.3.5.8"
94 
95 static int process( void *arg );
96 static void options_callback( int option, char *optarg );
97 static void addmodifyop( LDAPMod ***pmodsp, int modop, char *attr,
98 	char *value, int vlen );
99 static void freepmods( LDAPMod **pmods );
100 static char *read_one_record( FILE *fp );
101 static char *strdup_and_trim( char *s );
102 
103 #ifdef SOLARIS_LDAP_CMD
104 static int process_ldapmod_rec( LDAP *ld, char *rbuf );
105 static int process_ldif_rec( LDAP *ld, char *rbuf );
106 static int domodify( LDAP *ld, char *dn, LDAPMod **pmods, int newentry );
107 static int dodelete( LDAP *ld, char *dn );
108 static int dorename( LDAP *ld, char *dn, char *newrdn, char *newparent,
109 	int deleteoldrdn );
110 #else
111 static int process_ldapmod_rec( char *rbuf );
112 static int process_ldif_rec( char *rbuf );
113 static int domodify( char *dn, LDAPMod **pmods, int newentry );
114 static int dodelete( char *dn );
115 static int dorename( char *dn, char *newrdn, char *newparent,
116 	int deleteoldrdn );
117 #endif	/* SOLARIS_LDAP_CMD */
118 
119 static void
120 usage( void )
121 {
122     fprintf( stderr, gettext("usage: %s [options]\n"), ldaptool_progname );
123     fprintf( stderr, gettext("options:\n") );
124     ldaptool_common_usage( 0 );
125     fprintf( stderr, gettext("    -c\t\tcontinuous mode (do not stop on errors)\n") );
126     fprintf( stderr, gettext("    -A\t\tdisplay non-ASCII values in conjunction with -v\n") );
127     fprintf( stderr, gettext("    -f file\tread modifications from file (default: standard input)\n") );
128     if ( strcmp( ldaptool_progname, "ldapmodify" ) == 0 ){
129 	fprintf( stderr, gettext("    -a\t\tadd entries\n") );
130     }
131     fprintf( stderr, gettext("    -b\t\tread values that start with / from files (for bin attrs)\n") );
132     fprintf( stderr, gettext("    -F\t\tforce application of all changes, regardless of\n") );
133     fprintf( stderr, gettext("      \t\treplica lines\n") );
134     fprintf( stderr, gettext("    -e rejfile\tsave rejected entries in \"rejfile\"\n") );
135     fprintf( stderr, gettext("    -B suffix\tbulk import to \"suffix\"\n"));
136     fprintf( stderr, gettext("    -q\t\tbe quiet when adding/modifying entries\n") );
137 #ifdef SOLARIS_LDAP_CMD
138     fprintf( stderr, gettext("    -r\t\treplace values\n"));
139     fprintf( stderr, gettext("    -l nb-connections\tnumber of LDAP connections\n"));
140 #endif	/* SOLARIS_LDAP_CMD */
141     exit( LDAP_PARAM_ERROR );
142 }
143 
144 
145 int
146 main( int argc, char **argv )
147 {
148     int		optind, i;
149 
150 #ifdef SOLARIS_LDAP_CMD
151     char *locale = setlocale(LC_ALL, "");
152     textdomain(TEXT_DOMAIN);
153 #endif
154 
155 #ifdef notdef
156 #ifdef HPUX11
157 #ifndef __LP64__
158 	_main( argc, argv);
159 #endif /* __LP64_ */
160 #endif /* HPUX11 */
161 #endif
162 
163     valsfromfiles = display_binary_values = 0;
164 
165 #ifdef SOLARIS_LDAP_CMD
166     optind = ldaptool_process_args( argc, argv, "aAbcFe:B:qrl:", 0,
167 	    options_callback );
168 #else
169     optind = ldaptool_process_args( argc, argv, "aAbcFe:B:q", 0,
170 	    options_callback );
171 #endif	/* SOLARIS_LDAP_CMD */
172 
173 
174     if ( optind == -1 ) {
175 	usage();
176     }
177 
178     if ( !newval && strcmp( ldaptool_progname, "ldapadd" ) == 0 ) {
179 	newval = 1;
180     }
181 
182     if ( ldaptool_fp == NULL ) {
183 	ldaptool_fp = stdin;
184     }
185 
186     if ( argc - optind != 0 ) {
187 	usage();
188     }
189 
190 #ifdef SOLARIS_LDAP_CMD
191     /* trivial case */
192     if ( nbthreads == 1 )
193 	return ( process(NULL) );
194 
195     for ( i=0; i<nbthreads; ++i ) {
196  	if ( thr_create(NULL, 0, process, NULL, NULL, NULL) != 0 )
197 		++thr_create_errors;
198     }
199 
200     if ( thr_create_errors < nbthreads )
201     	while ( thr_join(0, NULL, NULL) == 0 );
202     else
203 	error = -1;
204     return ( error );
205 #else
206     return ( process(NULL) );
207 #endif  /* SOLARIS_LDAP_CMD */
208 }
209 
210 #ifdef SOLARIS_LDAP_CMD
211 #define	exit(a)	\
212 	if (nbthreads > 1) { \
213     		mutex_lock(&read_mutex); \
214 		error |= a; \
215     		mutex_unlock(&read_mutex); \
216 		thr_exit(&error); \
217 	} else { \
218 		exit(a); \
219 	}
220 #endif  /* SOLARIS_LDAP_CMD */
221 
222 static int
223 process( void *arg )
224 {
225     char	*rbuf, *saved_rbuf, *start, *p, *q;
226     FILE	*rfp = NULL;
227     int		rc, use_ldif, deref;
228     LDAPControl	*ldctrl;
229 
230 #ifdef SOLARIS_LDAP_CMD
231     LDAP	*ld;
232 #endif  /* SOLARIS_LDAP_CMD */
233 
234     ld = ldaptool_ldap_init( 0 );
235 
236     if ( !ldaptool_not ) {
237 	deref = LDAP_DEREF_NEVER;	/* this seems prudent */
238 	ldap_set_option( ld, LDAP_OPT_DEREF, &deref );
239     }
240 
241     ldaptool_bind( ld );
242 
243     if (( ldctrl = ldaptool_create_manage_dsait_control()) != NULL ) {
244 	ldaptool_add_control_to_array( ldctrl, ldaptool_request_ctrls);
245     }
246 
247     if ((ldctrl = ldaptool_create_proxyauth_control(ld)) !=NULL) {
248 	ldaptool_add_control_to_array( ldctrl, ldaptool_request_ctrls);
249     }
250 
251     rc = 0;
252 
253     /* turn on bulk import?*/
254     if (bulkimport_suffix) {
255 	struct berval	bv, *retdata;
256 	char		*retoid;
257 
258 	bv.bv_val = bulkimport_suffix;
259 	bv.bv_len = strlen(bulkimport_suffix);
260 	if ((rc = ldap_extended_operation_s(ld,
261 	    BULKIMPORT_START_OID, &bv, NULL,
262 	    NULL, &retoid, &retdata)) != 0) {
263 		fprintf(stderr, gettext("Error: unable to service "
264 		    "extended operation request\n\t'%s' for "
265 		    "bulk import\n\t(error:%d:'%s')\n"),
266 		    BULKIMPORT_START_OID, rc, ldap_err2string(rc));
267 		return (rc);
268 	}
269 	if (retoid)
270 		ldap_memfree(retoid);
271 	if (retdata)
272 		ber_bvfree(retdata);
273     }
274 
275     while (( rc == 0 || contoper ) &&
276 		( rbuf = read_one_record( ldaptool_fp )) != NULL ) {
277 	/*
278 	 * we assume record is ldif/slapd.replog if the first line
279 	 * has a colon that appears to the left of any equal signs, OR
280 	 * if the first line consists entirely of digits (an entry id)
281 	 */
282 	use_ldif = ( p = strchr( rbuf, ':' )) != NULL &&
283 		( q = strchr( rbuf, '\n' )) != NULL && p < q &&
284 		(( q = strchr( rbuf, '=' )) == NULL || p < q );
285 
286 	start = rbuf;
287 	saved_rbuf = strdup( rbuf );
288 
289 	if ( !use_ldif && ( q = strchr( rbuf, '\n' )) != NULL ) {
290 	    for ( p = rbuf; p < q; ++p ) {
291 		if ( !isdigit( *p )) {
292 		    break;
293 		}
294 	    }
295 	    if ( p >= q ) {
296 		use_ldif = 1;
297 		start = q + 1;
298 	    }
299 	}
300 
301 #ifdef SOLARIS_LDAP_CMD
302 	if ( use_ldif ) {
303 	    rc = process_ldif_rec( ld, start );
304 	} else {
305 	    rc = process_ldapmod_rec( ld, start );
306 	}
307 #else
308 	if ( use_ldif ) {
309 	    rc = process_ldif_rec( start );
310 	} else {
311 	    rc = process_ldapmod_rec( start );
312 	}
313 #endif	/* SOLARIS_LDAP_CMD */
314 
315 	if ( rc != LDAP_SUCCESS && rejfile != NULL ) {
316 	    /* Write this record to the reject file */
317 	    int newfile = 0;
318 	    struct stat stbuf;
319 	    if ( stat( rejfile, &stbuf ) < 0 ) {
320 		if ( errno == ENOENT ) {
321 		    newfile = 1;
322 		}
323 	    }
324 	    if (( rfp = ldaptool_open_file( rejfile, "a" )) == NULL ) {
325 		fprintf( stderr, gettext("Cannot open error file \"%s\" - "
326 			"erroneous entries will not be saved\n"), rejfile );
327 		rejfile = NULL;
328 	    } else {
329 		if ( newfile == 0 ) {
330 		    fputs( "\n", rfp );
331 		}
332 		fprintf( rfp, gettext("# Error: %s\n"), ldap_err2string( rc ));
333 		fputs( saved_rbuf, rfp );
334 		fclose( rfp );
335 		rfp = NULL;
336 	    }
337 	}
338 
339 	free( rbuf );
340 	free( saved_rbuf );
341     }
342     ldaptool_reset_control_array( ldaptool_request_ctrls );
343 
344     /* turn off bulk import?*/
345     if (bulkimport_suffix) {
346 	struct berval	bv, *retdata;
347 	char		*retoid;
348 
349 	bv.bv_val = "";
350 	bv.bv_len = 0;
351 	if ((rc = ldap_extended_operation_s(ld,
352 	    BULKIMPORT_STOP_OID, &bv, NULL,
353 	    NULL, &retoid, &retdata)) != 0) {
354 
355 		fprintf(stderr, gettext("Error: unable to service "
356 		    "extended operation request\n\t '%s' for "
357 		    "bulk import\n\t(rc:%d:'%s')\n"),
358 		    BULKIMPORT_STOP_OID, rc, ldap_err2string(rc));
359 		return (rc);
360 	}
361 	if (retoid)
362 		ldap_memfree(retoid);
363 	if (retdata)
364 		ber_bvfree(retdata);
365     }
366 
367 #ifdef SOLARIS_LDAP_CMD
368     mutex_lock(&read_mutex);
369 #endif
370     ldaptool_cleanup( ld );
371 #ifdef SOLARIS_LDAP_CMD
372     mutex_unlock(&read_mutex);
373 #endif
374     return( rc );
375 }
376 
377 
378 static void
379 options_callback( int option, char *optarg )
380 {
381     switch( option ) {
382     case 'a':	/* add */
383 	newval = 1;
384 	break;
385     case 'b':	/* read values from files (for binary attributes) */
386 	valsfromfiles = 1;
387 	break;
388     case 'A':	/* display non-ASCII values when -v is used */
389 	display_binary_values = 1;
390 	break;
391     case 'c':	/* continuous operation */
392 	contoper = 1;
393 	break;
394     case 'F':	/* force all changes records to be used */
395 	force = 1;
396 	break;
397     case 'e':
398 	rejfile = strdup( optarg );
399 	break;
400     case 'B':	/* bulk import option */
401 	bulkimport_suffix = strdup( optarg );
402 	break;
403     case 'q':	/* quiet mode on add/modify operations */
404 	ldapmodify_quiet = 1;
405 	break;
406 #ifdef SOLARIS_LDAP_CMD
407     case 'r':	/* default is to replace rather than add values */
408 	replace = 1;
409 	break;
410     case 'l':
411 	nbthreads = atoi(optarg);
412 	break;
413 #endif	/* SOLARIS_LDAP_CMD */
414     default:
415 	usage();
416     }
417 }
418 
419 
420 
421 static int
422 #ifdef SOLARIS_LDAP_CMD
423 process_ldif_rec( LDAP *ld, char *rbuf )
424 #else
425 process_ldif_rec( char *rbuf )
426 #endif
427 {
428     char	*line, *dn, *type, *value, *newrdn, *newparent, *p;
429     char	*ctrl_oid=NULL, *ctrl_value=NULL;
430     int 	ctrl_criticality=1;
431     LDAPControl *ldctrl;
432     int		rc, linenum, vlen, modop, replicaport;
433     int		expect_modop, expect_sep, expect_chgtype_or_control, expect_newrdn;
434     int		expect_deleteoldrdn, expect_newparent, rename, moddn;
435     int		deleteoldrdn, saw_replica, use_record, new_entry, delete_entry;
436     int         got_all, got_value;
437     LDAPMod	**pmods;
438 
439     new_entry = newval;
440 
441     rc = got_all = saw_replica = delete_entry = expect_modop = 0;
442     expect_deleteoldrdn = expect_newrdn = expect_newparent = expect_sep = 0;
443     expect_chgtype_or_control = linenum = got_value = rename = moddn = 0;
444     deleteoldrdn = 1;
445     use_record = force;
446     pmods = NULL;
447     dn = newrdn = newparent = NULL;
448 
449 #ifdef SOLARIS_LDAP_CMD
450     while ( rc == 0 && ( line = str_getline( &rbuf )) != NULL ) {
451 #else
452     while ( rc == 0 && ( line = ldif_getline( &rbuf )) != NULL ) {
453 #endif	/* SOLARIS_LDAP_CMD */
454 	++linenum;
455 	if ( expect_sep && strcasecmp( line, T_MODSEPSTR ) == 0 ) {
456 	    expect_sep = 0;
457 	    expect_modop = 1;
458 
459 	    /*If we see a separator in the input stream,
460 	     but we didn't get a value from the last modify
461 	     then we have to fill pmods with an empty value*/
462 	    if (modop == LDAP_MOD_REPLACE && !got_value){
463 	      addmodifyop( &pmods, modop, value, NULL, 0);
464 	    }
465 
466 	    got_value = 0;
467 	    continue;
468 	}
469 
470 #ifdef SOLARIS_LDAP_CMD
471 	if ( str_parse_line( line, &type, &value, &vlen ) < 0 ) {
472 #else
473 	if ( ldif_parse_line( line, &type, &value, &vlen ) < 0 ) {
474 #endif	/* SOLARIS_LDAP_CMD */
475 	    fprintf( stderr, gettext("%s: invalid format (line %d of entry: %s)\n"),
476 		    ldaptool_progname, linenum, dn == NULL ? "" : dn );
477 	    fprintf( stderr, gettext("%s: line contents: (%s)\n"),
478 		    ldaptool_progname, line );
479 	    rc = LDAP_PARAM_ERROR;
480 	    break;
481 	}
482 
483 evaluate_line:
484 	if ( dn == NULL ) {
485 	    if ( !use_record && strcasecmp( type, T_REPLICA_STR ) == 0 ) {
486 		++saw_replica;
487 		if (( p = strchr( value, ':' )) == NULL ) {
488 		    replicaport = LDAP_PORT;
489 		} else {
490 		    *p++ = '\0';
491 		    replicaport = atoi( p );
492 		}
493 		if ( strcasecmp( value, ldaptool_host ) == 0 &&
494 			replicaport == ldaptool_port ) {
495 		    use_record = 1;
496 		}
497 
498 	    } else if ( strcasecmp( type, T_DN_STR ) == 0 ) {
499 		if (( dn = strdup( value )) == NULL ) {
500 		    perror( "strdup" );
501 		    exit( LDAP_NO_MEMORY );
502 		}
503 		expect_chgtype_or_control = 1;
504 
505 	    } else if ( strcasecmp( type, T_VERSION_STR ) == 0 ) {
506 		ldif_version = atoi( value );
507 		if ( ldif_version != LDIF_VERSION_ONE ) {
508 		    fprintf( stderr, gettext("%s:  LDIF version %d is not supported;"
509 			" use version: %d\n"), ldaptool_progname, ldif_version,
510 			LDIF_VERSION_ONE );
511 		    exit( LDAP_PARAM_ERROR );
512 		}
513 		if ( ldaptool_verbose ) {
514 		    printf( gettext("Processing a version %d LDIF file...\n"),
515 			    ldif_version );
516 		}
517 
518 		/* Now check if there's something left to process   */
519 		/* and if not, go get the new record, else continue */
520 		if ( *rbuf == '\0' ) {
521 			return( 0 );
522 		}
523 
524 	    } else if ( !saw_replica ) {
525 		printf( gettext("%s: skipping change record: no dn: line\n"),
526 			ldaptool_progname );
527 		return( 0 );
528 	    }
529 
530 	    continue; /* skip all lines until we see "dn:" */
531 	}
532 
533 	if ( expect_chgtype_or_control ) {
534 	    expect_chgtype_or_control = 0;
535 	    if ( !use_record && saw_replica ) {
536 		printf( gettext("%s: skipping change record for entry: %s\n\t(LDAP host/port does not match replica: lines)\n"),
537 			ldaptool_progname, dn );
538 		free( dn );
539 		return( 0 );
540 	    }
541 
542 #ifndef SOLARIS_LDAP_CMD
543 	    if ( strcasecmp( type, "control" ) == 0 ) {
544 		value = strdup_and_trim( value );
545 		if (ldaptool_parse_ctrl_arg(value, ' ', &ctrl_oid,
546 			&ctrl_criticality, &ctrl_value, &vlen)) {
547 			    usage();
548 		}
549         	ldctrl = calloc(1,sizeof(LDAPControl));
550         	if (ctrl_value) {
551         	    rc = ldaptool_berval_from_ldif_value( ctrl_value, vlen,
552 			 &(ldctrl->ldctl_value),
553 			 1 /* recognize file URLs */, 0 /* always try file */,
554             		 1 /* report errors */ );
555         	    if ((rc = ldaptool_fileurlerr2ldaperr( rc )) != LDAP_SUCCESS) {
556             		fprintf( stderr, gettext("Unable to parse %s\n"), ctrl_value);
557             		usage();
558         	    }
559         	}
560         	ldctrl->ldctl_oid = ctrl_oid;
561         	ldctrl->ldctl_iscritical = ctrl_criticality;
562         	ldaptool_add_control_to_array(ldctrl, ldaptool_request_ctrls);
563 		expect_chgtype_or_control = 1;
564 		continue;
565 	    }
566 #endif /* SOLARIS_LDAP_CMD */
567 
568 	    if ( strcasecmp( type, T_CHANGETYPESTR ) == 0 ) {
569 		value = strdup_and_trim( value );
570 		if ( strcasecmp( value, T_MODIFYCTSTR ) == 0 ) {
571 		    new_entry = 0;
572 		    expect_modop = 1;
573 		} else if ( strcasecmp( value, T_ADDCTSTR ) == 0 ) {
574 		    new_entry = 1;
575 		    modop = LDAP_MOD_ADD;
576 		} else if ( strcasecmp( value, T_MODRDNCTSTR ) == 0 ) {
577 		    expect_newrdn = 1;
578 		    moddn = 1;
579 		} else if ( strcasecmp( value, T_MODDNCTSTR ) == 0 ) {
580 		    expect_newrdn = 1;
581 		    moddn = 1;
582 		} else if ( strcasecmp( value, T_RENAMECTSTR ) == 0 ) {
583 		    expect_newrdn = 1;
584 		    rename = 1;
585 		} else if ( strcasecmp( value, T_DELETECTSTR ) == 0 ) {
586 		    got_all = delete_entry = 1;
587 		} else {
588 		    fprintf( stderr,
589 			    gettext("%s:  unknown %s \"%s\" (line %d of entry: %s)\n"),
590 			    ldaptool_progname, T_CHANGETYPESTR, value,
591 			    linenum, dn );
592 		    rc = LDAP_PARAM_ERROR;
593 		}
594 		free( value );
595 		continue;
596 	    } else if ( newval ) {		/*  missing changetype => add */
597 		new_entry = 1;
598 		modop = LDAP_MOD_ADD;
599 	    } else {
600 	      /*The user MUST put in changetype: blah
601 	       unless adding a new entry with either -a or ldapadd*/
602 		fprintf(stderr, gettext("%s: Missing changetype operation specification.\n\tThe dn line must be followed by \"changetype: operation\"\n\t(unless ldapmodify is called with -a option)\n\twhere operation is add|delete|modify|modrdn|moddn|rename\n\t\"%s\" is not a valid changetype operation specification\n\t(line %d of entry %s)\n"),
603 		ldaptool_progname, type, linenum, dn);
604 		rc = LDAP_PARAM_ERROR;
605 		/*expect_modop = 1;	 missing changetype => modify */
606 	    }
607 	}
608 
609 	if ( expect_modop ) {
610 	    expect_modop = 0;
611 	    expect_sep = 1;
612 	    if ( strcasecmp( type, T_MODOPADDSTR ) == 0 ) {
613 		modop = LDAP_MOD_ADD;
614 		continue;
615 	    } else if ( strcasecmp( type, T_MODOPREPLACESTR ) == 0 ) {
616 		modop = LDAP_MOD_REPLACE;
617 		continue;
618 	    } else if ( strcasecmp( type, T_MODOPDELETESTR ) == 0 ) {
619 		modop = LDAP_MOD_DELETE;
620 		addmodifyop( &pmods, modop, value, NULL, 0 );
621 		continue;
622 #ifdef SOLARIS_LDAP_CMD
623 	    }  else { /* no modify op: use default */
624 		modop = replace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD;
625 	    }
626 #else
627 	    }  else { /*Bug 27479. Remove default add operation*/
628 	      fprintf(stderr, gettext("%s: Invalid parameter \"%s\" specified for changetype modify (line %d of entry %s)\n"),
629 		      ldaptool_progname, type, linenum, dn);
630 	      rc = LDAP_PARAM_ERROR;
631 	    }
632 #endif	/* SOLARIS_LDAP_CMD */
633 
634 	  }
635 
636 	if ( expect_newrdn ) {
637 	    if ( strcasecmp( type, T_NEWRDNSTR ) == 0 ) {
638 		if ( *value == '\0' ) {
639 		    fprintf( stderr,
640 			    gettext("%s: newrdn value missing (line %d of entry: %s)\n"),
641 			    ldaptool_progname, linenum, dn == NULL ? "" : dn );
642 		    rc = LDAP_PARAM_ERROR;
643 		} else if (( newrdn = strdup( value )) == NULL ) {
644 		    perror( "strdup" );
645 		    exit( LDAP_NO_MEMORY );
646 		} else {
647 		    expect_newrdn = 0;
648 		    if ( rename ) {
649 			expect_newparent = 1;
650 		    } else {
651 			expect_deleteoldrdn = 1;
652 		    }
653 		}
654 	    } else {
655 		fprintf( stderr, gettext("%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry %s)\n"),
656 			ldaptool_progname, T_NEWRDNSTR, type, linenum, dn );
657 		rc = LDAP_PARAM_ERROR;
658 	    }
659 	} else if ( expect_newparent ) {
660 	    expect_newparent = 0;
661 	    if ( rename ) {
662 		expect_deleteoldrdn = 1;
663 	    }
664 	    if ( strcasecmp( type, T_NEWPARENTSTR ) == 0
665 		    || strcasecmp( type, T_NEWSUPERIORSTR ) == 0 ) {
666 		if (( newparent = strdup( value )) == NULL ) {
667 		    perror( "strdup" );
668 		    exit( LDAP_NO_MEMORY );
669 		}
670 	    } else {
671 		/* Since this is an optional argument for rename/moddn, cause
672 		 * the current line to be re-evaluated if newparent doesn't
673 		 * follow deleteoldrdn.
674 		 */
675 		newparent = NULL;
676 		goto evaluate_line;
677 	    }
678 	} else if ( expect_deleteoldrdn ) {
679 	    if ( strcasecmp( type, T_DELETEOLDRDNSTR ) == 0 ) {
680 		if ( *value == '\0' ) {
681 		    fprintf( stderr,
682 			    gettext("%s: missing 0 or 1 (line %d of entry: %s)\n"),
683 			    ldaptool_progname, linenum, dn == NULL ? "" : dn );
684 		    rc = LDAP_PARAM_ERROR;
685 		} else {
686 		    deleteoldrdn = ( *value == '0' ) ? 0 : 1;
687 		    expect_deleteoldrdn = 0;
688 		    if ( moddn ) {
689 			expect_newparent = 1;
690 		    }
691 		}
692 	    } else {
693 		fprintf( stderr, gettext("%s: expecting \"%s:\" but saw \"%s:\" (line %d of entry %s)\n"),
694 			ldaptool_progname, T_DELETEOLDRDNSTR, type, linenum,
695 			dn );
696 		rc = LDAP_PARAM_ERROR;
697 	    }
698 	    got_all = 1;
699 	} else if ( got_all ) {
700 	    fprintf( stderr,
701 		    gettext("%s: extra lines at end (line %d of entry %s)\n"),
702 		    ldaptool_progname, linenum, dn );
703 	    rc = LDAP_PARAM_ERROR;
704 	    got_all = 1;
705 	} else {
706 	    addmodifyop( &pmods, modop, type, value, vlen );
707 	    /*There was a value to replace*/
708 	    got_value = 1;
709 
710 	}
711     }
712 
713     if ( rc == 0 ) {
714 	if ( delete_entry ) {
715 #ifdef SOLARIS_LDAP_CMD
716 	    rc = dodelete( ld, dn );
717 #else
718 	    rc = dodelete( dn );
719 #endif	/* SOLARIS_LDAP_CMD */
720 	} else if ( newrdn != NULL ) {
721 #ifdef SOLARIS_LDAP_CMD
722 	    rc = dorename( ld, dn, newrdn, newparent, deleteoldrdn );
723 #else
724 	    rc = dorename( dn, newrdn, newparent, deleteoldrdn );
725 #endif	/* SOLARIS_LDAP_CMD */
726 	    rename = 0;
727 	} else {
728 
729 	  /*Patch to fix Bug 22183
730 	    If pmods is null, then there is no
731 	    attribute to replace, so we alloc
732 	    an empty pmods*/
733 	  if (modop == LDAP_MOD_REPLACE && !got_value && expect_sep){
734 	    addmodifyop( &pmods, modop, value, NULL, 0);
735 	  }/*End Patch*/
736 
737 
738 #ifdef SOLARIS_LDAP_CMD
739 	  rc = domodify( ld, dn, pmods, new_entry );
740 #else
741 	  rc = domodify( dn, pmods, new_entry );
742 #endif	/* SOLARIS_LDAP_CMD */
743 	}
744 
745 	if ( rc == LDAP_SUCCESS ) {
746 	    rc = 0;
747 	}
748     }
749 
750     if ( dn != NULL ) {
751 	free( dn );
752     }
753     if ( newrdn != NULL ) {
754 	free( newrdn );
755     }
756     if ( newparent != NULL ) {
757 	free( newparent );
758     }
759     if ( pmods != NULL ) {
760 	freepmods( pmods );
761     }
762 
763     return( rc );
764 }
765 
766 
767 static int
768 #ifdef SOLARIS_LDAP_CMD
769 process_ldapmod_rec( LDAP *ld, char *rbuf )
770 #else
771 process_ldapmod_rec( char *rbuf )
772 #endif	/* SOLARIS_LDAP_CMD */
773 {
774     char	*line, *dn, *p, *q, *attr, *value;
775     int		rc, linenum, modop;
776     LDAPMod	**pmods;
777 
778     pmods = NULL;
779     dn = NULL;
780     linenum = 0;
781     line = rbuf;
782     rc = 0;
783 
784     while ( rc == 0 && rbuf != NULL && *rbuf != '\0' ) {
785 	++linenum;
786 	if (( p = strchr( rbuf, '\n' )) == NULL ) {
787 	    rbuf = NULL;
788 	} else {
789 	    if ( *(p-1) == '\\' ) {	/* lines ending in '\' are continued */
790 		strcpy( p - 1, p );
791 		rbuf = p;
792 		continue;
793 	    }
794 	    *p++ = '\0';
795 	    rbuf = p;
796 	}
797 
798 	if ( dn == NULL ) {	/* first line contains DN */
799 	    if (( dn = strdup( line )) == NULL ) {
800 		perror( "strdup" );
801 		exit( LDAP_NO_MEMORY );
802 	    }
803 	} else {
804 	    if (( p = strchr( line, '=' )) == NULL ) {
805 		value = NULL;
806 		p = line + strlen( line );
807 	    } else {
808 		*p++ = '\0';
809 		value = p;
810 	    }
811 
812 	    for ( attr = line; *attr != '\0' && isspace( *attr ); ++attr ) {
813 		;	/* skip attribute leading white space */
814 	    }
815 
816 	    for ( q = p - 1; q > attr && isspace( *q ); --q ) {
817 		*q = '\0';	/* remove attribute trailing white space */
818 	    }
819 
820 	    if ( value != NULL ) {
821 		while ( isspace( *value )) {
822 		    ++value;		/* skip value leading white space */
823 		}
824 		for ( q = value + strlen( value ) - 1; q > value &&
825 			isspace( *q ); --q ) {
826 		    *q = '\0';	/* remove value trailing white space */
827 		}
828 		if ( *value == '\0' ) {
829 		    value = NULL;
830 		}
831 
832 	    }
833 
834 	    if ( value == NULL && newval ) {
835 		fprintf( stderr, gettext("%s: missing value on line %d (attr is %s)\n"),
836 			ldaptool_progname, linenum, attr );
837 		rc = LDAP_PARAM_ERROR;
838 	    } else {
839 		 switch ( *attr ) {
840 		case '-':
841 		    modop = LDAP_MOD_DELETE;
842 		    ++attr;
843 		    break;
844 		case '+':
845 		    modop = LDAP_MOD_ADD;
846 		    ++attr;
847 		    break;
848 		default:
849 #ifdef SOLARIS_LDAP_CMD
850 		    modop = replace ? LDAP_MOD_REPLACE : LDAP_MOD_ADD;
851 #else
852 		    /*Bug 27479. Remove the add default*/
853 		      fprintf(stderr, gettext("%s: Invalid parameter specified for changetype modify (line %d of entry %s)\n"),
854 		      ldaptool_progname, linenum, dn);
855 		      rc = LDAP_PARAM_ERROR;
856 #endif	/* SOLARIS_LDAP_CMD */
857 		}
858 
859 		addmodifyop( &pmods, modop, attr, value,
860 			( value == NULL ) ? 0 : strlen( value ));
861 	    }
862 	}
863 
864 	line = rbuf;
865     }
866 
867     if ( rc == 0 ) {
868 	if ( dn == NULL ) {
869 	    rc = LDAP_PARAM_ERROR;
870 #ifdef SOLARIS_LDAP_CMD
871 	} else if (( rc = domodify( ld, dn, pmods, newval )) == LDAP_SUCCESS ){
872 #else
873 	} else if (( rc = domodify( dn, pmods, newval )) == LDAP_SUCCESS ){
874 #endif	/* SOLARIS_LDAP_CMD */
875 	  rc = 0;
876 	}
877       }
878 
879     if ( pmods != NULL ) {
880 	freepmods( pmods );
881     }
882     if ( dn != NULL ) {
883 	free( dn );
884     }
885 
886     return( rc );
887 }
888 
889 
890 static void
891 addmodifyop( LDAPMod ***pmodsp, int modop, char *attr, char *value, int vlen )
892 {
893     LDAPMod		**pmods;
894     int			i, j, rc;
895     struct berval	*bvp;
896 
897     pmods = *pmodsp;
898     modop |= LDAP_MOD_BVALUES;
899 
900     i = 0;
901     if ( pmods != NULL ) {
902 	for ( ; pmods[ i ] != NULL; ++i ) {
903 	    if ( strcasecmp( pmods[ i ]->mod_type, attr ) == 0 &&
904 		    pmods[ i ]->mod_op == modop ) {
905 		break;
906 	    }
907 	}
908     }
909 
910     if ( pmods == NULL || pmods[ i ] == NULL ) {
911 	if (( pmods = (LDAPMod **)LDAPTOOL_SAFEREALLOC( pmods, (i + 2) *
912 		sizeof( LDAPMod * ))) == NULL ) {
913 	    perror( "realloc" );
914 	    exit( LDAP_NO_MEMORY );
915 	}
916 	*pmodsp = pmods;
917 	pmods[ i + 1 ] = NULL;
918 	if (( pmods[ i ] = (LDAPMod *)calloc( 1, sizeof( LDAPMod )))
919 		== NULL ) {
920 	    perror( "calloc" );
921 	    exit( LDAP_NO_MEMORY );
922 	}
923 	pmods[ i ]->mod_op = modop;
924 	if (( pmods[ i ]->mod_type = strdup( attr )) == NULL ) {
925 	    perror( "strdup" );
926 	    exit( LDAP_NO_MEMORY );
927 	}
928     }
929 
930     if ( value != NULL ) {
931 	j = 0;
932 	if ( pmods[ i ]->mod_bvalues != NULL ) {
933 	    for ( ; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
934 		;
935 	    }
936 	}
937 	if (( pmods[ i ]->mod_bvalues = (struct berval **)
938 		LDAPTOOL_SAFEREALLOC( pmods[ i ]->mod_bvalues,
939 		(j + 2) * sizeof( struct berval * ))) == NULL ) {
940 	    perror( "realloc" );
941 	    exit( LDAP_NO_MEMORY );
942 	}
943 	pmods[ i ]->mod_bvalues[ j + 1 ] = NULL;
944 	if (( bvp = (struct berval *)malloc( sizeof( struct berval )))
945 		== NULL ) {
946 	    perror( "malloc" );
947 	    exit( LDAP_NO_MEMORY );
948 	}
949 	pmods[ i ]->mod_bvalues[ j ] = bvp;
950 
951 #ifdef notdef
952 	if (ldaptool_verbose) {
953 		printf(gettext("%s: value: %s vlen: %d\n"), "ldapmodify", value, vlen);
954 	}
955 #endif
956 	rc = ldaptool_berval_from_ldif_value( value, vlen, bvp,
957 		    ( ldif_version >= LDIF_VERSION_ONE ), valsfromfiles,
958 			1 /* report errors */ );
959 	if ( rc != LDAPTOOL_FILEURL_SUCCESS ) {
960 	    exit( ldaptool_fileurlerr2ldaperr( rc ));
961 	}
962     }
963 }
964 
965 
966 static int
967 #ifdef SOLARIS_LDAP_CMD
968 domodify( LDAP *ld, char *dn, LDAPMod **pmods, int newentry )
969 #else
970 domodify( char *dn, LDAPMod **pmods, int newentry )
971 #endif	/* SOLARIS_LDAP_CMD */
972 {
973     int			i, j, notascii, op;
974     struct berval	*bvp;
975 
976     if ( pmods == NULL ) {
977 	fprintf( stderr, gettext("%s: no attributes to change or add (entry %s)\n"),
978 		ldaptool_progname, dn );
979 	return( LDAP_PARAM_ERROR );
980     }
981 
982     if ( ldaptool_verbose ) {
983 	for ( i = 0; pmods[ i ] != NULL; ++i ) {
984 	    op = pmods[ i ]->mod_op & ~LDAP_MOD_BVALUES;
985 	    printf( gettext("%s %s:\n"), op == LDAP_MOD_REPLACE ?
986 		    gettext("replace") : op == LDAP_MOD_ADD ?
987 		    gettext("add") : gettext("delete"), pmods[ i ]->mod_type );
988 	    if ( pmods[ i ]->mod_bvalues != NULL ) {
989 		for ( j = 0; pmods[ i ]->mod_bvalues[ j ] != NULL; ++j ) {
990 		    bvp = pmods[ i ]->mod_bvalues[ j ];
991 		    notascii = 0;
992 		    if ( !display_binary_values ) {
993 			notascii = !ldaptool_berval_is_ascii( bvp );
994 		    }
995 		    if ( notascii ) {
996 			printf( gettext("\tNOT ASCII (%ld bytes)\n"), bvp->bv_len );
997 		    } else {
998 			printf( "\t%s\n", bvp->bv_val );
999 		    }
1000 		}
1001 	    }
1002 	}
1003     }
1004 
1005     if ( !ldapmodify_quiet) {
1006 	if ( newentry ) {
1007 	    printf( gettext("%sadding new entry %s\n"),
1008 		ldaptool_not ? "!" : "", dn );
1009 	} else {
1010 	    printf( gettext("%smodifying entry %s\n"),
1011 		ldaptool_not ? "!" : "", dn );
1012 	}
1013     }
1014 
1015     if ( !ldaptool_not ) {
1016 	if ( newentry ) {
1017 	unsigned int	sleep_interval = 2; /* seconds */
1018 
1019 #ifdef SOLARIS_LDAP_CMD
1020 	    /* Backward compatibility with old Solaris command */
1021 	    unsigned int nb = 0;
1022 	    timestruc_t to;
1023 	    while ((i = ldaptool_add_ext_s( ld, dn, pmods,
1024 			ldaptool_request_ctrls, NULL, "ldap_add" ))
1025 			!= LDAP_SUCCESS) {
1026 		if (i == LDAP_BUSY) {
1027 			if ( sleep_interval > 3600 ) {
1028 				printf(gettext("ldap_add: Unable to complete "
1029 						"request.  Server is too "
1030 						"busy servicing other "
1031 						"requests\n"));
1032 				break;
1033 			}
1034 			if ( !ldapmodify_quiet ) {
1035 				printf(gettext("ldap_add: LDAP_BUSY returned "
1036 						"by server.  Will retry "
1037 						"operation in %d seconds\n"),
1038 						sleep_interval);
1039 			}
1040 			sleep( sleep_interval );
1041 			sleep_interval *= 2;
1042 		} else if (i == LDAP_NO_SUCH_OBJECT) {
1043 			/*
1044 			 * Wait for the parent entry to be created by
1045 			 * another thread. Do not retry more than the
1046 			 * number of threads.
1047 			 */
1048 			++nb;
1049 			if (nb >= nbthreads)
1050 				break;
1051 			mutex_lock(&wait_mutex);
1052 			to.tv_sec = 5;
1053 			to.tv_nsec = 0;
1054 			if (cond_reltimedwait(&wait_cond, &wait_mutex, &to)
1055 				== ETIME) {
1056 					nb = nbthreads; /* last chance */
1057 			}
1058 			mutex_unlock(&wait_mutex);
1059 		} else {
1060 			break;
1061 		}
1062 	    }
1063 	    cond_broadcast(&wait_cond);
1064 #else
1065 	    while ((i = ldaptool_add_ext_s( ld, dn, pmods,
1066 			ldaptool_request_ctrls, NULL, "ldap_add" ))
1067 			== LDAP_BUSY) {
1068 		if ( sleep_interval > 3600 ) {
1069 			printf("ldap_add: Unable to complete request. ");
1070 			printf("Server is too ");
1071 			printf("busy servicing other requests\n");
1072 			break;
1073 		}
1074 		if ( !ldapmodify_quiet ) {
1075 			printf("ldap_add: LDAP_BUSY returned by server. ");
1076 			printf("Will retry operation ");
1077 			printf("in %d seconds\n", sleep_interval);
1078 		}
1079 		sleep( sleep_interval );
1080 		sleep_interval *= 2;
1081 	    }
1082 #endif	/* SOLARIS_LDAP_CMD */
1083 	} else {
1084 	    i = ldaptool_modify_ext_s( ld, dn, pmods, ldaptool_request_ctrls,
1085 		    NULL, "ldap_modify" );
1086 	}
1087 	if ( i == LDAP_SUCCESS && ldaptool_verbose ) {
1088 	    printf( gettext("modify complete\n") );
1089 	}
1090     } else {
1091 	i = LDAP_SUCCESS;
1092     }
1093 
1094     if ( !ldapmodify_quiet) {
1095 	putchar( '\n' );
1096     }
1097 
1098     return( i );
1099 }
1100 
1101 
1102 static int
1103 #ifdef SOLARIS_LDAP_CMD
1104 dodelete( LDAP *ld, char *dn )
1105 #else
1106 dodelete( char *dn )
1107 #endif	/* SOLARIS_LDAP_CMD */
1108 {
1109     int	rc;
1110 
1111     printf( gettext("%sdeleting entry %s\n"), ldaptool_not ? "!" : "", dn );
1112     if ( !ldaptool_not ) {
1113 	if (( rc = ldaptool_delete_ext_s( ld, dn, ldaptool_request_ctrls,
1114 		NULL, "ldap_delete" )) == LDAP_SUCCESS && ldaptool_verbose ) {
1115 	    printf( gettext("delete complete") );
1116 	}
1117     } else {
1118 	rc = LDAP_SUCCESS;
1119     }
1120 
1121     putchar( '\n' );
1122 
1123     return( rc );
1124 }
1125 
1126 
1127 static int
1128 #ifdef SOLARIS_LDAP_CMD
1129 dorename( LDAP *ld, char *dn, char *newrdn, char *newparent, int deleteoldrdn )
1130 #else
1131 dorename( char *dn, char *newrdn, char *newparent, int deleteoldrdn )
1132 #endif	/* SOLARIS_LDAP_CMD */
1133 {
1134     int	rc;
1135 
1136     if ( ldaptool_verbose ) {
1137 	if ( newparent == NULL ) {
1138 	    printf(deleteoldrdn ?
1139 		  gettext("new RDN: %s (do not keep existing values)\n"):
1140 		  gettext("new RDN: %s (keep existing values)\n"));
1141 	} else {
1142 	    printf(deleteoldrdn ?
1143 		  gettext("new RDN: %s, new parent %s ( do not keep existing values)\n"):
1144 		  gettext("new RDN: %s, new parent %s ( keep existing values)\n"));
1145 	}
1146     }
1147 
1148     printf( gettext("%smodifying RDN of entry %s%s\n"),
1149 	    ldaptool_not ? "!" : "", dn, ( newparent == NULL ) ? "" :
1150 	    gettext(" and/or moving it beneath a new parent\n") );
1151 
1152     if ( !ldaptool_not ) {
1153 	if (( rc = ldaptool_rename_s( ld, dn, newrdn, newparent, deleteoldrdn,
1154 		ldaptool_request_ctrls, NULL, "ldap_rename" )) == LDAP_SUCCESS
1155 		&& ldaptool_verbose ) {
1156 	    printf( gettext("rename completed\n") );
1157 	}
1158     } else {
1159 	rc = LDAP_SUCCESS;
1160     }
1161 
1162     putchar( '\n' );
1163 
1164     return( rc );
1165 }
1166 
1167 
1168 static void
1169 freepmods( LDAPMod **pmods )
1170 {
1171     int	i;
1172 
1173     for ( i = 0; pmods[ i ] != NULL; ++i ) {
1174 	if ( pmods[ i ]->mod_bvalues != NULL ) {
1175 	    ber_bvecfree( pmods[ i ]->mod_bvalues );
1176 	}
1177 	if ( pmods[ i ]->mod_type != NULL ) {
1178 	    free( pmods[ i ]->mod_type );
1179 	}
1180 	free( pmods[ i ] );
1181     }
1182     free( pmods );
1183 }
1184 
1185 
1186 static char *
1187 read_one_record( FILE *fp )
1188 {
1189     int         len, gotnothing;
1190     char        *buf, line[ LDAPMOD_MAXLINE ];
1191     int		lcur, lmax;
1192 
1193     lcur = lmax = 0;
1194     buf = NULL;
1195     gotnothing = 1;
1196 
1197 #ifdef SOLARIS_LDAP_CMD
1198     mutex_lock(&read_mutex);
1199 
1200     if (fp == NULL) {
1201     	mutex_unlock(&read_mutex);
1202 	return(NULL);
1203     }
1204 #endif
1205 
1206     while ( fgets( line, sizeof(line), fp ) != NULL ) {
1207 	if ( (len = strlen( line )) < 2 ) {
1208 	    if ( gotnothing ) {
1209 		continue;
1210 	    } else {
1211 		break;
1212 	    }
1213 	}
1214 
1215 	/* Check if the blank line starts with '\r' (CR) */
1216 	if ( ((len = strlen( line )) == 2) && (line[0] == '\r') ) {
1217 	    if ( gotnothing ) {
1218 		continue;
1219 	    } else {
1220 		break;
1221 	      }
1222 	}
1223 
1224 	if ( *line == '#' ) {
1225 	    continue;			/* skip comment lines */
1226 	}
1227 
1228 	gotnothing = 0;
1229         if ( lcur + len + 1 > lmax ) {
1230             lmax = LDAPMOD_MAXLINE
1231 		    * (( lcur + len + 1 ) / LDAPMOD_MAXLINE + 1 );
1232 	    if (( buf = (char *)LDAPTOOL_SAFEREALLOC( buf, lmax )) == NULL ) {
1233 		perror( "realloc" );
1234 		#ifdef SOLARIS_LDAP_CMD
1235     			mutex_unlock(&read_mutex);
1236 		#endif
1237 		exit( LDAP_NO_MEMORY );
1238 	    }
1239         }
1240         strcpy( buf + lcur, line );
1241         lcur += len;
1242     }
1243 
1244 #ifdef SOLARIS_LDAP_CMD
1245     mutex_unlock(&read_mutex);
1246 #endif
1247 
1248     return( buf );
1249 }
1250 
1251 
1252 /*
1253  * strdup and trim trailing blanks
1254  */
1255 static char *
1256 strdup_and_trim( char *s )
1257 {
1258     char	*p;
1259 
1260     if (( s = strdup( s )) == NULL ) {
1261 	perror( "strdup" );
1262 	exit( LDAP_NO_MEMORY );
1263     }
1264 
1265     p = s + strlen( s ) - 1;
1266     while ( p >= s && isspace( *p )) {
1267 	--p;
1268     }
1269     *++p = '\0';
1270 
1271     return( s );
1272 }
1273