Logo Search packages:      
Sourcecode: ldapvi version File versions  Download package

ldapvi.c

/* (c) 2003 David Lichteblau.  License: GNU GPL. */
#include <curses.h>
#include <signal.h>
#include <term.h>
#include "common.h"

static int
compare(int (*handler)(tentry *, tentry *, LDAPMod **, void *),
      void *userdata,
      GArray *offsets, char *cleanname, char *dataname)
{
      FILE *clean, *data;
      int rc;
      long pos;

      if ( !(clean = fopen(cleanname, "r+"))) syserr();
      if ( !(data = fopen(dataname, "r"))) syserr();
      rc = compare_streams(handler, userdata, offsets, clean, data, &pos);
      if (fclose(clean) == EOF) syserr();
      if (fclose(data) == EOF) syserr();

      if (rc == -2) {
            /* an error has happened */
            int n;

            /* remove already-processed entries from the data file */
            char *tmpname = append(dataname, ".tmp");
            cp(dataname, tmpname, pos, 0);
            rename(tmpname, dataname);
            free(tmpname);

            /* 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) syserr();
      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 (*ptr) g_string_append(newsup, *ptr++);
      for (; *ptr; ptr++) {
            g_string_append_c(newsup, ',');
            g_string_append(newsup, *ptr);
      }
      rc = ldap_rename_s(ld, old, newrdn, newsup->str, dor, ctrls, 0);
      g_string_free(newsup, 1);
      ldap_value_free(newrdns);
      return rc;
}

struct ldapmodify_context {
      LDAP *ld;
      LDAPControl **controls;
      int verbose;
};

static int
ldapmodify_handler(tentry *clean, tentry *modified, LDAPMod **mods,
               void *userdata)
{
      struct ldapmodify_context *ctx = userdata;
      LDAP *ld = ctx->ld;
      LDAPControl **ctrls = ctx->controls;
      int verbose = ctx->verbose;
      
      if (clean && modified) {
            char *dn1 = entry_dn(clean);
            char *dn2 = entry_dn(modified);
            if (mods) {
                  if (verbose) printf("(modify) %s\n", dn1);
                  if (ldap_modify_ext_s(ld, dn2, mods, ctrls, 0)) {
                        ldap_perror(ld, "ldap_modify");
                        return -1;
                  }
            } else {
                  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)) {
                        ldap_perror(ld, "ldap_rename");
                        return -1;
                  }
            }
      } else if (modified) {
            if (verbose) printf("(add) %s\n", entry_dn(modified));
            if (ldap_add_ext_s(ld, entry_dn(modified), mods, ctrls, 0)) {
                  ldap_perror(ld, "ldap_add");
                  return -1;
            }
      } else if (clean) {
            if (verbose) printf("(delete) %s\n", entry_dn(clean));
            if (ldap_delete_ext_s(ld, entry_dn(clean), ctrls, 0)) {
                  ldap_perror(ld, "ldap_delete");
                  return -1;
            }
      } else
            abort();
      return 0;
}

static int
ldif_handler(tentry *clean, tentry *modified, LDAPMod **mods, void *userdata)
{
      FILE *s = userdata;
      if (clean && modified) {
            if (mods)
                  print_ldif_modify(s, entry_dn(modified), mods);
            else {
                  char *dn1 = entry_dn(clean);
                  int deleteoldrdn =
                        frob_rdn(modified, dn1, FROB_RDN_CHECK) == -1;
                  print_ldif_rename(
                        s, entry_dn(clean), entry_dn(modified),
                        deleteoldrdn);
            }
      } else if (modified)
            print_ldif_add(s, entry_dn(modified), mods);
      else if (clean)
            print_ldif_delete(s, entry_dn(clean));
      else
            abort();
      return 0;
}

struct statistics {
      int nmodify, nadd, ndelete, nrename;
};

static int
statistics_handler(tentry *clean, tentry *modified, LDAPMod **mods,
               void *userdata)
{
      struct statistics *st = userdata;

      if (clean && modified) {
            if (mods)
                  st->nmodify++;
            else
                  st->nrename++;
      } else if (modified)
            st->nadd++;
      else if (clean)
            st->ndelete++;
      else
            abort();
      return 0;
}

struct rebind_data {
      char *user;
      char *password;
      LDAPURLDesc *seen;
};

static char *login(
      LDAP *ld, char *user, char *password, int register_callback);

static int
rebind_callback(
      LDAP *ld, const char *url, ber_tag_t request, int msgid, void *args)
{
      struct rebind_data *rebind_data = args;
      char *user = rebind_data->user;
      char *password = rebind_data->password;
      LDAPURLDesc *urld;
      
      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 (;;) {
            switch (choose("Rebind?", "y!nqQ?", "(Type '?' for help.)")) {
            case 'y':
                  user = password = 0;
                  /* fall through */
            case '!':
                  if (login(ld, user, password, 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 '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"
                       "  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 int
rebind(LDAP *ld, char *user, char *password, int register_callback, char **dn)
{
      int free_password = 0;
      int rc = -1;
      struct rebind_data *rebind_data = xalloc(sizeof(struct rebind_data));
      
      if (user && !password) {
            password = get_password();
            free_password = 1;
      }
      if (user && user[0] == '(') 
            /* user is a search filter, not a name */
            if ( !(user = find_user(ld, user)))
                  goto cleanup;
      if (ldap_bind_s(ld, user, password, LDAP_AUTH_SIMPLE)) {
            ldap_perror(ld, "ldap_bind");
            goto cleanup;
      }
      rc = 0;
      if (dn) *dn = user;

      if (register_callback) {
            rebind_data->user = user;
            rebind_data->password = xdup(password);
            rebind_data->seen = 0;
            if (ldap_set_rebind_proc(ld, rebind_callback, rebind_data))
                  ldaperr(ld, "ldap_set_rebind_proc");
      }

cleanup:
      if (free_password)
            free(password);
      return rc;
}

static LDAP *
do_connect(char *server, char *user, char *password,
         int referrals, int starttls, int deref)
{
      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 ( rc = ldap_initialize(&ld, server)) {
            fprintf(stderr, "ldap_initialize: %s\n", ldap_err2string(rc));
            exit(1);
      }
      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, user, password, 1, 0) == -1) {
            ldap_unbind_s(ld);
            return 0;
      }
      if (ldap_set_option(ld, LDAP_OPT_REFERRALS, (void *) referrals))
            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;
}

static char *
login(LDAP *ld, char *user, char *password, int register_callback)
{
      char *dn;
      if (!user) user = getline("Filter or DN: ")->str;
      if (rebind(ld, user, password, register_callback, &dn) == 0)
            printf("OK, bound as %s.\n", dn);
      else
            user = 0;
      return user;
}

static int
save_ldif(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("# apply these changes using ldapmodify(1) like this:\n", s);
      fputs("# ldapmodify", s);
      if (managedsait)
            fputs(" -MM", s);
      if (server) {
            fputs(" -h ", s);
            fputs(server, s);
      }
      if (user) {
            fputs(" -D ", s);
            fputs(user, s);
      }
      fputs(" -f ", s);
      fputs(name->str, s);
      fputs("\n", s);

      compare(ldif_handler, s, offsets, clean, data);
      if (fclose(s) == EOF) syserr();

      printf("Your changes have been saved to %s.\n", name->str);
      return 0;
}

static void
view_ldif(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(ldif_handler, s, offsets, clean, data);
      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(GArray *offsets, char *clean, char *data)
{
      struct statistics st;
      int rc;

retry:
      memset(&st, 0, sizeof(st));
      rc = compare(statistics_handler, &st, offsets, clean, data);

      /* Success? */
      if (rc == 0) {
            if (!(st.nadd + st.ndelete + st.nmodify + st.nrename)) {
                  puts("No changes.");
                  return 0;
            }
            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;
      }

      /* Syntax error */
      for (;;) {
            switch (choose("What now?", "eQ?", "(Type '?' for help.)")) {
            case 'e':
                  edit(data);
                  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(LDAP *ld, GArray *offsets, char *clean, char *data, LDAPControl **ctrls,
       int verbose)
{
      struct ldapmodify_context ctx;
      ctx.ld = ld;
      ctx.controls = ctrls;
      ctx.verbose = verbose;
      
      switch (compare(ldapmodify_handler, &ctx, offsets, clean, data)) {
      case 0:
            puts("Done.");
            exit(0);
      case -1:
            yourfault("unexpected syntax error!");
      case -2:
            /* user error */
            break;
      default:
            abort();
      }
}

FILE *
fixup_streams()
{
      FILE *target_stream = 0;
      
      if (!isatty(0)) yourfault("Error: Standard input is not a terminal.");
      if (!isatty(1)) {
            /* clever user has redirected stdout */

            /* record current stdout */
            int out = dup(fileno(stdout));
            if (out == -1) syserr();
            target_stream = fdopen(out, "w");
            if (close(1) == -1) syserr();

            /* find a terminal and restore fd 1 to a sensible value
             * for reading the password */
            if ( (out = open("/dev/tty", O_RDWR)) != 1)
                  yourfault("Error: Sorry, cannot find a terminal.");
      }
      return target_stream;
}

void
skip(char *dataname, GArray *offsets)
{
      long pos;
      char *key;
      char *tmpname = append(dataname, ".tmp");

      /* find file position right after entry */
      tentry *entry;
      FILE *s = fopen(dataname, "r");
      if (!s) syserr();
      read_entry(s, 0, &key, &entry, 0);
      entry_free(entry);
      if ( (pos = ftell(s)) == -1) syserr();
      if (fclose(s) == EOF) syserr();

      /* remove from datafile */
      cp(dataname, tmpname, pos, 0);
      rename(tmpname, dataname);
      free(tmpname);

      /* remove entry from offsets table */
      if (strcmp(key, "add"))
            g_array_index(offsets, long, atoi(key)) = -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(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 (read_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(char *a, char *b)
{
      GArray *offsets = read_offsets(a);
      compare(ldif_handler, stdout, offsets, a, b);
      g_array_free(offsets, 1);
}

int
main(int argc, const char **argv)
{
      LDAP *ld;
      char *server = 0;
      char *user = 0;
      char *password = 0;
      char *base = 0;
      int scope = LDAP_SCOPE_SUBTREE;
      char *filter = "(objectclass=*)";
      char **attrs = 0;
      static char dir[] = "/tmp/ldapvi-XXXXXX";
      char *clean;
      char *data;
      GArray *offsets;
      int changed;
      int progress = 1;
      int referrals = 1;
      int add = 0;
      FILE *s;
      int managedsait = 0;
      char *sortkeys = 0;
      GPtrArray *ctrls = g_ptr_array_new();
      FILE *target_stream;
      int deref = LDAP_DEREF_NEVER;
      int starttls = 0;
      int verbose = 0;
      int noquestions = 0;

      if (argc >= 2 && !strcmp(argv[1], "--diff")) {
            if (argc != 4) {
                  fputs("wrong number of arguments to --diff\n", stderr);
                  usage(2, 1);
            }
            offline_diff((char *) argv[2], (char *) argv[3]);
            exit(0);
      }

      parse_arguments(argc, argv, &server, &base, &scope, &filter, &attrs,
                  &user, &password, &progress, &referrals, &add,
                  ctrls, &managedsait, &sortkeys, &starttls, &deref,
                  &verbose, &noquestions);
      target_stream = fixup_streams();

      ld = do_connect(server, user, password, referrals, starttls, deref);
      if (!ld) exit(1);
      setupterm(0, 1, 0);

      if (sortkeys)
            append_sort_control(ld, ctrls, sortkeys);
      g_ptr_array_add(ctrls, 0);
      if (target_stream) {
            if (add) yourfault("Cannot --add entries noninteractively.");
            search(target_stream, ld, base, scope, filter, attrs,
                   (void *) ctrls->pdata, progress, 1);
            exit(0);
      }

      mkdtemp(dir);
      on_exit((on_exit_function) cleanup, dir);
      signal(SIGTERM, cleanup_signal);
      signal(SIGINT, cleanup_signal);
      signal(SIGPIPE, SIG_IGN);
      clean = append(dir, "/clean");
      data = append(dir, "/data");
      
      if ( !(s = fopen(clean, "w"))) syserr();
      fputs("# ldapvi(1)\n", s);
      if (add)
            offsets = g_array_new(0, 0, sizeof(long));
      else
            offsets = search(s, ld, base, scope, filter, attrs,
                         (void *) ctrls->pdata, progress, noquestions);
      if (fclose(s) == EOF) syserr();
      cp(clean, data, 0, 0);
      edit(data);

      if (noquestions) {
            if (!analyze_changes(offsets, clean, data)) return 0;
            commit(ld, offsets, clean, data, (void *) ctrls->pdata,
                   verbose);
            return 1;
      }

      changed = 1;
      for (;;) {
            if (changed)
                  if (!analyze_changes(offsets, clean, data)) return 0;
            changed = 0;
            switch (choose("Action?", "yqQvebrs?", "(Type '?' for help.)")) {
            case 'y':
                  commit(ld, offsets, clean, data, (void *) ctrls->pdata,
                         verbose);
                  changed = 1;
                  break; /* reached only on user error */
            case 'q':
                  if (save_ldif(offsets, clean, data,
                              server, user, managedsait))
                        break;
                  return 0;
            case 'Q':
                  return 0;
            case 'v':
                  view_ldif(dir, offsets, clean, data);
                  break;
            case 'e':
                  edit(data);
                  changed = 1;
                  break;
            case 'b':
                  user = login(ld, 0, 0, 1);
                  changed = 1; /* print stats again */
                  break;
            case 'r':
                  ldap_unbind_s(ld);
                  ld = do_connect(
                        server, user, password, referrals,
                        starttls, deref);
                  printf("Connected to %s.\n", server);
                  changed = 1; /* print stats again */
                  break;
            case 's':
                  skip(data, offsets);
                  changed = 1;
                  break;
            case '?':
                  puts("Commands:\n"
                       "  y -- commit changes\n"
                       "  q -- save changes as LDIF and quit\n"
                       "  Q -- discard changes and quit\n"
                       "  v -- view changes as LDIF\n"
                       "  e -- open editor again\n"
                       "  b -- ask for user name and rebind\n"
                       "  r -- reconnect to server\n"
                       "  s -- skip one entry\n"
                       "  ? -- this help");
                  break;
            }
      }
}

Generated by  Doxygen 1.6.0   Back to index