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