1 /* -*- show-trailing-whitespace: t; indent-tabs: t -*-
2  *
3  * Copyright (c) 2003,2004,2005,2006 David Lichteblau
4  * Copyright (c) 2006 Perry Nguyen
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  */
20 #include <curses.h>
21 #include <signal.h>
22 #include <term.h>
23 #include "common.h"
24 
25 typedef void (*handler_entry)(char *, tentry *, void *);
26 static void parse_file(
27 	FILE *, tparser *, thandler *, void *, handler_entry, void *, int);
28 static void cut_datafile(char *, long, cmdline *);
29 static int write_file_header(FILE *, cmdline *);
30 static int rebind(LDAP *, bind_options *, int, char *, int);
31 
32 static int
compare(tparser * p,thandler * handler,void * userdata,GArray * offsets,char * cleanname,char * dataname,long * error_position,cmdline * cmdline)33 compare(tparser *p, thandler *handler, void *userdata, GArray *offsets,
34 	char *cleanname, char *dataname, long *error_position,
35 	cmdline *cmdline)
36 {
37 	FILE *clean, *data;
38 	int rc;
39 	long pos;
40 
41 	if ( !(clean = fopen(cleanname, "r+"))) syserr();
42 	if ( !(data = fopen(dataname, "r"))) syserr();
43 	rc = compare_streams(p, handler, userdata, offsets, clean, data, &pos,
44 			     error_position);
45 	if (fclose(clean) == EOF) syserr();
46 	if (fclose(data) == EOF) syserr();
47 
48 	if (rc == -2) {
49 		/* an error has happened */
50 		int n;
51 
52 		if (!cmdline) {
53 			fputs("oops: unexpected error in handler\n", stderr);
54 			exit(1);
55 		}
56 
57 		/* remove already-processed entries from the data file */
58 		cut_datafile(dataname, pos, cmdline);
59 
60 		/* flag already-processed entries in the offset table */
61 		for (n = 0; n < offsets->len; n++)
62 			if (g_array_index(offsets, long, n) < 0)
63 				g_array_index(offsets, long, n) = -1;
64 	}
65 	return rc;
66 }
67 
68 static void
cleanup(int rc,char * pathname)69 cleanup(int rc, char *pathname)
70 {
71 	DIR *dir;
72 	struct dirent *entry;
73 	GString *str = g_string_new(pathname);
74 	int len;
75 	struct termios term;
76 
77 	/*
78 	 * delete temporary directory
79 	 */
80 	g_string_append(str, "/");
81 	len = str->len;
82 
83 	if ( !(dir = opendir(pathname))) syserr();
84 	while ( (entry = readdir(dir)))
85 		if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")){
86 			g_string_truncate(str, len);
87 			g_string_append(str, entry->d_name);
88 			if (unlink(str->str) == -1) syserr();
89 		}
90 	if (closedir(dir) == -1) syserr();
91 	if (rmdir(pathname) == -1) syserr();
92 	g_string_free(str, 1);
93 
94 	/*
95 	 * reset terminal
96 	 */
97 	if (tcgetattr(0, &term) == -1)
98 		/* oh, running without a terminal */
99 		return;
100 	term.c_lflag |= ICANON;
101 	term.c_lflag |= ECHO;
102 	if (tcsetattr(0, TCSANOW, &term) == -1) syserr();
103 }
104 
105 static void
cleanup_signal(int n)106 cleanup_signal(int n)
107 {
108 	fprintf(stderr, "\nCaught signal %d, exiting...\n", n);
109 	exit(2);
110 }
111 
112 static int
moddn(LDAP * ld,char * old,char * new,int dor,LDAPControl ** ctrls)113 moddn(LDAP *ld, char *old, char *new, int dor, LDAPControl **ctrls)
114 {
115 	int rc;
116 	char **newrdns = ldap_explode_dn(new, 0);
117 	char **ptr = newrdns;
118 	char *newrdn = *ptr++;
119 	GString *newsup = g_string_sized_new(strlen(new));
120 
121 	if (newrdn) {
122 		if (*ptr) g_string_append(newsup, *ptr++);
123 		for (; *ptr; ptr++) {
124 			g_string_append_c(newsup, ',');
125 			g_string_append(newsup, *ptr);
126 		}
127 	} else
128 		newrdn = "";
129 	rc = ldap_rename_s(ld, old, newrdn, newsup->str, dor, ctrls, 0);
130 	g_string_free(newsup, 1);
131 	ldap_value_free(newrdns);
132 	return rc;
133 }
134 
135 
136 /*****************************************
137  * ldapmodify_handler
138  */
139 struct ldapmodify_context {
140 	LDAP *ld;
141 	LDAPControl **controls;
142 	int verbose;
143 	int noquestions;
144 	int continuous;
145 };
146 
147 static int
ldapmodify_error(struct ldapmodify_context * ctx,char * error)148 ldapmodify_error(struct ldapmodify_context *ctx, char *error)
149 {
150 	ldap_perror(ctx->ld, error);
151 	if (!ctx->continuous)
152 		return -1;
153 	fputs("(error ignored)\n", stderr);
154 	return 0;
155 }
156 
157 static int
ldapmodify_change(int key,char * labeldn,char * dn,LDAPMod ** mods,void * userdata)158 ldapmodify_change(
159 	int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata)
160 {
161 	struct ldapmodify_context *ctx = userdata;
162 	LDAP *ld = ctx->ld;
163 	LDAPControl **ctrls = ctx->controls;
164 	int verbose = ctx->verbose;
165 
166 	if (verbose) printf("(modify) %s\n", labeldn);
167 	if (ldap_modify_ext_s(ld, dn, mods, ctrls, 0))
168 		return ldapmodify_error(ctx, "ldap_modify");
169 	return 0;
170 }
171 
172 static int
ldapmodify_rename(int key,char * dn1,tentry * modified,void * userdata)173 ldapmodify_rename(int key, char *dn1, tentry *modified, void *userdata)
174 {
175 	struct ldapmodify_context *ctx = userdata;
176 	LDAP *ld = ctx->ld;
177 	LDAPControl **ctrls = ctx->controls;
178 	int verbose = ctx->verbose;
179 
180 	char *dn2 = entry_dn(modified);
181 	int deleteoldrdn = frob_rdn(modified, dn1, FROB_RDN_CHECK) == -1;
182 	if (verbose) printf("(rename) %s to %s\n", dn1, dn2);
183 	if (moddn(ld, dn1, dn2, deleteoldrdn, ctrls))
184 		return ldapmodify_error(ctx, "ldap_rename");
185 	return 0;
186 }
187 
188 static int
ldapmodify_add(int key,char * dn,LDAPMod ** mods,void * userdata)189 ldapmodify_add(int key, char *dn, LDAPMod **mods, void *userdata)
190 {
191 	struct ldapmodify_context *ctx = userdata;
192 	LDAP *ld = ctx->ld;
193 	LDAPControl **ctrls = ctx->controls;
194 	int verbose = ctx->verbose;
195 
196 	if (verbose) printf("(add) %s\n", dn);
197 	if (ldap_add_ext_s(ld, dn, mods, ctrls, 0))
198 		return ldapmodify_error(ctx, "ldap_add");
199 	return 0;
200 }
201 
202 static int
ldapmodify_delete(int key,char * dn,void * userdata)203 ldapmodify_delete(int key, char *dn, void *userdata)
204 {
205 	struct ldapmodify_context *ctx = userdata;
206 	LDAP *ld = ctx->ld;
207 	LDAPControl **ctrls = ctx->controls;
208 	int verbose = ctx->verbose;
209 
210 	if (verbose) printf("(delete) %s\n", dn);
211 	switch (ldap_delete_ext_s(ld, dn, ctrls, 0)) {
212 	case 0:
213 		break;
214 	case LDAP_NOT_ALLOWED_ON_NONLEAF:
215 		if (!ctx->noquestions)
216 			return -2;
217 		/* else fall through */
218 	default:
219 		return ldapmodify_error(ctx, "ldap_delete");
220 	}
221 	return 0;
222 }
223 
224 static int
ldapmodify_rename0(int key,char * dn1,char * dn2,int deleteoldrdn,void * userdata)225 ldapmodify_rename0(
226 	int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata)
227 {
228 	struct ldapmodify_context *ctx = userdata;
229 	LDAP *ld = ctx->ld;
230 	LDAPControl **ctrls = ctx->controls;
231 	int verbose = ctx->verbose;
232 
233 	if (verbose) printf("(rename) %s to %s\n", dn1, dn2);
234 	if (moddn(ld, dn1, dn2, deleteoldrdn, ctrls))
235 		return ldapmodify_error(ctx, "ldap_rename");
236 	return 0;
237 }
238 
239 
240 /*****************************************
241  * ldif_handler
242  */
243 static int
ldif_change(int key,char * labeldn,char * dn,LDAPMod ** mods,void * userdata)244 ldif_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata)
245 {
246 	FILE *s = userdata;
247 	print_ldif_modify(s, dn, mods);
248 	return 0;
249 }
250 
251 static int
ldif_rename(int key,char * olddn,tentry * modified,void * userdata)252 ldif_rename(int key, char *olddn, tentry *modified, void *userdata)
253 {
254 	FILE *s = userdata;
255 	int deleteoldrdn = frob_rdn(modified, olddn, FROB_RDN_CHECK) == -1;
256 	print_ldif_rename(
257 		s, olddn, entry_dn(modified),
258 		deleteoldrdn);
259 	return 0;
260 }
261 
262 static int
ldif_add(int key,char * dn,LDAPMod ** mods,void * userdata)263 ldif_add(int key, char *dn, LDAPMod **mods, void *userdata)
264 {
265 	FILE *s = userdata;
266 	print_ldif_add(s, dn, mods);
267 	return 0;
268 }
269 
270 static int
ldif_delete(int key,char * dn,void * userdata)271 ldif_delete(int key, char *dn, void *userdata)
272 {
273 	FILE *s = userdata;
274 	print_ldif_delete(s, dn);
275 	return 0;
276 }
277 
278 static int
ldif_rename0(int key,char * dn1,char * dn2,int deleteoldrdn,void * userdata)279 ldif_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata)
280 {
281 	FILE *s = userdata;
282 	print_ldif_rename(s, dn1, dn2, deleteoldrdn);
283 	return 0;
284 }
285 
286 static thandler ldif_handler = {
287 	ldif_change,
288 	ldif_rename,
289 	ldif_add,
290 	ldif_delete,
291 	ldif_rename0
292 };
293 
294 
295 /*****************************************
296  * noop handler
297  */
298 static int
noop_change(int key,char * labeldn,char * dn,LDAPMod ** mods,void * userdata)299 noop_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata)
300 {
301 	return 0;
302 }
303 
304 static int
noop_rename(int key,char * olddn,tentry * modified,void * userdata)305 noop_rename(int key, char *olddn, tentry *modified, void *userdata)
306 {
307 	return 0;
308 }
309 
310 static int
noop_add(int key,char * dn,LDAPMod ** mods,void * userdata)311 noop_add(int key, char *dn, LDAPMod **mods, void *userdata)
312 {
313 	return 0;
314 }
315 
316 static int
noop_delete(int key,char * dn,void * userdata)317 noop_delete(int key, char *dn, void *userdata)
318 {
319 	return 0;
320 }
321 
322 static int
noop_rename0(int key,char * dn1,char * dn2,int deleteoldrdn,void * userdata)323 noop_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata)
324 {
325 	return 0;
326 }
327 
328 
329 /*****************************************
330  * forget_deletions_handler
331  */
332 static int
forget_deletion(int key,char * dn,void * userdata)333 forget_deletion(int key, char *dn, void *userdata)
334 {
335 	GArray *deletions = userdata;
336 	g_array_append_val(deletions, key);
337 	return 0;
338 }
339 
340 static thandler forget_deletions_handler = {
341 	noop_change,
342 	noop_rename,
343 	noop_add,
344 	forget_deletion,
345 	noop_rename0
346 };
347 
348 
349 /*****************************************
350  * vdif_handler
351  */
352 static int
vdif_change(int key,char * labeldn,char * dn,LDAPMod ** mods,void * userdata)353 vdif_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata)
354 {
355 	FILE *s = userdata;
356 	print_ldapvi_modify(s, dn, mods);
357 	return 0;
358 }
359 
360 static int
vdif_rename(int key,char * olddn,tentry * modified,void * userdata)361 vdif_rename(int key, char *olddn, tentry *modified, void *userdata)
362 {
363 	FILE *s = userdata;
364 	int deleteoldrdn = frob_rdn(modified, olddn, FROB_RDN_CHECK) == -1;
365 	print_ldapvi_rename(s, olddn, entry_dn(modified), deleteoldrdn);
366 	return 0;
367 }
368 
369 static int
vdif_add(int key,char * dn,LDAPMod ** mods,void * userdata)370 vdif_add(int key, char *dn, LDAPMod **mods, void *userdata)
371 {
372 	FILE *s = userdata;
373 	print_ldapvi_add(s, dn, mods);
374 	return 0;
375 }
376 
377 static int
vdif_delete(int key,char * dn,void * userdata)378 vdif_delete(int key, char *dn, void *userdata)
379 {
380 	FILE *s = userdata;
381 	print_ldapvi_delete(s, dn);
382 	return 0;
383 }
384 
385 static int
vdif_rename0(int key,char * dn1,char * dn2,int deleteoldrdn,void * userdata)386 vdif_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata)
387 {
388 	FILE *s = userdata;
389 	print_ldapvi_rename(s, dn1, dn2, deleteoldrdn);
390 	return 0;
391 }
392 
393 
394 /*****************************************
395  * statistics_handler
396  */
397 struct statistics {
398 	int nmodify, nadd, ndelete, nrename;
399 };
400 
401 static int
statistics_change(int key,char * labeldn,char * dn,LDAPMod ** mods,void * userdata)402 statistics_change(
403 	int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata)
404 {
405 	struct statistics *st = userdata;
406 	st->nmodify++;
407 	return 0;
408 }
409 
410 static int
statistics_rename(int key,char * olddn,tentry * modified,void * userdata)411 statistics_rename(int key, char *olddn, tentry *modified, void *userdata)
412 {
413 	struct statistics *st = userdata;
414 	st->nrename++;
415 	return 0;
416 }
417 
418 static int
statistics_add(int key,char * dn,LDAPMod ** mods,void * userdata)419 statistics_add(int key, char *dn, LDAPMod **mods, void *userdata)
420 {
421 	struct statistics *st = userdata;
422 	st->nadd++;
423 	return 0;
424 }
425 
426 static int
statistics_delete(int key,char * dn,void * userdata)427 statistics_delete(int key, char *dn, void *userdata)
428 {
429 	struct statistics *st = userdata;
430 	st->ndelete++;
431 	return 0;
432 }
433 
434 static int
statistics_rename0(int key,char * dn1,char * dn2,int deleteoldrdn,void * userdata)435 statistics_rename0(
436 	int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata)
437 {
438 	struct statistics *st = userdata;
439 	st->nrename++;
440 	return 0;
441 }
442 
443 
444 /* end of handlers
445  * **************************************** */
446 
447 struct rebind_data {
448         bind_options bind_options;
449 	LDAPURLDesc *seen;
450 };
451 
452 static void
toggle_sasl(bind_options * bo)453 toggle_sasl(bind_options *bo)
454 {
455 	if (bo->authmethod == LDAP_AUTH_SIMPLE) {
456 		bo->authmethod = LDAP_AUTH_SASL;
457 		puts("SASL authentication enabled.");
458 		printf("SASL mechanism: %s (use '*' to change)\n",
459 		       bo->sasl_mech ? bo->sasl_mech : "(none)");
460 	} else {
461 		bo->authmethod = LDAP_AUTH_SIMPLE;
462 		puts("Simple authentication enabled.");
463 	}
464 }
465 
466 static void
change_mechanism(bind_options * bo)467 change_mechanism(bind_options *bo)
468 {
469 	if (bo->authmethod == LDAP_AUTH_SIMPLE) {
470 		bo->authmethod = LDAP_AUTH_SASL;
471 		puts("Switching to SASL authentication.");
472 	}
473 	bo->sasl_mech = ldapvi_getline("SASL mechanism", bo->sasl_mech);
474 }
475 
476 static int
rebind_callback(LDAP * ld,const char * url,ber_tag_t request,int msgid,void * args)477 rebind_callback(
478 	LDAP *ld, const char *url, ber_tag_t request, int msgid, void *args)
479 {
480 	struct rebind_data *rebind_data = args;
481 	LDAPURLDesc *urld;
482 	bind_options bo = rebind_data->bind_options;
483 
484 	printf("Received referral to %s.\n", url);
485 
486 	if (ldap_url_parse(url, &urld)) {
487 		puts("Error: Cannot parse URL.");
488 		return -1;
489 	}
490 	if (rebind_data->seen
491 	    && !strcmp(rebind_data->seen->lud_scheme, urld->lud_scheme)
492 	    && !strcmp(rebind_data->seen->lud_host, urld->lud_host)
493 	    && rebind_data->seen->lud_port == urld->lud_port)
494 		/* confirmed already */
495 		return 0;
496 
497 	printf("You are not logged in to %s://%s:%d yet.\n"
498 	       "Type '!' or 'y' to do so.\n",
499 	       urld->lud_scheme, urld->lud_host, urld->lud_port);
500 	for (;;) {
501 		bo.dialog = BD_ALWAYS;
502 
503 		switch (choose("Rebind?", "y!nB*qQ?", "(Type '?' for help.)"))
504 		{
505 		case '!':
506                         bo.dialog = BD_NEVER;
507 			/* fall through */
508 		case 'y':
509 			if (rebind(ld, &bo, 0, 0, 1) == 0) {
510 				if (rebind_data->seen)
511 					ldap_free_urldesc(rebind_data->seen);
512 				rebind_data->seen = urld;
513 				return 0;
514 			}
515 			break;
516 		case 'n':
517 			ldap_free_urldesc(urld);
518 			return 0;
519 		case '*':
520 			change_mechanism(&bo);
521 			break;
522 		case 'B':
523 			toggle_sasl(&bo);
524 			break;
525 		case 'q':
526 			ldap_free_urldesc(urld);
527 			return -1;
528 		case 'Q':
529 			exit(0);
530 		case '?':
531 			puts("Commands:\n"
532 			     "  y -- ask for user name and rebind\n"
533 			     "  ! -- rebind using cached credentials\n"
534 			     "  n -- don't login, just continue\n"
535 			     "  B -- toggle SASL\n"
536 			     "  * -- set SASL mechanism\n"
537 			     "  q -- give up\n"
538 			     "  Q -- give up and exit ldapvi\n"
539 			     "  ? -- this help");
540 			break;
541 		}
542 	}
543 }
544 
545 static char *
find_user(LDAP * ld,char * filter)546 find_user(LDAP *ld, char *filter)
547 {
548 	char *dn = 0;
549 	LDAPMessage *result = 0;
550 	LDAPMessage *entry = 0;
551 
552 	if (ldap_bind_s(ld, 0, 0, LDAP_AUTH_SIMPLE)) {
553 		ldap_perror(ld, "ldap_bind");
554 		goto cleanup;
555 	}
556 	if (ldap_search_s(ld, 0, LDAP_SCOPE_SUBTREE, filter, 0, 0, &result)) {
557 		ldap_perror(ld, "ldap_search");
558 		goto cleanup;
559 	}
560 	if ( !(entry = ldap_first_entry(ld, result))) {
561 		puts("User not found.");
562 		goto cleanup;
563 	}
564 	if (ldap_next_entry(ld, result)) {
565 		puts("More than one entry matched user filter.");
566 		goto cleanup;
567 	}
568 	dn = ldap_get_dn(ld, entry);
569 
570 cleanup:
571 	if (result) ldap_msgfree(result);
572 	return dn;
573 }
574 
575 static void
ensure_tmp_directory(char * dir)576 ensure_tmp_directory(char *dir)
577 {
578 	if (strcmp(dir, "/tmp/ldapvi-XXXXXX")) return;
579 	mkdtemp(dir);
580 	on_exit((on_exit_function) cleanup, dir);
581 	signal(SIGTERM, cleanup_signal);
582 	signal(SIGINT, cleanup_signal);
583 	signal(SIGPIPE, SIG_IGN);
584 }
585 
586 static int
rebind_sasl(LDAP * ld,bind_options * bind_options,char * dir,int verbose)587 rebind_sasl(LDAP *ld, bind_options *bind_options, char *dir, int verbose)
588 {
589 	tsasl_defaults *defaults = sasl_defaults_new(bind_options);
590 	int rc;
591 	int sasl_mode;
592 
593 	switch (bind_options->dialog) {
594 	case BD_NEVER: sasl_mode = LDAP_SASL_QUIET; break;
595 	case BD_AUTO: sasl_mode = LDAP_SASL_AUTOMATIC; break;
596 	case BD_ALWAYS: sasl_mode = LDAP_SASL_INTERACTIVE; break;
597 	default: abort();
598 	}
599 
600 	if (dir) {
601 		ensure_tmp_directory(dir);
602 		init_sasl_redirection(defaults, append(dir, "/sasl"));
603 	}
604 
605 	rc = ldap_sasl_interactive_bind_s(
606 		ld, bind_options->user, bind_options->sasl_mech, NULL,
607 		NULL, sasl_mode, ldapvi_sasl_interact, defaults);
608 
609 	sasl_defaults_free(defaults);
610 	if (defaults->fd != -1) {
611 		finish_sasl_redirection(defaults);
612 		free(defaults->pathname);
613 	}
614 
615 	if (rc != LDAP_SUCCESS) {
616 		ldap_perror(ld, "ldap_sasl_interactive_bind_s");
617 		return -1;
618 	}
619 
620 	if (verbose)
621 		printf("Bound as authzid=%s, authcid=%s.\n",
622 		       bind_options->sasl_authzid,
623 		       bind_options->sasl_authcid);
624 	return 0;
625 }
626 
627 static int
rebind_simple(LDAP * ld,bind_options * bo,int verbose)628 rebind_simple(LDAP *ld, bind_options *bo, int verbose)
629 {
630 	if (bo->dialog == BD_ALWAYS
631 	    || (bo->dialog == BD_AUTO && bo->user && !bo->password))
632 	{
633 		tdialog d[2];
634 		init_dialog(d, DIALOG_DEFAULT, "Filter or DN", bo->user);
635 		init_dialog(d + 1, DIALOG_PASSWORD, "Password", bo->password);
636 		dialog("--- Login", d, 2, bo->user ? 1 : 0);
637 		bo->user = d[0].value;
638 		bo->password = d[1].value;
639 	}
640 	if (bo->user && bo->user[0] == '(')
641 		/* user is a search filter, not a name */
642 		if ( !(bo->user = find_user(ld, bo->user)))
643 			return -1;
644 	if (ldap_bind_s(ld, bo->user, bo->password, LDAP_AUTH_SIMPLE)) {
645 		ldap_perror(ld, "ldap_bind");
646 		return -1;
647 	}
648 	if (verbose)
649 		printf("Bound as %s.\n", bo->user);
650 	return 0;
651 }
652 
653 static int
rebind(LDAP * ld,bind_options * bind_options,int register_callback,char * dir,int verbose)654 rebind(LDAP *ld, bind_options *bind_options, int register_callback,
655        char *dir, int verbose)
656 {
657 	int rc = -1;
658 	struct rebind_data *rebind_data = xalloc(sizeof(struct rebind_data));
659 
660 	switch (bind_options->authmethod) {
661 	case LDAP_AUTH_SASL:
662 		if (rebind_sasl(ld, bind_options, dir, verbose))
663 			return -1;
664 		break;
665 	case LDAP_AUTH_SIMPLE:
666 		if (rebind_simple(ld, bind_options, verbose))
667 			return -1;
668 		break;
669 	}
670 
671 	if (register_callback) {
672 		rebind_data->bind_options = *bind_options;
673 		rebind_data->bind_options.password
674 			= xdup(bind_options->password);
675 		rebind_data->seen = 0;
676 		if (ldap_set_rebind_proc(ld, rebind_callback, rebind_data))
677 			ldaperr(ld, "ldap_set_rebind_proc");
678 	}
679 	return 0;
680 }
681 
682 void
init_sasl_arguments(LDAP * ld,bind_options * bind_options)683 init_sasl_arguments(LDAP *ld, bind_options *bind_options)
684 {
685 	if (!bind_options->sasl_mech)
686 		ldap_get_option(ld,
687 				LDAP_OPT_X_SASL_MECH,
688 				&bind_options->sasl_mech);
689 	if (!bind_options->sasl_realm)
690 		ldap_get_option(ld,
691 				LDAP_OPT_X_SASL_REALM,
692 				&bind_options->sasl_realm);
693 	if (!bind_options->sasl_authcid)
694 		ldap_get_option(ld,
695 				LDAP_OPT_X_SASL_AUTHCID,
696 				&bind_options->sasl_authcid);
697 	if (!bind_options->sasl_authzid)
698 		ldap_get_option(ld,
699 				LDAP_OPT_X_SASL_AUTHZID,
700 				&bind_options->sasl_authzid);
701 }
702 
703 static LDAP *
do_connect(char * server,bind_options * bind_options,int referrals,int starttls,int tls,int deref,int profileonlyp,char * dir)704 do_connect(char *server, bind_options *bind_options,
705 	   int referrals, int starttls, int tls, int deref, int profileonlyp,
706 	   char *dir)
707 {
708 	LDAP *ld = 0;
709 	int rc = 0;
710 	int drei = 3;
711 
712 	if (server && !strstr(server, "://")) {
713 		char *url = xalloc(strlen(server) + sizeof("ldap://"));
714 		strcpy(url, "ldap://");
715 		strcpy(url + 7, server);
716 		server = url;
717 	}
718 
719 	if (ldap_set_option(0, LDAP_OPT_X_TLS_REQUIRE_CERT, (void *) &tls))
720 		ldaperr(0, "ldap_set_option(LDAP_OPT_X_TLS)");
721 	if ( rc = ldap_initialize(&ld, server)) {
722 		fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rc));
723 		exit(1);
724 	}
725 	if (!profileonlyp)
726 		init_sasl_arguments(ld, bind_options);
727 	if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &drei))
728 		ldaperr(ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)");
729 	if (starttls)
730 		if (ldap_start_tls_s(ld, 0, 0))
731 			ldaperr(ld, "ldap_start_tls_s");
732 	if (rebind(ld, bind_options, 1, dir, 0) == -1) {
733 		ldap_unbind_s(ld);
734 		return 0;
735 	}
736 	/* after initial bind, always ask interactively (except in '!' rebinds,
737 	 * which are special-cased): */
738 	bind_options->dialog = BD_ALWAYS;
739 	if (ldap_set_option(ld, LDAP_OPT_REFERRALS,
740                             referrals ? LDAP_OPT_ON : LDAP_OPT_OFF))
741 		ldaperr(ld, "ldap_set_option(LDAP_OPT_REFERRALS)");
742 	if (ldap_set_option(ld, LDAP_OPT_DEREF, (void *) &deref))
743 		ldaperr(ld, "ldap_set_option(LDAP_OPT_DEREF)");
744 
745 	return ld;
746 }
747 
748 /*
749  * fixme: brauchen wir das mit dem user?  dann sollten wir hier noch
750  * sasl support vorsehen
751  *
752  * ldapvi-Kommandozeile konnte auch nicht schaden
753  */
754 static int
save_ldif(tparser * parser,GArray * offsets,char * clean,char * data,char * server,char * user,int managedsait)755 save_ldif(tparser *parser, GArray *offsets, char *clean, char *data,
756 	  char *server, char *user, int managedsait)
757 {
758 	int fd;
759 	FILE *s;
760 
761 	GString *name = g_string_sized_new(300);
762 	g_string_append(name, ",ldapvi-");
763 	if (gethostname(name->str + name->len, 300 - name->len) == -1)
764 		syserr();
765 	name->len = strlen(name->str);
766 	g_string_sprintfa(name, "-%d.ldif", getpid());
767 
768 	if ( (fd = open(name->str, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) {
769 		int error = errno;
770 		fprintf(stderr, "Cannot save %s: ", name->str);
771 		errno = error;
772 		perror(0);
773 		g_string_free(name, 1);
774 		return 1;
775 	}
776 	if ( !(s = fdopen(fd, "w"))) syserr();
777 
778 	fputs("version: 1\n", s);
779 	fputs("# to apply these changes using ldapvi, run:\n", s);
780 	fputs("#   ldapvi --ldapmodify", s);
781 	if (managedsait)
782 		fputs(" -M", s);
783 	if (server) {
784 		fputs(" -h ", s);
785 		fputs(server, s);
786 	}
787 	fputc(' ', s);
788 	fputs(name->str, s);
789 	fputc('\n', s);
790 
791 	compare(parser, &ldif_handler, s, offsets, clean, data, 0, 0);
792 	if (fclose(s) == EOF) syserr();
793 
794 	printf("Your changes have been saved to %s.\n", name->str);
795 	return 0;
796 }
797 
798 static void
view_ldif(tparser * parser,char * dir,GArray * offsets,char * clean,char * data)799 view_ldif(tparser *parser, char *dir, GArray *offsets, char *clean, char *data)
800 {
801 	FILE *s;
802 	char *name = append(dir, "/ldif");
803 	if ( !(s = fopen(name, "w"))) syserr();
804 	fputs("version: 1\n", s);
805 	compare(parser, &ldif_handler, s, offsets, clean, data, 0, 0);
806 	if (fclose(s) == EOF) syserr();
807 	view(name);
808 	free(name);
809 }
810 
811 static thandler vdif_handler = {
812 	vdif_change,
813 	vdif_rename,
814 	vdif_add,
815 	vdif_delete,
816 	vdif_rename0
817 };
818 
819 static void
view_vdif(tparser * parser,char * dir,GArray * offsets,char * clean,char * data)820 view_vdif(tparser *parser, char *dir, GArray *offsets, char *clean, char *data)
821 {
822 	FILE *s;
823 	char *name = append(dir, "/vdif");
824 
825 	if ( !(s = fopen(name, "w"))) syserr();
826 	fputs("version: ldapvi\n", s);
827 	compare(parser, &vdif_handler, s, offsets, clean, data, 0, 0);
828 	if (fclose(s) == EOF) syserr();
829 	view(name);
830 	free(name);
831 }
832 
833 static void
setcolor(int fg)834 setcolor(int fg)
835 {
836 	char *bold = tigetstr("bold");
837 	char *setaf = tigetstr("setaf");
838 	if (setaf) putp(tparm(setaf, fg));
839 	if (bold) putp(bold);
840 }
841 
842 static void
print_counter(int color,char * label,int value)843 print_counter(int color, char *label, int value)
844 {
845 	char *sgr0 = tigetstr("sgr0");
846 
847 	if (value) setcolor(color);
848 	printf("%s: %d", label, value);
849 	if (sgr0) putp(sgr0);
850 }
851 
852 /* collect statistics.  This comparison step is important
853  * for catching syntax errors before real processing starts.
854  */
855 static int
analyze_changes(tparser * p,GArray * offsets,char * clean,char * data,cmdline * cmdline)856 analyze_changes(tparser *p, GArray *offsets, char *clean, char *data,
857 		cmdline *cmdline)
858 {
859 	struct statistics st;
860 	static thandler statistics_handler = {
861 		statistics_change,
862 		statistics_rename,
863 		statistics_add,
864 		statistics_delete,
865 		statistics_rename0
866 	};
867 	int rc;
868 	long pos;
869 
870 retry:
871 	memset(&st, 0, sizeof(st));
872 	rc = compare(
873 		p, &statistics_handler, &st, offsets, clean, data, &pos, 0);
874 
875 	/* Success? */
876 	if (rc == 0) {
877 		if (!(st.nadd + st.ndelete + st.nmodify + st.nrename)) {
878 			if (!cmdline->quiet)
879 				puts("No changes.");
880 			return 0;
881 		}
882 		if (cmdline->quiet)
883 			return 1;
884 		print_counter(COLOR_GREEN, "add", st.nadd);
885 		fputs(", ", stdout);
886 		print_counter(COLOR_BLUE, "rename", st.nrename);
887 		fputs(", ", stdout);
888 		print_counter(COLOR_YELLOW, "modify", st.nmodify);
889 		fputs(", ", stdout);
890 		print_counter(COLOR_RED, "delete", st.ndelete);
891 		putchar('\n');
892 		return 1;
893 	}
894 
895 	if (cmdline->noninteractive) {
896 		fputs("Syntax error in noninteractive mode, giving up.\n",
897 		      stderr);
898 		exit(1);
899 	}
900 
901 	/* Syntax error */
902 	for (;;) {
903 		switch (choose("What now?", "eQ?", "(Type '?' for help.)")) {
904 		case 'e':
905 			edit_pos(data, pos);
906 			goto retry;
907 		case 'Q':
908 			exit(0);
909 		case '?':
910 			puts("Commands:\n"
911 			     "  Q -- discard changes and quit\n"
912 			     "  e -- open editor again\n"
913 			     "  ? -- this help");
914 			break;
915 		}
916 	}
917 }
918 
919 static void
commit(tparser * p,LDAP * ld,GArray * offsets,char * clean,char * data,LDAPControl ** ctrls,int verbose,int noquestions,int continuous,cmdline * cmdline)920 commit(tparser *p, LDAP *ld, GArray *offsets, char *clean, char *data,
921        LDAPControl **ctrls, int verbose, int noquestions, int continuous,
922        cmdline *cmdline)
923 {
924 	struct ldapmodify_context ctx;
925 	static thandler ldapmodify_handler = {
926 		ldapmodify_change,
927 		ldapmodify_rename,
928 		ldapmodify_add,
929 		ldapmodify_delete,
930 		ldapmodify_rename0
931 	};
932 	ctx.ld = ld;
933 	ctx.controls = ctrls;
934 	ctx.verbose = verbose;
935 	ctx.noquestions = noquestions;
936 	ctx.continuous = continuous;
937 
938 	switch (compare(p, &ldapmodify_handler, &ctx, offsets, clean, data, 0,
939 			cmdline))
940 	{
941 	case 0:
942 		if (!cmdline->quiet)
943 			puts("Done.");
944 		write_ldapvi_history();
945 		exit(0);
946 	case -1:
947 		yourfault("unexpected syntax error!");
948 	case -2:
949 		/* user error */
950 		break;
951 	default:
952 		abort();
953 	}
954 }
955 
956 static int
getty(int fd)957 getty(int fd)
958 {
959 	if (close(fd) == -1)
960 		syserr();
961 	if (open("/dev/tty", O_RDWR) != fd)
962 		return -1;
963 	return 0;
964 }
965 
966 static int
fixup_streams(FILE ** source,FILE ** target)967 fixup_streams(FILE **source, FILE **target)
968 {
969 	int rc = 0;
970 
971 	/* find a terminal and restore fds 0, 1 to a sensible value for
972 	 * reading the password.  Save the original streams for later use.*/
973 
974 	if (!isatty(0)) {
975 		/* user has redirected stdout */
976 		int in = dup(fileno(stdin));
977 		if (in == -1) syserr();
978 		*source = fdopen(in, "r");
979 		if (getty(0) == -1) rc = -1;
980 	}
981 	if (!isatty(1)) {
982 		/* user has redirected stdout */
983 		int out = dup(fileno(stdout));
984 		if (out == -1) syserr();
985 		*target = fdopen(out, "w");
986 		if (getty(1) == -1) rc = -1;
987 	}
988 	return rc;
989 }
990 
991 static int
ndecimalp(char * str)992 ndecimalp(char *str)
993 {
994 	char *ptr;
995 	strtol(str, &ptr, 10);
996 	return !*ptr;
997 }
998 
999 static void
skip(tparser * p,char * dataname,GArray * offsets,cmdline * cmdline)1000 skip(tparser *p, char *dataname, GArray *offsets, cmdline *cmdline)
1001 {
1002 	long pos;
1003 	char *key;
1004 	FILE *s;
1005 
1006 	if ( !(s = fopen(dataname, "r"))) syserr();
1007 	p->skip(s, 0, &key);
1008 	if ( (pos = ftell(s)) == -1) syserr();
1009 	if (fclose(s) == EOF) syserr();
1010 
1011 	if (key) {
1012 		cut_datafile(dataname, pos, cmdline);
1013 		if (ndecimalp(key))
1014 			g_array_index(offsets, long, atoi(key)) = -1;
1015 		free(key);
1016 	} else {
1017                 /* Im Normalfall wollen wir einen Eintrag in data
1018                  * ueberspringen.  Wenn aber in data nichts mehr steht,
1019                  * sind wir ueber die eigentlichen Aenderungen schon
1020                  * hinweg und es handelt sich um eine Loeschung.  In
1021                  * diesem Fall muessen wir nur das Offset aus der
1022                  * Tabelle entfernen. */
1023                 int n;
1024                 for (n = 0; n < offsets->len; n++)
1025                         if (g_array_index(offsets, long, n) >= 0) {
1026                                 g_array_remove_index(offsets, n);
1027 				break;
1028 			}
1029 	}
1030 }
1031 
1032 static tentroid *
entroid_set_entry(LDAP * ld,tentroid * entroid,tentry * entry)1033 entroid_set_entry(LDAP *ld, tentroid *entroid, tentry *entry)
1034 {
1035 	int i;
1036 	tattribute *oc = entry_find_attribute(entry, "objectClass", 0);
1037 	GPtrArray *values;
1038 
1039 	if (!oc)
1040 		return 0;
1041 
1042 	entroid_reset(entroid);
1043 	values = attribute_values(oc);
1044 	for (i = 0; i < values->len; i++) {
1045 		GArray *av = g_ptr_array_index(values, i);
1046 		LDAPObjectClass *cls;
1047 
1048 		{
1049 			char zero = 0;
1050 			/* PFUSCH!  die GArrays muessen absolut weg! */
1051 			g_array_append_val(av, zero);
1052 			av->len--;
1053 		}
1054 
1055 		cls = entroid_request_class(entroid, av->data);
1056 		if (!cls) {
1057 			g_string_append(entroid->comment, "# ");
1058 			g_string_append(entroid->comment, entroid->error->str);
1059 			return entroid;
1060 		}
1061 	}
1062 
1063 	if (compute_entroid(entroid) == -1) {
1064 		g_string_append(entroid->comment, "# ");
1065 		g_string_append(entroid->comment, entroid->error->str);
1066 		return entroid;
1067 	}
1068 	return entroid;
1069 }
1070 
1071 struct annotation_context {
1072 	LDAP *ld;
1073 	FILE *out;
1074 	tparser *parser;
1075 	tentroid *entroid;
1076 };
1077 
1078 static void
annotate_entry(char * key,tentry * entry,void * userdata)1079 annotate_entry(char *key, tentry *entry, void *userdata)
1080 {
1081 	struct annotation_context *ctx = userdata;
1082 	tentroid *entroid = entroid_set_entry(ctx->ld, ctx->entroid, entry);
1083 	ctx->parser->print(ctx->out, entry, key, entroid);
1084 }
1085 
1086 static void
rewrite_comments(LDAP * ld,char * dataname,cmdline * cmdline)1087 rewrite_comments(LDAP *ld, char *dataname, cmdline *cmdline)
1088 {
1089 	FILE *in;
1090 	FILE *out;
1091 	char *tmpname;
1092 	tparser *p = &ldapvi_parser;
1093 	thandler *h = &vdif_handler;
1094 	struct annotation_context ctx;
1095 	int addp = cmdline->ldapmodify_add;
1096 	tschema *schema = schema_new(ld);
1097 
1098 	if (!schema) {
1099 		fputs("Error: Failed to read schema.\n", stderr);
1100 		return;
1101 	}
1102 
1103 	tmpname = append(dataname, ".tmp");
1104 	if ( !(in = fopen(dataname, "r"))) syserr();
1105 	if ( !(out = fopen(tmpname, "w"))) syserr();
1106 
1107 	write_file_header(out, cmdline);
1108 	if (cmdline->ldif) {
1109 		p = &ldif_parser;
1110 		h = &ldif_handler;
1111 	}
1112 	ctx.ld = ld;
1113 	ctx.out = out;
1114 	ctx.entroid = entroid_new(schema);
1115 	ctx.parser = p;
1116 	parse_file(in, p, h, out, annotate_entry, &ctx, addp);
1117 
1118 	if (fclose(in) == EOF) syserr();
1119 	if (fclose(out) == EOF) syserr();
1120 	rename(tmpname, dataname);
1121 	free(tmpname);
1122 	schema_free(schema);
1123 }
1124 
1125 
1126 static void
forget_deletions(tparser * p,GArray * offsets,char * clean,char * data)1127 forget_deletions(tparser *p, GArray *offsets, char *clean, char *data)
1128 {
1129 	int i;
1130 	GArray *deletions = g_array_new(0, 0, sizeof(int));
1131 
1132 	compare(p, &forget_deletions_handler, deletions,
1133 		offsets, clean, data, 0, 0);
1134 	for (i = 0; i < deletions->len; i++) {
1135 		int n = g_array_index(deletions, int, i);
1136 		g_array_index(offsets, long, n) = -1;
1137 	}
1138 	g_array_free(deletions, 1);
1139 }
1140 
1141 static void
append_sort_control(LDAP * ld,GPtrArray * ctrls,char * keystring)1142 append_sort_control(LDAP *ld, GPtrArray *ctrls, char *keystring)
1143 {
1144 	LDAPControl *ctrl;
1145 	LDAPSortKey **keylist;
1146 
1147 	if (ldap_create_sort_keylist(&keylist, keystring))
1148 		ldaperr(ld, "ldap_create_sort_keylist");
1149 	if (ldap_create_sort_control(ld, keylist, 1, &ctrl))
1150 		ldaperr(ld, "ldap_create_sort_keylist");
1151 	g_ptr_array_add(ctrls, ctrl);
1152 }
1153 
1154 static GArray *
read_offsets(tparser * p,char * file)1155 read_offsets(tparser *p, char *file)
1156 {
1157 	GArray *offsets = g_array_new(0, 0, sizeof(long));
1158 	FILE *s;
1159 
1160 	if ( !(s = fopen(file, "r"))) syserr();
1161 	for (;;) {
1162 		long offset;
1163 		char *key, *ptr;
1164 		int n;
1165 		tentry *entry;
1166 
1167 		key = 0;
1168 		if (p->entry(s, -1, &key, &entry, &offset) == -1) exit(1);
1169 		if (!key) break;
1170 
1171 		n = strtol(key, &ptr, 10);
1172 		if (*ptr) {
1173 			fprintf(stderr, "Error: Invalid key: `%s'.\n", key);
1174 			exit(1);
1175 		}
1176 		if (n != offsets->len) {
1177 			fprintf(stderr, "Error: Unexpected key: `%s'.\n", key);
1178 			exit(1);
1179 		}
1180 		free(key);
1181 		entry_free(entry);
1182 		g_array_append_val(offsets, offset);
1183 	}
1184 	if (fclose(s) == -1) syserr();
1185 
1186 	return offsets;
1187 }
1188 
1189 static void
offline_diff(tparser * p,char * a,char * b)1190 offline_diff(tparser *p, char *a, char *b)
1191 {
1192 	GArray *offsets = read_offsets(p, a);
1193 	compare(p, &ldif_handler, stdout, offsets, a, b, 0, 0);
1194 	g_array_free(offsets, 1);
1195 }
1196 
1197 void
write_config(LDAP * ld,FILE * f,cmdline * cmdline)1198 write_config(LDAP *ld, FILE *f, cmdline *cmdline)
1199 {
1200 	char *user = cmdline->bind_options.user;
1201 	char *server = cmdline->server;
1202 	int limit;
1203 
1204 	if (!f) f = stdout;
1205 	fputs("# ldap.conf(5)\n", f);
1206 	fputs("# edit this as needed and paste into ~/.ldaprc\n", f);
1207 
1208 	/* URI/HOST */
1209 	fputc('\n', f);
1210 	fputs("# server name\n", f);
1211 	fputs("# (for parameterless operation, make sure to include at"
1212 	      " least this line)\n",
1213 	      f);
1214 	if (!server)
1215 		ldap_get_option(ld, LDAP_OPT_URI, &server);
1216 	if (!server)
1217 		ldap_get_option(ld, LDAP_OPT_HOST_NAME, &server);
1218 	if (server)
1219 		if (strstr(server, "://"))
1220 			fprintf(f, "URI %s\n", server);
1221 		else
1222 			fprintf(f, "HOST %s\n", server);
1223 
1224 	/* BASE */
1225 	fputc('\n', f);
1226 	fputs("# default search base\n", f);
1227 	if (cmdline->basedns->len) {
1228 		GPtrArray *basedns = cmdline->basedns;
1229 		int i;
1230 		if (basedns->len > 1)
1231 			fputs("### multiple namingcontexts found (uncomment"
1232 			      " one of these lines):\n",
1233 			      f);
1234 		for (i = 0; i < basedns->len; i++) {
1235 			if (basedns->len > 1) fputc('#', f);
1236 			fprintf(f, "BASE %s\n", g_ptr_array_index(basedns, i));
1237 		}
1238 	} else {
1239 		if (!cmdline->discover)
1240 			fputs("### no search base specified, retry with"
1241 			      " --discover?\n",
1242 			      f);
1243 		fputs("#BASE <dn>\n", f);
1244 	}
1245 
1246 	/* BINDDN */
1247 	fputc('\n', f);
1248 	fputs("# user to bind as\n", f);
1249 	if (user && user[0] == '(')
1250 		user = find_user(ld, user);
1251 	if (user)
1252 		fprintf(f, "BINDDN %s\n", user);
1253 	else
1254 		fputs("#BINDDN <dn>\n", f);
1255 
1256 	/* search options */
1257 	fputc('\n', f);
1258 	fputs("# search parameters (uncomment as needed)\n", f);
1259 	switch (cmdline->deref) {
1260 	case LDAP_DEREF_NEVER:
1261 		fputs("#DEREF never\n", f);
1262 		break;
1263 	case LDAP_DEREF_SEARCHING:
1264 		fputs("#DEREF searcing\n", f);
1265 		break;
1266 	case LDAP_DEREF_FINDING:
1267 		fputs("#DEREF finding\n", f);
1268 		break;
1269 	case LDAP_DEREF_ALWAYS:
1270 		fputs("#DEREF always\n", f);
1271 		break;
1272 	}
1273 	ldap_get_option(ld, LDAP_OPT_SIZELIMIT, &limit);
1274 	fprintf(f, "#SIZELIMIT %d\n", limit);
1275 	ldap_get_option(ld, LDAP_OPT_TIMELIMIT, &limit);
1276 	fprintf(f, "#TIMELIMIT %d\n", limit);
1277 }
1278 
1279 static void
add_changerecord(FILE * s,cmdline * cmdline)1280 add_changerecord(FILE *s, cmdline *cmdline)
1281 {
1282 	switch (cmdline->mode) {
1283 	case ldapvi_mode_delete: {
1284 		char **ptr;
1285 		for (ptr = cmdline->delete_dns; *ptr; ptr++)
1286 			if (cmdline->ldif)
1287 				print_ldif_delete(s, *ptr);
1288 			else
1289 				print_ldapvi_delete(s, *ptr);
1290 		break;
1291 	}
1292 	case ldapvi_mode_rename:
1293 		if (cmdline->ldif)
1294 			print_ldif_rename(s,
1295 					  cmdline->rename_old,
1296 					  cmdline->rename_new,
1297 					  cmdline->rename_dor);
1298 		else
1299 			print_ldapvi_rename(s,
1300 					    cmdline->rename_old,
1301 					    cmdline->rename_new,
1302 					    cmdline->rename_dor);
1303 		break;
1304 	case ldapvi_mode_modrdn:
1305 		if (cmdline->ldif)
1306 			print_ldif_modrdn(s,
1307 					  cmdline->rename_old,
1308 					  cmdline->rename_new,
1309 					  cmdline->rename_dor);
1310 		else
1311 			print_ldapvi_modrdn(s,
1312 					    cmdline->rename_old,
1313 					    cmdline->rename_new,
1314 					    cmdline->rename_dor);
1315 		break;
1316 	default:
1317 		abort();
1318 	}
1319 }
1320 
1321 static void
add_template(LDAP * ld,FILE * s,GPtrArray * wanted,char * base)1322 add_template(LDAP *ld, FILE *s, GPtrArray *wanted, char *base)
1323 {
1324 	int i;
1325 	tentroid *entroid;
1326 	LDAPObjectClass *cls;
1327 	LDAPAttributeType *at;
1328 	tschema *schema = schema_new(ld);
1329 
1330 	if (!schema) {
1331 		fputs("Error: Failed to read schema, giving up.\n", stderr);
1332 		exit(1);
1333 	}
1334 
1335 	entroid = entroid_new(schema);
1336 	for (i = 0; i < wanted->len; i++) {
1337 		char *name = g_ptr_array_index(wanted, i);
1338 		cls = entroid_request_class(entroid, name);
1339 		if (!cls) {
1340 			fputs(entroid->error->str, stderr);
1341 			exit(1);
1342 		}
1343 		if (cls->oc_kind == LDAP_SCHEMA_ABSTRACT) {
1344 			g_string_append(entroid->comment,
1345 					"### NOTE: objectclass is abstract: ");
1346 			g_string_append(entroid->comment, name);
1347 			g_string_append_c(entroid->comment, '\n');
1348 		}
1349 	}
1350 
1351 	if (compute_entroid(entroid) == -1) {
1352 		fputs(entroid->error->str, stderr);
1353 		exit(1);
1354 	}
1355 
1356 	fputc('\n', s);
1357 	fputs(entroid->comment->str, s);
1358 	fprintf(s, "add %s\n", base ? base : "<DN>");
1359 
1360 	for (i = 0; i < entroid->classes->len; i++) {
1361 		cls = g_ptr_array_index(entroid->classes, i);
1362 		fprintf(s, "objectClass: %s\n", objectclass_name(cls));
1363 	}
1364 	for (i = 0; i < entroid->must->len; i++) {
1365 		at = g_ptr_array_index(entroid->must, i);
1366 		if (strcmp(at->at_oid, "2.5.4.0"))
1367 			fprintf(s, "%s: \n", attributetype_name(at));
1368 	}
1369 	for (i = 0; i < entroid->may->len; i++) {
1370 		at = g_ptr_array_index(entroid->may, i);
1371 		if (strcmp(at->at_oid, "2.5.4.0"))
1372 			fprintf(s, "#%s: \n", attributetype_name(at));
1373 	}
1374 
1375 	entroid_free(entroid);
1376 	schema_free(schema);
1377 }
1378 
1379 static void
parse_file(FILE * in,tparser * p,thandler * h,void * userdata,handler_entry hentry,void * entrydata,int addp)1380 parse_file(FILE *in,
1381 	   tparser *p, thandler *h, void *userdata,
1382 	   handler_entry hentry, void *entrydata,
1383 	   int addp)
1384 {
1385 	char *key = 0;
1386 
1387 	for (;;) {
1388 		long pos;
1389 
1390 		if (p->peek(in, -1, &key, &pos) == -1) exit(1);
1391 		if (!key) break;
1392 
1393 		if (ndecimalp(key)) {
1394 			tentry *entry;
1395 			if (p->entry(in, pos, 0, &entry, 0) == -1)
1396 				exit(1);
1397 			if (hentry)
1398 				hentry(key, entry, entrydata);
1399 			entry_free(entry);
1400 		} else {
1401 			char *k = key;
1402 			if (!strcmp(key, "add") && !addp)
1403 				k = "replace";
1404 			if (process_immediate(p, h, userdata, in, pos, k) < 0)
1405 				exit(1);
1406 		}
1407 		free(key);
1408 	}
1409 }
1410 
1411 static int
write_file_header(FILE * s,cmdline * cmdline)1412 write_file_header(FILE *s, cmdline *cmdline)
1413 {
1414 	int nlines = 0;
1415 
1416 	if (print_binary_mode == PRINT_UTF8 && !cmdline->ldif) {
1417 		fputs("# -*- coding: utf-8 -*- vim:encoding=utf-8:\n", s);
1418 		nlines++;
1419 	}
1420 	if (cmdline->ldif) {
1421 		fputs("# " RFC_2849_URL "\n" "# " MANUAL_LDIF_URL "\n", s);
1422 		nlines += 2;
1423 	} else  {
1424 		fputs("# " MANUAL_SYNTAX_URL "\n", s);
1425 		nlines++;
1426 	}
1427 
1428 	return nlines;
1429 }
1430 
1431 static void
cut_datafile(char * dataname,long pos,cmdline * cmdline)1432 cut_datafile(char *dataname, long pos, cmdline *cmdline)
1433 {
1434 	FILE *in;
1435 	FILE *out;
1436 	char *tmpname = append(dataname, ".tmp");
1437 
1438 	if ( !(in = fopen(dataname, "r"))) syserr();
1439 	if ( !(out = fopen(tmpname, "w"))) syserr();
1440 	if (fseek(in, pos, SEEK_SET) == -1) syserr();
1441 	write_file_header(out, cmdline);
1442 	fputc('\n', out);
1443 	fcopy(in, out);
1444 	if (fclose(in) == EOF) syserr();
1445 	if (fclose(out) == EOF) syserr();
1446 	rename(tmpname, dataname);
1447 	free(tmpname);
1448 }
1449 
1450 static int
can_seek(FILE * s)1451 can_seek(FILE *s)
1452 {
1453 	long pos;
1454 	if ( (pos = ftell(s)) == -1) return 0;
1455 	if (fseek(s, pos, SEEK_SET) == -1) return 0;
1456 	return 1;
1457 }
1458 
1459 static int
copy_sasl_output(FILE * out,char * sasl)1460 copy_sasl_output(FILE *out, char *sasl)
1461 {
1462 	struct stat st;
1463 	FILE *in;
1464 	int have_sharpsign = 0;
1465 	int line = 0;
1466 	int c;
1467 
1468 	if (lstat(sasl, &st) == -1) return 0;
1469 	if ( !(in = fopen(sasl, "r"))) syserr();
1470 
1471 	if (st.st_size > 0) {
1472 		fputs("\n# SASL output:\n", out);
1473 		line += 2;
1474 	}
1475 	for (;;) {
1476 		switch ( c = getc_unlocked(in)) {
1477 		case 0:
1478 		case '\r':
1479 			break;
1480 		case '\n':
1481 			if (!have_sharpsign)
1482 				fputc('#', out);
1483 			line++;
1484 			have_sharpsign = 0;
1485 			fputc(c, out);
1486 			break;
1487 		case EOF:
1488 			if (have_sharpsign) {
1489 				line++;
1490 				fputc('\n', out);
1491 			}
1492 			if (fclose(in) == EOF) syserr();
1493 			return line;
1494 		default:
1495 			if (!have_sharpsign) {
1496 				have_sharpsign = 1;
1497 				fputs("# ", out);
1498 			}
1499 			fputc(c, out);
1500 		}
1501 	}
1502 }
1503 
1504 static GArray *
main_write_files(LDAP * ld,cmdline * cmdline,char * clean,char * data,char * sasl,GPtrArray * ctrls,FILE * source,int * nlines)1505 main_write_files(
1506 	LDAP *ld, cmdline *cmdline,
1507 	char *clean, char *data, char *sasl,
1508 	GPtrArray *ctrls,
1509 	FILE *source,
1510 	int *nlines)
1511 {
1512 	FILE *s;
1513 	int line;
1514 	GArray *offsets;
1515 
1516 	if ( !(s = fopen(data, "w"))) syserr();
1517 	line = write_file_header(s, cmdline);
1518 	line += copy_sasl_output(s, sasl);
1519 
1520 	if (cmdline->mode == ldapvi_mode_in) {
1521 		tparser *p = &ldif_parser;
1522 		thandler *h = &vdif_handler;
1523 		FILE *tmp = 0;
1524 
1525 		if (cmdline->in_file) {
1526 			if ( !(source = fopen(cmdline->in_file, "r+")))
1527 				syserr();
1528 		} else {
1529 			if (!source)
1530 				source = stdin;
1531 			if (!can_seek(source)) {
1532 				/* einfach clean als tmpfile nehmen */
1533 				if ( !(tmp = fopen(clean, "w+"))) syserr();
1534 				fcopy(source, tmp);
1535 				if (fseek(tmp, 0, SEEK_SET) == -1) syserr();
1536 				source = tmp;
1537 				/* source war stdin, kann offen bleiben */
1538 			}
1539 		}
1540 
1541 		if (cmdline->ldif) h = &ldif_handler;
1542 		if (cmdline->ldapvi) p = &ldapvi_parser;
1543 		parse_file(source, p, h, s, 0, 0, cmdline->ldapmodify_add);
1544 
1545 		if (cmdline->in_file)
1546 			if (fclose(source) == EOF) syserr();
1547 		if (tmp)
1548 			if (unlink(clean) == -1) syserr();
1549 		if (fclose(s) == EOF) syserr();
1550 		cp("/dev/null", clean, 0, 0);
1551 		offsets = g_array_new(0, 0, sizeof(long));
1552 	} else if (cmdline->classes || cmdline->mode != ldapvi_mode_edit) {
1553 		if (!cmdline->classes)
1554 			add_changerecord(s, cmdline);
1555 		else if (cmdline->classes->len) {
1556 			char *base = 0;
1557 			if (cmdline->basedns->len > 0)
1558 				base = g_ptr_array_index(cmdline->basedns, 0);
1559 			add_template(ld, s, cmdline->classes, base);
1560 		} else
1561 			fputc('\n', s);
1562 		if (fclose(s) == EOF) syserr();
1563 		cp("/dev/null", clean, 0, 0);
1564 		offsets = g_array_new(0, 0, sizeof(long));
1565 	} else {
1566 		offsets = search(s, ld, cmdline, (void *) ctrls->pdata, 0,
1567 				 cmdline->ldif);
1568 		if (fclose(s) == EOF) syserr();
1569 		cp(data, clean, 0, 0);
1570 	}
1571 
1572 	*nlines = line;
1573 	return offsets;
1574 }
1575 
1576 static int
main_loop(LDAP * ld,cmdline * cmdline,tparser * parser,GArray * offsets,char * clean,char * data,GPtrArray * ctrls,char * dir)1577 main_loop(LDAP *ld, cmdline *cmdline,
1578 	  tparser *parser, GArray *offsets, char *clean, char *data,
1579 	  GPtrArray *ctrls, char *dir)
1580 {
1581 	int changed = 1;
1582 	int continuous = cmdline->continuous;
1583 
1584 	for (;;) {
1585 		if (changed)
1586 			if (!analyze_changes(
1587 				    parser, offsets, clean, data, cmdline))
1588 			{
1589 				write_ldapvi_history();
1590 				return 0;
1591 			}
1592 		changed = 0;
1593 		switch (choose("Action?",
1594 			       "yYqQvVebB*rsf+?",
1595 			       "(Type '?' for help.)")) {
1596 		case 'Y':
1597 			continuous = 1;
1598 			/* fall through */
1599 		case 'y':
1600 			commit(parser, ld, offsets, clean, data,
1601 			       (void *) ctrls->pdata, cmdline->verbose, 0,
1602 			       continuous, cmdline);
1603 			changed = 1;
1604 			break; /* reached only on user error */
1605 		case 'q':
1606 			if (save_ldif(parser,
1607 				      offsets, clean, data,
1608 				      cmdline->server,
1609 				      cmdline->bind_options.user,
1610 				      cmdline->managedsait))
1611 				break;
1612 			write_ldapvi_history();
1613 			return 0;
1614 		case 'Q':
1615 			write_ldapvi_history();
1616 			return 0;
1617 		case 'v':
1618 			view_ldif(parser, dir, offsets, clean, data);
1619 			break;
1620 		case 'V':
1621 			view_vdif(parser, dir, offsets, clean, data);
1622 			break;
1623 		case 'e':
1624 			edit(data, 0);
1625 			changed = 1;
1626 			break;
1627 		case 'b':
1628 			rebind(ld, &cmdline->bind_options, 1, 0, 1);
1629 			changed = 1; /* print stats again */
1630 			break;
1631 		case '*':
1632 			change_mechanism(&cmdline->bind_options);
1633 			puts("Type 'b' to log in.");
1634 			break;
1635 		case 'B':
1636 			toggle_sasl(&cmdline->bind_options);
1637 			puts("Type 'b' to log in.");
1638 			break;
1639 		case 'r':
1640 			ldap_unbind_s(ld);
1641 			ld = do_connect(
1642 				cmdline->server,
1643 				&cmdline->bind_options,
1644 				cmdline->referrals,
1645 				cmdline->starttls,
1646 				cmdline->tls,
1647 				cmdline->deref,
1648 				1,
1649 				0);
1650 			printf("Connected to %s.\n", cmdline->server);
1651 			changed = 1; /* print stats again */
1652 			break;
1653 		case 's':
1654 			skip(parser, data, offsets, cmdline);
1655 			changed = 1;
1656 			break;
1657 		case 'f':
1658 			forget_deletions(parser, offsets, clean, data);
1659 			changed = 1;
1660 			break;
1661 		case '+':
1662 			rewrite_comments(ld, data, cmdline);
1663 			edit(data, 0);
1664 			changed = 1;
1665 			break;
1666 		case 'L' - '@':
1667 			{
1668 				char *clear = tigetstr("clear");
1669 				if (clear && clear != (char *) -1)
1670 					putp(clear);
1671 			}
1672 			break;
1673 		case '?':
1674 			puts("Commands:\n"
1675 			     "  y -- commit changes\n"
1676 			     "  Y -- commit, ignoring all errors\n"
1677 			     "  q -- save changes as LDIF and quit\n"
1678 			     "  Q -- discard changes and quit\n"
1679 			     "  v -- view changes as LDIF change records\n"
1680 			     "  V -- view changes as ldapvi change records\n"
1681 			     "  e -- open editor again\n"
1682 			     "  b -- show login dialog and rebind\n"
1683 			     "  B -- toggle SASL\n"
1684 			     "  * -- set SASL mechanism\n"
1685 			     "  r -- reconnect to server\n"
1686 			     "  s -- skip one entry\n"
1687 			     "  f -- forget deletions\n"
1688 			     "  + -- rewrite file to include schema comments\n"
1689 			     "  ? -- this help");
1690 			break;
1691 		}
1692 	}
1693 }
1694 
1695 int
main(int argc,const char ** argv)1696 main(int argc, const char **argv)
1697 {
1698 	LDAP *ld;
1699 	cmdline cmdline;
1700 	GPtrArray *ctrls = g_ptr_array_new();
1701 	static char dir[] = "/tmp/ldapvi-XXXXXX";
1702 	char *clean;
1703 	char *data;
1704 	char *sasl;
1705 	GArray *offsets;
1706 	FILE *source_stream = 0;
1707 	FILE *target_stream = 0;
1708 	tparser *parser;
1709 	int nlines;
1710 
1711 	init_cmdline(&cmdline);
1712 
1713 	if (argc >= 2 && !strcmp(argv[1], "--diff")) {
1714 		if (argc != 4) {
1715 			fputs("wrong number of arguments to --diff\n", stderr);
1716 			usage(2, 1);
1717 		}
1718 		offline_diff(&ldapvi_parser,
1719 			     (char *) argv[2],
1720 			     (char *) argv[3]);
1721 		exit(0);
1722 	}
1723 
1724 	parse_arguments(argc, argv, &cmdline, ctrls);
1725 	if (fixup_streams(&source_stream, &target_stream) == -1)
1726 		cmdline.noninteractive = 1;
1727 	if (cmdline.noninteractive) {
1728 		cmdline.noquestions = 1;
1729 		cmdline.quiet = 1;
1730 		cmdline.bind_options.dialog = BD_NEVER;
1731 	}
1732 	if (cmdline.ldif)
1733 		parser = &ldif_parser;
1734 	else
1735 		parser = &ldapvi_parser;
1736 	read_ldapvi_history();
1737 
1738 	setupterm(0, 1, 0);
1739 	ld = do_connect(cmdline.server,
1740 			&cmdline.bind_options,
1741 			cmdline.referrals,
1742 			cmdline.starttls,
1743 			cmdline.tls,
1744 			cmdline.deref,
1745 			cmdline.profileonlyp,
1746 			dir);
1747 	if (!ld) {
1748 		write_ldapvi_history();
1749 		exit(1);
1750 	}
1751 
1752 	if (cmdline.sortkeys)
1753 		append_sort_control(ld, ctrls, cmdline.sortkeys);
1754 	g_ptr_array_add(ctrls, 0);
1755 
1756 	if (cmdline.discover) {
1757 		if (cmdline.basedns->len > 0)
1758 			yourfault("Conflicting options given:"
1759 				  " --base and --discover.");
1760 		discover_naming_contexts(ld, cmdline.basedns);
1761 	}
1762 
1763 	if (cmdline.config) {
1764 		write_config(ld, target_stream, &cmdline);
1765 		write_ldapvi_history();
1766 		exit(0);
1767 	}
1768 
1769 	if (cmdline.mode == ldapvi_mode_out
1770 	    || (cmdline.mode == ldapvi_mode_edit && target_stream))
1771 	{
1772 		if (cmdline.classes)
1773 			yourfault("Cannot edit entry templates noninteractively.");
1774 		if (!target_stream)
1775 			target_stream = stdout;
1776 		search(target_stream, ld, &cmdline, (void *) ctrls->pdata, 1,
1777 		       cmdline.mode == ldapvi_mode_out
1778 		       ? !cmdline.ldapvi
1779 		       : cmdline.ldif);
1780 		write_ldapvi_history();
1781 		exit(0);
1782 	}
1783 
1784 	ensure_tmp_directory(dir);
1785 	clean = append(dir, "/clean");
1786 	data = append(dir, "/data");
1787 	sasl = append(dir, "/sasl");
1788 
1789 	offsets = main_write_files(
1790 		ld, &cmdline, clean, data, sasl, ctrls, source_stream,
1791 		&nlines);
1792 
1793 	if (!cmdline.noninteractive) {
1794 		if (target_stream) {
1795 			FILE *tmp = fopen(data, "r");
1796 			if (!tmp) syserr();
1797 			fcopy(tmp, target_stream);
1798 			write_ldapvi_history();
1799 			exit(0);
1800 		}
1801 		edit(data, nlines + 1);
1802 	} else if (cmdline.mode == ldapvi_mode_edit)
1803 		yourfault("Cannot edit entries noninteractively.");
1804 
1805 	if (cmdline.noquestions) {
1806 		if (!analyze_changes(parser, offsets, clean, data, &cmdline)) {
1807 			write_ldapvi_history();
1808 			return 0;
1809 		}
1810 		commit(parser, ld, offsets, clean, data, (void *) ctrls->pdata,
1811 		       cmdline.verbose, 1, cmdline.continuous, &cmdline);
1812 		fputs("Error in noninteractive mode, giving up.\n", stderr);
1813 		return 1;
1814 	}
1815 
1816 	return main_loop(
1817 		ld, &cmdline, parser, offsets, clean, data, ctrls, dir);
1818 }
1819