/* -*- show-trailing-whitespace: t; indent-tabs: t -*- * * Copyright (c) 2003,2004,2005,2006 David Lichteblau * Copyright (c) 2006 Perry Nguyen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include "common.h" typedef void (*handler_entry)(char *, tentry *, void *); static void parse_file( FILE *, tparser *, thandler *, void *, handler_entry, void *, int); static void cut_datafile(char *, long, cmdline *); static int write_file_header(FILE *, cmdline *); static int rebind(LDAP *, bind_options *, int, char *, int); static int compare(tparser *p, thandler *handler, void *userdata, GArray *offsets, char *cleanname, char *dataname, long *error_position, cmdline *cmdline) { FILE *clean, *data; int rc; long pos; if ( !(clean = fopen(cleanname, "r+"))) syserr(); if ( !(data = fopen(dataname, "r"))) syserr(); rc = compare_streams(p, handler, userdata, offsets, clean, data, &pos, error_position); if (fclose(clean) == EOF) syserr(); if (fclose(data) == EOF) syserr(); if (rc == -2) { /* an error has happened */ int n; if (!cmdline) { fputs("oops: unexpected error in handler\n", stderr); exit(1); } /* remove already-processed entries from the data file */ cut_datafile(dataname, pos, cmdline); /* flag already-processed entries in the offset table */ for (n = 0; n < offsets->len; n++) if (g_array_index(offsets, long, n) < 0) g_array_index(offsets, long, n) = -1; } return rc; } static void cleanup(int rc, char *pathname) { DIR *dir; struct dirent *entry; GString *str = g_string_new(pathname); int len; struct termios term; /* * delete temporary directory */ g_string_append(str, "/"); len = str->len; if ( !(dir = opendir(pathname))) syserr(); while ( (entry = readdir(dir))) if (strcmp(entry->d_name, ".") && strcmp(entry->d_name, "..")){ g_string_truncate(str, len); g_string_append(str, entry->d_name); if (unlink(str->str) == -1) syserr(); } if (closedir(dir) == -1) syserr(); if (rmdir(pathname) == -1) syserr(); g_string_free(str, 1); /* * reset terminal */ if (tcgetattr(0, &term) == -1) /* oh, running without a terminal */ return; term.c_lflag |= ICANON; term.c_lflag |= ECHO; if (tcsetattr(0, TCSANOW, &term) == -1) syserr(); } static void cleanup_signal(int n) { fprintf(stderr, "\nCaught signal %d, exiting...\n", n); exit(2); } static int moddn(LDAP *ld, char *old, char *new, int dor, LDAPControl **ctrls) { int rc; char **newrdns = ldap_explode_dn(new, 0); char **ptr = newrdns; char *newrdn = *ptr++; GString *newsup = g_string_sized_new(strlen(new)); if (newrdn) { if (*ptr) g_string_append(newsup, *ptr++); for (; *ptr; ptr++) { g_string_append_c(newsup, ','); g_string_append(newsup, *ptr); } } else newrdn = ""; rc = ldap_rename_s(ld, old, newrdn, newsup->str, dor, ctrls, 0); g_string_free(newsup, 1); ldap_value_free(newrdns); return rc; } /***************************************** * ldapmodify_handler */ struct ldapmodify_context { LDAP *ld; LDAPControl **controls; int verbose; int noquestions; int continuous; }; static int ldapmodify_error(struct ldapmodify_context *ctx, char *error) { ldap_perror(ctx->ld, error); if (!ctx->continuous) return -1; fputs("(error ignored)\n", stderr); return 0; } static int ldapmodify_change( int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata) { struct ldapmodify_context *ctx = userdata; LDAP *ld = ctx->ld; LDAPControl **ctrls = ctx->controls; int verbose = ctx->verbose; if (verbose) printf("(modify) %s\n", labeldn); if (ldap_modify_ext_s(ld, dn, mods, ctrls, 0)) return ldapmodify_error(ctx, "ldap_modify"); return 0; } static int ldapmodify_rename(int key, char *dn1, tentry *modified, void *userdata) { struct ldapmodify_context *ctx = userdata; LDAP *ld = ctx->ld; LDAPControl **ctrls = ctx->controls; int verbose = ctx->verbose; char *dn2 = entry_dn(modified); int deleteoldrdn = frob_rdn(modified, dn1, FROB_RDN_CHECK) == -1; if (verbose) printf("(rename) %s to %s\n", dn1, dn2); if (moddn(ld, dn1, dn2, deleteoldrdn, ctrls)) return ldapmodify_error(ctx, "ldap_rename"); return 0; } static int ldapmodify_add(int key, char *dn, LDAPMod **mods, void *userdata) { struct ldapmodify_context *ctx = userdata; LDAP *ld = ctx->ld; LDAPControl **ctrls = ctx->controls; int verbose = ctx->verbose; if (verbose) printf("(add) %s\n", dn); if (ldap_add_ext_s(ld, dn, mods, ctrls, 0)) return ldapmodify_error(ctx, "ldap_add"); return 0; } static int ldapmodify_delete(int key, char *dn, void *userdata) { struct ldapmodify_context *ctx = userdata; LDAP *ld = ctx->ld; LDAPControl **ctrls = ctx->controls; int verbose = ctx->verbose; if (verbose) printf("(delete) %s\n", dn); switch (ldap_delete_ext_s(ld, dn, ctrls, 0)) { case 0: break; case LDAP_NOT_ALLOWED_ON_NONLEAF: if (!ctx->noquestions) return -2; /* else fall through */ default: return ldapmodify_error(ctx, "ldap_delete"); } return 0; } static int ldapmodify_rename0( int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata) { struct ldapmodify_context *ctx = userdata; LDAP *ld = ctx->ld; LDAPControl **ctrls = ctx->controls; int verbose = ctx->verbose; if (verbose) printf("(rename) %s to %s\n", dn1, dn2); if (moddn(ld, dn1, dn2, deleteoldrdn, ctrls)) return ldapmodify_error(ctx, "ldap_rename"); return 0; } /***************************************** * ldif_handler */ static int ldif_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata) { FILE *s = userdata; print_ldif_modify(s, dn, mods); return 0; } static int ldif_rename(int key, char *olddn, tentry *modified, void *userdata) { FILE *s = userdata; int deleteoldrdn = frob_rdn(modified, olddn, FROB_RDN_CHECK) == -1; print_ldif_rename( s, olddn, entry_dn(modified), deleteoldrdn); return 0; } static int ldif_add(int key, char *dn, LDAPMod **mods, void *userdata) { FILE *s = userdata; print_ldif_add(s, dn, mods); return 0; } static int ldif_delete(int key, char *dn, void *userdata) { FILE *s = userdata; print_ldif_delete(s, dn); return 0; } static int ldif_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata) { FILE *s = userdata; print_ldif_rename(s, dn1, dn2, deleteoldrdn); return 0; } static thandler ldif_handler = { ldif_change, ldif_rename, ldif_add, ldif_delete, ldif_rename0 }; /***************************************** * noop handler */ static int noop_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata) { return 0; } static int noop_rename(int key, char *olddn, tentry *modified, void *userdata) { return 0; } static int noop_add(int key, char *dn, LDAPMod **mods, void *userdata) { return 0; } static int noop_delete(int key, char *dn, void *userdata) { return 0; } static int noop_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata) { return 0; } /***************************************** * forget_deletions_handler */ static int forget_deletion(int key, char *dn, void *userdata) { GArray *deletions = userdata; g_array_append_val(deletions, key); return 0; } static thandler forget_deletions_handler = { noop_change, noop_rename, noop_add, forget_deletion, noop_rename0 }; /***************************************** * vdif_handler */ static int vdif_change(int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata) { FILE *s = userdata; print_ldapvi_modify(s, dn, mods); return 0; } static int vdif_rename(int key, char *olddn, tentry *modified, void *userdata) { FILE *s = userdata; int deleteoldrdn = frob_rdn(modified, olddn, FROB_RDN_CHECK) == -1; print_ldapvi_rename(s, olddn, entry_dn(modified), deleteoldrdn); return 0; } static int vdif_add(int key, char *dn, LDAPMod **mods, void *userdata) { FILE *s = userdata; print_ldapvi_add(s, dn, mods); return 0; } static int vdif_delete(int key, char *dn, void *userdata) { FILE *s = userdata; print_ldapvi_delete(s, dn); return 0; } static int vdif_rename0(int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata) { FILE *s = userdata; print_ldapvi_rename(s, dn1, dn2, deleteoldrdn); return 0; } /***************************************** * statistics_handler */ struct statistics { int nmodify, nadd, ndelete, nrename; }; static int statistics_change( int key, char *labeldn, char *dn, LDAPMod **mods, void *userdata) { struct statistics *st = userdata; st->nmodify++; return 0; } static int statistics_rename(int key, char *olddn, tentry *modified, void *userdata) { struct statistics *st = userdata; st->nrename++; return 0; } static int statistics_add(int key, char *dn, LDAPMod **mods, void *userdata) { struct statistics *st = userdata; st->nadd++; return 0; } static int statistics_delete(int key, char *dn, void *userdata) { struct statistics *st = userdata; st->ndelete++; return 0; } static int statistics_rename0( int key, char *dn1, char *dn2, int deleteoldrdn, void *userdata) { struct statistics *st = userdata; st->nrename++; return 0; } /* end of handlers * **************************************** */ struct rebind_data { bind_options bind_options; LDAPURLDesc *seen; }; static void toggle_sasl(bind_options *bo) { if (bo->authmethod == LDAP_AUTH_SIMPLE) { bo->authmethod = LDAP_AUTH_SASL; puts("SASL authentication enabled."); printf("SASL mechanism: %s (use '*' to change)\n", bo->sasl_mech ? bo->sasl_mech : "(none)"); } else { bo->authmethod = LDAP_AUTH_SIMPLE; puts("Simple authentication enabled."); } } static void change_mechanism(bind_options *bo) { if (bo->authmethod == LDAP_AUTH_SIMPLE) { bo->authmethod = LDAP_AUTH_SASL; puts("Switching to SASL authentication."); } bo->sasl_mech = getline("SASL mechanism", bo->sasl_mech); } static int rebind_callback( LDAP *ld, const char *url, ber_tag_t request, int msgid, void *args) { struct rebind_data *rebind_data = args; LDAPURLDesc *urld; bind_options bo = rebind_data->bind_options; printf("Received referral to %s.\n", url); if (ldap_url_parse(url, &urld)) { puts("Error: Cannot parse URL."); return -1; } if (rebind_data->seen && !strcmp(rebind_data->seen->lud_scheme, urld->lud_scheme) && !strcmp(rebind_data->seen->lud_host, urld->lud_host) && rebind_data->seen->lud_port == urld->lud_port) /* confirmed already */ return 0; printf("You are not logged in to %s://%s:%d yet.\n" "Type '!' or 'y' to do so.\n", urld->lud_scheme, urld->lud_host, urld->lud_port); for (;;) { bo.dialog = BD_ALWAYS; switch (choose("Rebind?", "y!nB*qQ?", "(Type '?' for help.)")) { case '!': bo.dialog = BD_NEVER; /* fall through */ case 'y': if (rebind(ld, &bo, 0, 0, 1) == 0) { if (rebind_data->seen) ldap_free_urldesc(rebind_data->seen); rebind_data->seen = urld; return 0; } break; case 'n': ldap_free_urldesc(urld); return 0; case '*': change_mechanism(&bo); break; case 'B': toggle_sasl(&bo); break; case 'q': ldap_free_urldesc(urld); return -1; case 'Q': exit(0); case '?': puts("Commands:\n" " y -- ask for user name and rebind\n" " ! -- rebind using cached credentials\n" " n -- don't login, just continue\n" " B -- toggle SASL\n" " * -- set SASL mechanism\n" " q -- give up\n" " Q -- give up and exit ldapvi\n" " ? -- this help"); break; } } } static char * find_user(LDAP *ld, char *filter) { char *dn = 0; LDAPMessage *result = 0; LDAPMessage *entry = 0; if (ldap_bind_s(ld, 0, 0, LDAP_AUTH_SIMPLE)) { ldap_perror(ld, "ldap_bind"); goto cleanup; } if (ldap_search_s(ld, 0, LDAP_SCOPE_SUBTREE, filter, 0, 0, &result)) { ldap_perror(ld, "ldap_search"); goto cleanup; } if ( !(entry = ldap_first_entry(ld, result))) { puts("User not found."); goto cleanup; } if (ldap_next_entry(ld, result)) { puts("More than one entry matched user filter."); goto cleanup; } dn = ldap_get_dn(ld, entry); cleanup: if (result) ldap_msgfree(result); return dn; } static void ensure_tmp_directory(char *dir) { if (strcmp(dir, "/tmp/ldapvi-XXXXXX")) return; mkdtemp(dir); on_exit((on_exit_function) cleanup, dir); signal(SIGTERM, cleanup_signal); signal(SIGINT, cleanup_signal); signal(SIGPIPE, SIG_IGN); } static int rebind_sasl(LDAP *ld, bind_options *bind_options, char *dir, int verbose) { tsasl_defaults *defaults = sasl_defaults_new(bind_options); int rc; int sasl_mode; switch (bind_options->dialog) { case BD_NEVER: sasl_mode = LDAP_SASL_QUIET; break; case BD_AUTO: sasl_mode = LDAP_SASL_AUTOMATIC; break; case BD_ALWAYS: sasl_mode = LDAP_SASL_INTERACTIVE; break; default: abort(); } if (dir) { ensure_tmp_directory(dir); init_sasl_redirection(defaults, append(dir, "/sasl")); } rc = ldap_sasl_interactive_bind_s( ld, bind_options->user, bind_options->sasl_mech, NULL, NULL, sasl_mode, ldapvi_sasl_interact, defaults); sasl_defaults_free(defaults); if (defaults->fd != -1) { finish_sasl_redirection(defaults); free(defaults->pathname); } if (rc != LDAP_SUCCESS) { ldap_perror(ld, "ldap_sasl_interactive_bind_s"); return -1; } if (verbose) printf("Bound as authzid=%s, authcid=%s.\n", bind_options->sasl_authzid, bind_options->sasl_authcid); return 0; } static int rebind_simple(LDAP *ld, bind_options *bo, int verbose) { if (bo->dialog == BD_ALWAYS || (bo->dialog == BD_AUTO && bo->user && !bo->password)) { tdialog d[2]; init_dialog(d, DIALOG_DEFAULT, "Filter or DN", bo->user); init_dialog(d + 1, DIALOG_PASSWORD, "Password", bo->password); dialog("--- Login", d, 2, bo->user ? 1 : 0); bo->user = d[0].value; bo->password = d[1].value; } if (bo->user && bo->user[0] == '(') /* user is a search filter, not a name */ if ( !(bo->user = find_user(ld, bo->user))) return -1; if (ldap_bind_s(ld, bo->user, bo->password, LDAP_AUTH_SIMPLE)) { ldap_perror(ld, "ldap_bind"); return -1; } if (verbose) printf("Bound as %s.\n", bo->user); return 0; } static int rebind(LDAP *ld, bind_options *bind_options, int register_callback, char *dir, int verbose) { int rc = -1; struct rebind_data *rebind_data = xalloc(sizeof(struct rebind_data)); switch (bind_options->authmethod) { case LDAP_AUTH_SASL: if (rebind_sasl(ld, bind_options, dir, verbose)) return -1; break; case LDAP_AUTH_SIMPLE: if (rebind_simple(ld, bind_options, verbose)) return -1; break; } if (register_callback) { rebind_data->bind_options = *bind_options; rebind_data->bind_options.password = xdup(bind_options->password); rebind_data->seen = 0; if (ldap_set_rebind_proc(ld, rebind_callback, rebind_data)) ldaperr(ld, "ldap_set_rebind_proc"); } return 0; } void init_sasl_arguments(LDAP *ld, bind_options *bind_options) { if (!bind_options->sasl_mech) ldap_get_option(ld, LDAP_OPT_X_SASL_MECH, &bind_options->sasl_mech); if (!bind_options->sasl_realm) ldap_get_option(ld, LDAP_OPT_X_SASL_REALM, &bind_options->sasl_realm); if (!bind_options->sasl_authcid) ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHCID, &bind_options->sasl_authcid); if (!bind_options->sasl_authzid) ldap_get_option(ld, LDAP_OPT_X_SASL_AUTHZID, &bind_options->sasl_authzid); } static LDAP * do_connect(char *server, bind_options *bind_options, int referrals, int starttls, int tls, int deref, int profileonlyp, char *dir) { LDAP *ld = 0; int rc = 0; int drei = 3; if (server && !strstr(server, "://")) { char *url = xalloc(strlen(server) + sizeof("ldap://")); strcpy(url, "ldap://"); strcpy(url + 7, server); server = url; } if (ldap_set_option(0, LDAP_OPT_X_TLS_REQUIRE_CERT, (void *) &tls)) ldaperr(0, "ldap_set_option(LDAP_OPT_X_TLS)"); if ( rc = ldap_initialize(&ld, server)) { fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rc)); exit(1); } if (!profileonlyp) init_sasl_arguments(ld, bind_options); if (ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &drei)) ldaperr(ld, "ldap_set_option(LDAP_OPT_PROTOCOL_VERSION)"); if (starttls) if (ldap_start_tls_s(ld, 0, 0)) ldaperr(ld, "ldap_start_tls_s"); if (rebind(ld, bind_options, 1, dir, 0) == -1) { ldap_unbind_s(ld); return 0; } /* after initial bind, always ask interactively (except in '!' rebinds, * which are special-cased): */ bind_options->dialog = BD_ALWAYS; if (ldap_set_option(ld, LDAP_OPT_REFERRALS, referrals ? LDAP_OPT_ON : LDAP_OPT_OFF)) ldaperr(ld, "ldap_set_option(LDAP_OPT_REFERRALS)"); if (ldap_set_option(ld, LDAP_OPT_DEREF, (void *) &deref)) ldaperr(ld, "ldap_set_option(LDAP_OPT_DEREF)"); return ld; } /* * fixme: brauchen wir das mit dem user? dann sollten wir hier noch * sasl support vorsehen * * ldapvi-Kommandozeile konnte auch nicht schaden */ static int save_ldif(tparser *parser, GArray *offsets, char *clean, char *data, char *server, char *user, int managedsait) { int fd; FILE *s; GString *name = g_string_sized_new(300); g_string_append(name, ",ldapvi-"); if (gethostname(name->str + name->len, 300 - name->len) == -1) syserr(); name->len = strlen(name->str); g_string_sprintfa(name, "-%d.ldif", getpid()); if ( (fd = open(name->str, O_WRONLY | O_CREAT | O_EXCL, 0600)) == -1) { int error = errno; fprintf(stderr, "Cannot save %s: ", name->str); errno = error; perror(0); g_string_free(name, 1); return 1; } if ( !(s = fdopen(fd, "w"))) syserr(); fputs("version: 1\n", s); fputs("# to apply these changes using ldapvi, run:\n", s); fputs("# ldapvi --ldapmodify", s); if (managedsait) fputs(" -M", s); if (server) { fputs(" -h ", s); fputs(server, s); } fputc(' ', s); fputs(name->str, s); fputc('\n', s); compare(parser, &ldif_handler, s, offsets, clean, data, 0, 0); if (fclose(s) == EOF) syserr(); printf("Your changes have been saved to %s.\n", name->str); return 0; } static void view_ldif(tparser *parser, char *dir, GArray *offsets, char *clean, char *data) { FILE *s; char *name = append(dir, "/ldif"); if ( !(s = fopen(name, "w"))) syserr(); fputs("version: 1\n", s); compare(parser, &ldif_handler, s, offsets, clean, data, 0, 0); if (fclose(s) == EOF) syserr(); view(name); free(name); } static thandler vdif_handler = { vdif_change, vdif_rename, vdif_add, vdif_delete, vdif_rename0 }; static void view_vdif(tparser *parser, char *dir, GArray *offsets, char *clean, char *data) { FILE *s; char *name = append(dir, "/vdif"); if ( !(s = fopen(name, "w"))) syserr(); fputs("version: ldapvi\n", s); compare(parser, &vdif_handler, s, offsets, clean, data, 0, 0); if (fclose(s) == EOF) syserr(); view(name); free(name); } static void setcolor(int fg) { char *bold = tigetstr("bold"); char *setaf = tigetstr("setaf"); if (setaf) putp(tparm(setaf, fg)); if (bold) putp(bold); } static void print_counter(int color, char *label, int value) { char *sgr0 = tigetstr("sgr0"); if (value) setcolor(color); printf("%s: %d", label, value); if (sgr0) putp(sgr0); } /* collect statistics. This comparison step is important * for catching syntax errors before real processing starts. */ static int analyze_changes(tparser *p, GArray *offsets, char *clean, char *data, cmdline *cmdline) { struct statistics st; static thandler statistics_handler = { statistics_change, statistics_rename, statistics_add, statistics_delete, statistics_rename0 }; int rc; long pos; retry: memset(&st, 0, sizeof(st)); rc = compare( p, &statistics_handler, &st, offsets, clean, data, &pos, 0); /* Success? */ if (rc == 0) { if (!(st.nadd + st.ndelete + st.nmodify + st.nrename)) { if (!cmdline->quiet) puts("No changes."); return 0; } if (cmdline->quiet) return 1; print_counter(COLOR_GREEN, "add", st.nadd); fputs(", ", stdout); print_counter(COLOR_BLUE, "rename", st.nrename); fputs(", ", stdout); print_counter(COLOR_YELLOW, "modify", st.nmodify); fputs(", ", stdout); print_counter(COLOR_RED, "delete", st.ndelete); putchar('\n'); return 1; } if (cmdline->noninteractive) { fputs("Syntax error in noninteractive mode, giving up.\n", stderr); exit(1); } /* Syntax error */ for (;;) { switch (choose("What now?", "eQ?", "(Type '?' for help.)")) { case 'e': edit_pos(data, pos); goto retry; case 'Q': exit(0); case '?': puts("Commands:\n" " Q -- discard changes and quit\n" " e -- open editor again\n" " ? -- this help"); break; } } } static void commit(tparser *p, LDAP *ld, GArray *offsets, char *clean, char *data, LDAPControl **ctrls, int verbose, int noquestions, int continuous, cmdline *cmdline) { struct ldapmodify_context ctx; static thandler ldapmodify_handler = { ldapmodify_change, ldapmodify_rename, ldapmodify_add, ldapmodify_delete, ldapmodify_rename0 }; ctx.ld = ld; ctx.controls = ctrls; ctx.verbose = verbose; ctx.noquestions = noquestions; ctx.continuous = continuous; switch (compare(p, &ldapmodify_handler, &ctx, offsets, clean, data, 0, cmdline)) { case 0: if (!cmdline->quiet) puts("Done."); write_ldapvi_history(); exit(0); case -1: yourfault("unexpected syntax error!"); case -2: /* user error */ break; default: abort(); } } static int getty(int fd) { if (close(fd) == -1) syserr(); if (open("/dev/tty", O_RDWR) != fd) return -1; return 0; } static int fixup_streams(FILE **source, FILE **target) { int rc = 0; /* find a terminal and restore fds 0, 1 to a sensible value for * reading the password. Save the original streams for later use.*/ if (!isatty(0)) { /* user has redirected stdout */ int in = dup(fileno(stdin)); if (in == -1) syserr(); *source = fdopen(in, "r"); if (getty(0) == -1) rc = -1; } if (!isatty(1)) { /* user has redirected stdout */ int out = dup(fileno(stdout)); if (out == -1) syserr(); *target = fdopen(out, "w"); if (getty(1) == -1) rc = -1; } return rc; } static int ndecimalp(char *str) { char *ptr; strtol(str, &ptr, 10); return !*ptr; } static void skip(tparser *p, char *dataname, GArray *offsets, cmdline *cmdline) { long pos; char *key; FILE *s; if ( !(s = fopen(dataname, "r"))) syserr(); p->skip(s, 0, &key); if ( (pos = ftell(s)) == -1) syserr(); if (fclose(s) == EOF) syserr(); if (key) { cut_datafile(dataname, pos, cmdline); if (ndecimalp(key)) g_array_index(offsets, long, atoi(key)) = -1; free(key); } else { /* Im Normalfall wollen wir einen Eintrag in data * ueberspringen. Wenn aber in data nichts mehr steht, * sind wir ueber die eigentlichen Aenderungen schon * hinweg und es handelt sich um eine Loeschung. In * diesem Fall muessen wir nur das Offset aus der * Tabelle entfernen. */ int n; for (n = 0; n < offsets->len; n++) if (g_array_index(offsets, long, n) >= 0) { g_array_remove_index(offsets, n); break; } } } static tentroid * entroid_set_entry(LDAP *ld, tentroid *entroid, tentry *entry) { int i; tattribute *oc = entry_find_attribute(entry, "objectClass", 0); GPtrArray *values; if (!oc) return 0; entroid_reset(entroid); values = attribute_values(oc); for (i = 0; i < values->len; i++) { GArray *av = g_ptr_array_index(values, i); LDAPObjectClass *cls; { char zero = 0; /* PFUSCH! die GArrays muessen absolut weg! */ g_array_append_val(av, zero); av->len--; } cls = entroid_request_class(entroid, av->data); if (!cls) { g_string_append(entroid->comment, "# "); g_string_append(entroid->comment, entroid->error->str); return entroid; } } if (compute_entroid(entroid) == -1) { g_string_append(entroid->comment, "# "); g_string_append(entroid->comment, entroid->error->str); return entroid; } return entroid; } struct annotation_context { LDAP *ld; FILE *out; tparser *parser; tentroid *entroid; }; static void annotate_entry(char *key, tentry *entry, void *userdata) { struct annotation_context *ctx = userdata; tentroid *entroid = entroid_set_entry(ctx->ld, ctx->entroid, entry); ctx->parser->print(ctx->out, entry, key, entroid); } static void rewrite_comments(LDAP *ld, char *dataname, cmdline *cmdline) { FILE *in; FILE *out; char *tmpname; tparser *p = &ldapvi_parser; thandler *h = &vdif_handler; struct annotation_context ctx; int addp = cmdline->ldapmodify_add; tschema *schema = schema_new(ld); if (!schema) { fputs("Error: Failed to read schema.\n", stderr); return; } tmpname = append(dataname, ".tmp"); if ( !(in = fopen(dataname, "r"))) syserr(); if ( !(out = fopen(tmpname, "w"))) syserr(); write_file_header(out, cmdline); if (cmdline->ldif) { p = &ldif_parser; h = &ldif_handler; } ctx.ld = ld; ctx.out = out; ctx.entroid = entroid_new(schema); ctx.parser = p; parse_file(in, p, h, out, annotate_entry, &ctx, addp); if (fclose(in) == EOF) syserr(); if (fclose(out) == EOF) syserr(); rename(tmpname, dataname); free(tmpname); schema_free(schema); } static void forget_deletions(tparser *p, GArray *offsets, char *clean, char *data) { int i; GArray *deletions = g_array_new(0, 0, sizeof(int)); compare(p, &forget_deletions_handler, deletions, offsets, clean, data, 0, 0); for (i = 0; i < deletions->len; i++) { int n = g_array_index(deletions, int, i); g_array_index(offsets, long, n) = -1; } g_array_free(deletions, 1); } static void append_sort_control(LDAP *ld, GPtrArray *ctrls, char *keystring) { LDAPControl *ctrl; LDAPSortKey **keylist; if (ldap_create_sort_keylist(&keylist, keystring)) ldaperr(ld, "ldap_create_sort_keylist"); if (ldap_create_sort_control(ld, keylist, 1, &ctrl)) ldaperr(ld, "ldap_create_sort_keylist"); g_ptr_array_add(ctrls, ctrl); } static GArray * read_offsets(tparser *p, char *file) { GArray *offsets = g_array_new(0, 0, sizeof(long)); FILE *s; if ( !(s = fopen(file, "r"))) syserr(); for (;;) { long offset; char *key, *ptr; int n; tentry *entry; key = 0; if (p->entry(s, -1, &key, &entry, &offset) == -1) exit(1); if (!key) break; n = strtol(key, &ptr, 10); if (*ptr) { fprintf(stderr, "Error: Invalid key: `%s'.\n", key); exit(1); } if (n != offsets->len) { fprintf(stderr, "Error: Unexpected key: `%s'.\n", key); exit(1); } free(key); entry_free(entry); g_array_append_val(offsets, offset); } if (fclose(s) == -1) syserr(); return offsets; } static void offline_diff(tparser *p, char *a, char *b) { GArray *offsets = read_offsets(p, a); compare(p, &ldif_handler, stdout, offsets, a, b, 0, 0); g_array_free(offsets, 1); } void write_config(LDAP *ld, FILE *f, cmdline *cmdline) { char *user = cmdline->bind_options.user; char *server = cmdline->server; int limit; if (!f) f = stdout; fputs("# ldap.conf(5)\n", f); fputs("# edit this as needed and paste into ~/.ldaprc\n", f); /* URI/HOST */ fputc('\n', f); fputs("# server name\n", f); fputs("# (for parameterless operation, make sure to include at" " least this line)\n", f); if (!server) ldap_get_option(ld, LDAP_OPT_URI, &server); if (!server) ldap_get_option(ld, LDAP_OPT_HOST_NAME, &server); if (server) if (strstr(server, "://")) fprintf(f, "URI %s\n", server); else fprintf(f, "HOST %s\n", server); /* BASE */ fputc('\n', f); fputs("# default search base\n", f); if (cmdline->basedns->len) { GPtrArray *basedns = cmdline->basedns; int i; if (basedns->len > 1) fputs("### multiple namingcontexts found (uncomment" " one of these lines):\n", f); for (i = 0; i < basedns->len; i++) { if (basedns->len > 1) fputc('#', f); fprintf(f, "BASE %s\n", g_ptr_array_index(basedns, i)); } } else { if (!cmdline->discover) fputs("### no search base specified, retry with" " --discover?\n", f); fputs("#BASE \n", f); } /* BINDDN */ fputc('\n', f); fputs("# user to bind as\n", f); if (user && user[0] == '(') user = find_user(ld, user); if (user) fprintf(f, "BINDDN %s\n", user); else fputs("#BINDDN \n", f); /* search options */ fputc('\n', f); fputs("# search parameters (uncomment as needed)\n", f); switch (cmdline->deref) { case LDAP_DEREF_NEVER: fputs("#DEREF never\n", f); break; case LDAP_DEREF_SEARCHING: fputs("#DEREF searcing\n", f); break; case LDAP_DEREF_FINDING: fputs("#DEREF finding\n", f); break; case LDAP_DEREF_ALWAYS: fputs("#DEREF always\n", f); break; } ldap_get_option(ld, LDAP_OPT_SIZELIMIT, &limit); fprintf(f, "#SIZELIMIT %d\n", limit); ldap_get_option(ld, LDAP_OPT_TIMELIMIT, &limit); fprintf(f, "#TIMELIMIT %d\n", limit); } static void add_changerecord(FILE *s, cmdline *cmdline) { switch (cmdline->mode) { case ldapvi_mode_delete: { char **ptr; for (ptr = cmdline->delete_dns; *ptr; ptr++) if (cmdline->ldif) print_ldif_delete(s, *ptr); else print_ldapvi_delete(s, *ptr); break; } case ldapvi_mode_rename: if (cmdline->ldif) print_ldif_rename(s, cmdline->rename_old, cmdline->rename_new, cmdline->rename_dor); else print_ldapvi_rename(s, cmdline->rename_old, cmdline->rename_new, cmdline->rename_dor); break; case ldapvi_mode_modrdn: if (cmdline->ldif) print_ldif_modrdn(s, cmdline->rename_old, cmdline->rename_new, cmdline->rename_dor); else print_ldapvi_modrdn(s, cmdline->rename_old, cmdline->rename_new, cmdline->rename_dor); break; default: abort(); } } static void add_template(LDAP *ld, FILE *s, GPtrArray *wanted, char *base) { int i; tentroid *entroid; LDAPObjectClass *cls; LDAPAttributeType *at; tschema *schema = schema_new(ld); if (!schema) { fputs("Error: Failed to read schema, giving up.\n", stderr); exit(1); } entroid = entroid_new(schema); for (i = 0; i < wanted->len; i++) { char *name = g_ptr_array_index(wanted, i); cls = entroid_request_class(entroid, name); if (!cls) { fputs(entroid->error->str, stderr); exit(1); } if (cls->oc_kind == LDAP_SCHEMA_ABSTRACT) { g_string_append(entroid->comment, "### NOTE: objectclass is abstract: "); g_string_append(entroid->comment, name); g_string_append_c(entroid->comment, '\n'); } } if (compute_entroid(entroid) == -1) { fputs(entroid->error->str, stderr); exit(1); } fputc('\n', s); fputs(entroid->comment->str, s); fprintf(s, "add %s\n", base ? base : ""); for (i = 0; i < entroid->classes->len; i++) { cls = g_ptr_array_index(entroid->classes, i); fprintf(s, "objectClass: %s\n", objectclass_name(cls)); } for (i = 0; i < entroid->must->len; i++) { at = g_ptr_array_index(entroid->must, i); if (strcmp(at->at_oid, "2.5.4.0")) fprintf(s, "%s: \n", attributetype_name(at)); } for (i = 0; i < entroid->may->len; i++) { at = g_ptr_array_index(entroid->may, i); if (strcmp(at->at_oid, "2.5.4.0")) fprintf(s, "#%s: \n", attributetype_name(at)); } entroid_free(entroid); schema_free(schema); } static void parse_file(FILE *in, tparser *p, thandler *h, void *userdata, handler_entry hentry, void *entrydata, int addp) { char *key = 0; for (;;) { long pos; if (p->peek(in, -1, &key, &pos) == -1) exit(1); if (!key) break; if (ndecimalp(key)) { tentry *entry; if (p->entry(in, pos, 0, &entry, 0) == -1) exit(1); if (hentry) hentry(key, entry, entrydata); entry_free(entry); } else { char *k = key; if (!strcmp(key, "add") && !addp) k = "replace"; if (process_immediate(p, h, userdata, in, pos, k) < 0) exit(1); } free(key); } } static int write_file_header(FILE *s, cmdline *cmdline) { int nlines = 0; if (print_binary_mode == PRINT_UTF8 && !cmdline->ldif) { fputs("# -*- coding: utf-8 -*- vim:encoding=utf-8:\n", s); nlines++; } if (cmdline->ldif) { fputs("# " RFC_2849_URL "\n" "# " MANUAL_LDIF_URL "\n", s); nlines += 2; } else { fputs("# " MANUAL_SYNTAX_URL "\n", s); nlines++; } return nlines; } static void cut_datafile(char *dataname, long pos, cmdline *cmdline) { FILE *in; FILE *out; char *tmpname = append(dataname, ".tmp"); if ( !(in = fopen(dataname, "r"))) syserr(); if ( !(out = fopen(tmpname, "w"))) syserr(); if (fseek(in, pos, SEEK_SET) == -1) syserr(); write_file_header(out, cmdline); fputc('\n', out); fcopy(in, out); if (fclose(in) == EOF) syserr(); if (fclose(out) == EOF) syserr(); rename(tmpname, dataname); free(tmpname); } static int can_seek(FILE *s) { long pos; if ( (pos = ftell(s)) == -1) return 0; if (fseek(s, pos, SEEK_SET) == -1) return 0; return 1; } static int copy_sasl_output(FILE *out, char *sasl) { struct stat st; FILE *in; int have_sharpsign = 0; int line = 0; int c; if (lstat(sasl, &st) == -1) return; if ( !(in = fopen(sasl, "r"))) syserr(); if (st.st_size > 0) { fputs("\n# SASL output:\n", out); line += 2; } for (;;) { switch ( c = getc_unlocked(in)) { case 0: case '\r': break; case '\n': if (!have_sharpsign) fputc('#', out); line++; have_sharpsign = 0; fputc(c, out); break; case EOF: if (have_sharpsign) { line++; fputc('\n', out); } if (fclose(in) == EOF) syserr(); return line; default: if (!have_sharpsign) { have_sharpsign = 1; fputs("# ", out); } fputc(c, out); } } } static GArray * main_write_files( LDAP *ld, cmdline *cmdline, char *clean, char *data, char *sasl, GPtrArray *ctrls, FILE *source, int *nlines) { FILE *s; int line; GArray *offsets; if ( !(s = fopen(data, "w"))) syserr(); line = write_file_header(s, cmdline); line += copy_sasl_output(s, sasl); if (cmdline->mode == ldapvi_mode_in) { tparser *p = &ldif_parser; thandler *h = &vdif_handler; FILE *tmp = 0; if (cmdline->in_file) { if ( !(source = fopen(cmdline->in_file, "r+"))) syserr(); } else { if (!source) source = stdin; if (!can_seek(source)) { /* einfach clean als tmpfile nehmen */ if ( !(tmp = fopen(clean, "w+"))) syserr(); fcopy(source, tmp); if (fseek(tmp, 0, SEEK_SET) == -1) syserr(); source = tmp; /* source war stdin, kann offen bleiben */ } } if (cmdline->ldif) h = &ldif_handler; if (cmdline->ldapvi) p = &ldapvi_parser; parse_file(source, p, h, s, 0, 0, cmdline->ldapmodify_add); if (cmdline->in_file) if (fclose(source) == EOF) syserr(); if (tmp) if (unlink(clean) == -1) syserr(); if (fclose(s) == EOF) syserr(); cp("/dev/null", clean, 0, 0); offsets = g_array_new(0, 0, sizeof(long)); } else if (cmdline->classes || cmdline->mode != ldapvi_mode_edit) { if (!cmdline->classes) add_changerecord(s, cmdline); else if (cmdline->classes->len) { char *base = 0; if (cmdline->basedns->len > 0) base = g_ptr_array_index(cmdline->basedns, 0); add_template(ld, s, cmdline->classes, base); } else fputc('\n', s); if (fclose(s) == EOF) syserr(); cp("/dev/null", clean, 0, 0); offsets = g_array_new(0, 0, sizeof(long)); } else { offsets = search(s, ld, cmdline, (void *) ctrls->pdata, 0, cmdline->ldif); if (fclose(s) == EOF) syserr(); cp(data, clean, 0, 0); } *nlines = line; return offsets; } static int main_loop(LDAP *ld, cmdline *cmdline, tparser *parser, GArray *offsets, char *clean, char *data, GPtrArray *ctrls, char *dir) { int changed = 1; int continuous = cmdline->continuous; for (;;) { if (changed) if (!analyze_changes( parser, offsets, clean, data, cmdline)) { write_ldapvi_history(); return 0; } changed = 0; switch (choose("Action?", "yYqQvVebB*rsf+? ", "(Type '?' for help.)")) { case 'Y': continuous = 1; /* fall through */ case 'y': commit(parser, ld, offsets, clean, data, (void *) ctrls->pdata, cmdline->verbose, 0, continuous, cmdline); changed = 1; break; /* reached only on user error */ case 'q': if (save_ldif(parser, offsets, clean, data, cmdline->server, cmdline->bind_options.user, cmdline->managedsait)) break; write_ldapvi_history(); return 0; case 'Q': write_ldapvi_history(); return 0; case 'v': view_ldif(parser, dir, offsets, clean, data); break; case 'V': view_vdif(parser, dir, offsets, clean, data); break; case 'e': edit(data, 0); changed = 1; break; case 'b': rebind(ld, &cmdline->bind_options, 1, 0, 1); changed = 1; /* print stats again */ break; case '*': change_mechanism(&cmdline->bind_options); puts("Type 'b' to log in."); break; case 'B': toggle_sasl(&cmdline->bind_options); puts("Type 'b' to log in."); break; case 'r': ldap_unbind_s(ld); ld = do_connect( cmdline->server, &cmdline->bind_options, cmdline->referrals, cmdline->starttls, cmdline->tls, cmdline->deref, 1, 0); printf("Connected to %s.\n", cmdline->server); changed = 1; /* print stats again */ break; case 's': skip(parser, data, offsets, cmdline); changed = 1; break; case 'f': forget_deletions(parser, offsets, clean, data); changed = 1; break; case '+': rewrite_comments(ld, data, cmdline); edit(data, 0); changed = 1; break; case 'L' - '@': { char *clear = tigetstr("clear"); if (clear && clear != (char *) -1) putp(clear); } break; case '?': puts("Commands:\n" " y -- commit changes\n" " Y -- commit, ignoring all errors\n" " q -- save changes as LDIF and quit\n" " Q -- discard changes and quit\n" " v -- view changes as LDIF change records\n" " V -- view changes as ldapvi change records\n" " e -- open editor again\n" " b -- show login dialog and rebind\n" " B -- toggle SASL\n" " * -- set SASL mechanism\n" " r -- reconnect to server\n" " s -- skip one entry\n" " f -- forget deletions\n" " + -- rewrite file to include schema comments\n" " ? -- this help"); break; } } } int main(int argc, const char **argv) { LDAP *ld; cmdline cmdline; GPtrArray *ctrls = g_ptr_array_new(); static char dir[] = "/tmp/ldapvi-XXXXXX"; char *clean; char *data; char *sasl; GArray *offsets; FILE *source_stream = 0; FILE *target_stream = 0; tparser *parser; int nlines; init_cmdline(&cmdline); if (argc >= 2 && !strcmp(argv[1], "--diff")) { if (argc != 4) { fputs("wrong number of arguments to --diff\n", stderr); usage(2, 1); } offline_diff(&ldapvi_parser, (char *) argv[2], (char *) argv[3]); exit(0); } parse_arguments(argc, argv, &cmdline, ctrls); if (fixup_streams(&source_stream, &target_stream) == -1) cmdline.noninteractive = 1; if (cmdline.noninteractive) { cmdline.noquestions = 1; cmdline.quiet = 1; cmdline.bind_options.dialog = BD_NEVER; } if (cmdline.ldif) parser = &ldif_parser; else parser = &ldapvi_parser; read_ldapvi_history(); setupterm(0, 1, 0); ld = do_connect(cmdline.server, &cmdline.bind_options, cmdline.referrals, cmdline.starttls, cmdline.tls, cmdline.deref, cmdline.profileonlyp, dir); if (!ld) { write_ldapvi_history(); exit(1); } if (cmdline.sortkeys) append_sort_control(ld, ctrls, cmdline.sortkeys); g_ptr_array_add(ctrls, 0); if (cmdline.discover) { if (cmdline.basedns->len > 0) yourfault("Conflicting options given:" " --base and --discover."); discover_naming_contexts(ld, cmdline.basedns); } if (cmdline.config) { write_config(ld, target_stream, &cmdline); write_ldapvi_history(); exit(0); } if (cmdline.mode == ldapvi_mode_out || (cmdline.mode == ldapvi_mode_edit && target_stream)) { if (cmdline.classes) yourfault("Cannot edit entry templates noninteractively."); if (!target_stream) target_stream = stdout; search(target_stream, ld, &cmdline, (void *) ctrls->pdata, 1, cmdline.mode == ldapvi_mode_out ? !cmdline.ldapvi : cmdline.ldif); write_ldapvi_history(); exit(0); } ensure_tmp_directory(dir); clean = append(dir, "/clean"); data = append(dir, "/data"); sasl = append(dir, "/sasl"); offsets = main_write_files( ld, &cmdline, clean, data, sasl, ctrls, source_stream, &nlines); if (!cmdline.noninteractive) { if (target_stream) { FILE *tmp = fopen(data, "r"); if (!tmp) syserr(); fcopy(tmp, target_stream); write_ldapvi_history(); exit(0); } edit(data, nlines + 1); } else if (cmdline.mode == ldapvi_mode_edit) yourfault("Cannot edit entries noninteractively."); if (cmdline.noquestions) { if (!analyze_changes(parser, offsets, clean, data, &cmdline)) { write_ldapvi_history(); return 0; } commit(parser, ld, offsets, clean, data, (void *) ctrls->pdata, cmdline.verbose, 1, cmdline.continuous, &cmdline); fputs("Error in noninteractive mode, giving up.\n", stderr); return 1; } return main_loop( ld, &cmdline, parser, offsets, clean, data, ctrls, dir); }