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
42static int		newval, contoper, force, valsfromfiles, display_binary_values;
43static int		ldif_version = -1;	/* -1 => unknown version */
44static char		*rejfile = NULL;
45static char		*bulkimport_suffix = NULL;
46static int		ldapmodify_quiet = 0;
47
48#ifdef SOLARIS_LDAP_CMD
49static int		error = 0, replace, nbthreads = 1;
50static int		thr_create_errors = 0;
51static pthread_mutex_t	read_mutex = {0};
52static pthread_mutex_t	wait_mutex = {0};
53static 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 */
62static 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
93static int process( void *arg );
94static void options_callback( int option, char *optarg );
95static void addmodifyop( LDAPMod ***pmodsp, int modop, char *attr,
96	char *value, int vlen );
97static void freepmods( LDAPMod **pmods );
98static char *read_one_record( FILE *fp );
99static char *strdup_and_trim( char *s );
100
101#ifdef SOLARIS_LDAP_CMD
102static int process_ldapmod_rec( LDAP *ld, char *rbuf );
103static int process_ldif_rec( LDAP *ld, char *rbuf );
104static int domodify( LDAP *ld, char *dn, LDAPMod **pmods, int newentry );
105static int dodelete( LDAP *ld, char *dn );
106static int dorename( LDAP *ld, char *dn, char *newrdn, char *newparent,
107	int deleteoldrdn );
108#else
109static int process_ldapmod_rec( char *rbuf );
110static int process_ldif_rec( char *rbuf );
111static int domodify( char *dn, LDAPMod **pmods, int newentry );
112static int dodelete( char *dn );
113static int dorename( char *dn, char *newrdn, char *newparent,
114	int deleteoldrdn );
115#endif	/* SOLARIS_LDAP_CMD */
116
117static void
118usage( 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
143int
144main( 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
237static int
238process( 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
393static void
394options_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
436static int
437#ifdef SOLARIS_LDAP_CMD
438process_ldif_rec( LDAP *ld, char *rbuf )
439#else
440process_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
498evaluate_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
782static int
783#ifdef SOLARIS_LDAP_CMD
784process_ldapmod_rec( LDAP *ld, char *rbuf )
785#else
786process_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
905static void
906addmodifyop( 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
981static int
982#ifdef SOLARIS_LDAP_CMD
983domodify( LDAP *ld, char *dn, LDAPMod **pmods, int newentry )
984#else
985domodify( 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
1117static int
1118#ifdef SOLARIS_LDAP_CMD
1119dodelete( LDAP *ld, char *dn )
1120#else
1121dodelete( 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
1142static int
1143#ifdef SOLARIS_LDAP_CMD
1144dorename( LDAP *ld, char *dn, char *newrdn, char *newparent, int deleteoldrdn )
1145#else
1146dorename( 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
1183static void
1184freepmods( 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
1201static char *
1202read_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 */
1270static char *
1271strdup_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