ldap(1) Add delete support [0/1]

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

ldap(1) Add delete support [0/1]

Martijn van Duren-5
I would like to see some more functionality in ldap(1), so I started of
with delete, because that's seems to be the easiest/shortest to
implement and the diffs are big enough as is.

I split it up in 2 diffs. This is the first one, which restructures
ldap(1) to make use of a per command environment.

Thoughts? OK?

martijn@

diff --git a/ldapclient.c b/ldapclient.c
index 9763b8e..9f29997 100644
--- a/ldapclient.c
+++ b/ldapclient.c
@@ -52,6 +52,8 @@
 #define F_NEEDAUTH 0x04
 #define F_LDIF 0x08
 
+#define GETOPT_COMMON "c:D:H:vWw:xy:Z"
+
 #define LDAPHOST "localhost"
 #define LDAPFILTER "(objectClass=*)"
 #define LDIF_LINELENGTH 79
@@ -79,9 +81,21 @@ struct ldapc_search {
  char **ls_attr;
 };
 
+struct ldapc_app {
+ const char *name;
+ const char *optstring;
+ const char *usage;
+ const char *pledge;
+ int (*exec)(int, char *[]);
+ struct ldapc ldap;
+ union {
+ struct ldapc_search ls;
+ };
+};
+
 __dead void usage(void);
 int ldapc_connect(struct ldapc *);
-int ldapc_search(struct ldapc *, struct ldapc_search *);
+int ldapc_search(int, char *[]);
 int ldapc_printattr(struct ldapc *, const char *,
     const struct ber_octetstring *);
 void ldapc_disconnect(struct ldapc *);
@@ -90,115 +104,132 @@ int ldapc_parseurl(struct ldapc *, struct ldapc_search *,
 const char *ldapc_resultcode(enum result_code);
 const char *url_decode(char *);
 
-__dead void
-usage(void)
-{
- extern char *__progname;
-
- fprintf(stderr,
-"usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n"
-"    [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n"
-"    [filter] [attributes ...]\n",
-    __progname);
-
- exit(1);
-}
+struct ldapc_app ldapc_apps[] = {
+ {"search", "Lb:s:l:z:", "[-L] [-b basedn] [-s scope] [-l timelimit] "
+    "[-z sizelimit] [filter] [attributes ...]", "stdio", ldapc_search,
+    {0}, {0}}
+};
+struct ldapc_app *ldapc_app = NULL;
 
 int
 main(int argc, char *argv[])
 {
+ char optstr[BUFSIZ];
  char passbuf[LDAPPASSMAX];
  const char *errstr, *url = NULL, *secretfile = NULL;
  struct stat st;
- struct ldapc ldap;
- struct ldapc_search ls;
  int ch;
  int verbose = 1;
+ size_t i;
  FILE *fp;
 
  if (pledge("stdio inet unix tty rpath dns", NULL) == -1)
  err(1, "pledge");
 
- log_init(verbose, 0);
+ if (strlcpy(optstr, GETOPT_COMMON, sizeof(optstr)) >= sizeof(optstr))
+ errx(1, "strlcpy");
 
- memset(&ldap, 0, sizeof(ldap));
- memset(&ls, 0, sizeof(ls));
- ls.ls_scope = -1;
- ldap.ldap_port = -1;
+ log_init(verbose, 0);
 
- /*
- * Check the command.  Currently only "search" is supported but
- * it could be extended with others such as add, modify, or delete.
- */
  if (argc < 2)
  usage();
- else if (strcmp("search", argv[1]) == 0)
- ldap.ldap_req = LDAP_REQ_SEARCH;
- else
+ for (i = 0; i < sizeof(ldapc_apps)/sizeof(*ldapc_apps); i++) {
+ if (strcmp(ldapc_apps[i].name, argv[1]) == 0) {
+ ldapc_app = &ldapc_apps[i];
+ break;
+ }
+ }
+ if (ldapc_app == NULL)
  usage();
  argc--;
  argv++;
 
- while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
+ if (strcmp(ldapc_app->name, "search") == 0)
+ ldapc_app->ls.ls_scope = -1;
+ ldapc_app->ldap.ldap_port = -1;
+
+
+ if (strlcat(optstr, ldapc_app->optstring, sizeof(optstr)) >=
+    sizeof(optstr))
+ errx(1, "strlcat optstr");
+ while ((ch = getopt(argc, argv, optstr)) != -1) {
  switch (ch) {
  case 'b':
- ls.ls_basedn = optarg;
- break;
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ ldapc_app->ls.ls_basedn = optarg;
+ break;
+ }
+ usage();
  case 'c':
- ldap.ldap_capath = optarg;
+ ldapc_app->ldap.ldap_capath = optarg;
  break;
  case 'D':
- ldap.ldap_binddn = optarg;
- ldap.ldap_flags |= F_NEEDAUTH;
+ ldapc_app->ldap.ldap_binddn = optarg;
+ ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
  break;
  case 'H':
  url = optarg;
  break;
  case 'L':
- ldap.ldap_flags |= F_LDIF;
- break;
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ ldapc_app->ldap.ldap_flags |= F_LDIF;
+ break;
+ }
+ usage();
  case 'l':
- ls.ls_timelimit = strtonum(optarg, 0, INT_MAX,
-    &errstr);
- if (errstr != NULL)
- errx(1, "timelimit %s", errstr);
- break;
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ ldapc_app->ls.ls_timelimit = strtonum(optarg, 0,
+    INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "timelimit %s", errstr);
+ break;
+ }
+ usage();
  case 's':
- if (strcasecmp("base", optarg) == 0)
- ls.ls_scope = LDAP_SCOPE_BASE;
- else if (strcasecmp("one", optarg) == 0)
- ls.ls_scope = LDAP_SCOPE_ONELEVEL;
- else if (strcasecmp("sub", optarg) == 0)
- ls.ls_scope = LDAP_SCOPE_SUBTREE;
- else
- errx(1, "invalid scope: %s", optarg);
- break;
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ if (strcasecmp("base", optarg) == 0)
+ ldapc_app->ls.ls_scope =
+    LDAP_SCOPE_BASE;
+ else if (strcasecmp("one", optarg) == 0)
+ ldapc_app->ls.ls_scope =
+    LDAP_SCOPE_ONELEVEL;
+ else if (strcasecmp("sub", optarg) == 0)
+ ldapc_app->ls.ls_scope =
+    LDAP_SCOPE_SUBTREE;
+ else
+ errx(1, "invalid scope: %s", optarg);
+ break;
+ }
+ usage();
  case 'v':
  verbose++;
  break;
  case 'w':
- ldap.ldap_secret = optarg;
- ldap.ldap_flags |= F_NEEDAUTH;
+ ldapc_app->ldap.ldap_secret = optarg;
+ ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
  break;
  case 'W':
- ldap.ldap_flags |= F_NEEDAUTH;
+ ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
  break;
  case 'x':
  /* provided for compatibility */
  break;
  case 'y':
  secretfile = optarg;
- ldap.ldap_flags |= F_NEEDAUTH;
+ ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
  break;
  case 'Z':
- ldap.ldap_flags |= F_STARTTLS;
+ ldapc_app->ldap.ldap_flags |= F_STARTTLS;
  break;
  case 'z':
- ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX,
-    &errstr);
- if (errstr != NULL)
- errx(1, "sizelimit %s", errstr);
- break;
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ ldapc_app->ls.ls_sizelimit = strtonum(optarg, 0,
+    INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "sizelimit %s", errstr);
+ break;
+ }
+ usage();
  default:
  usage();
  }
@@ -208,33 +239,41 @@ main(int argc, char *argv[])
 
  log_setverbose(verbose);
 
- if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1)
- errx(1, "ldapurl");
+ if (url != NULL && strcmp(ldapc_app->name, "search") == 0) {
+ if (ldapc_parseurl(&ldapc_app->ldap, &ldapc_app->ls, url) == -1)
+ errx(1, "ldapurl");
+ } else if (url != NULL && strcmp(ldapc_app->name, "search") != 0) {
+ if (ldapc_parseurl(&ldapc_app->ldap, NULL, url) == -1)
+ errx(1, "ldapurl");
+ }
 
  /* Set the default after parsing URL and/or options */
- if (ldap.ldap_host == NULL)
- ldap.ldap_host = LDAPHOST;
- if (ldap.ldap_port == -1)
- ldap.ldap_port = ldap.ldap_protocol == LDAPS ?
-    LDAPS_PORT : LDAP_PORT;
- if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS))
- ldap.ldap_protocol = LDAPTLS;
- if (ldap.ldap_capath == NULL)
- ldap.ldap_capath = tls_default_ca_cert_file();
- if (ls.ls_basedn == NULL)
- ls.ls_basedn = "";
- if (ls.ls_scope == -1)
- ls.ls_scope = LDAP_SCOPE_SUBTREE;
- if (ls.ls_filter == NULL)
- ls.ls_filter = LDAPFILTER;
-
- if (ldap.ldap_flags & F_NEEDAUTH) {
- if (ldap.ldap_binddn == NULL) {
+ if (ldapc_app->ldap.ldap_host == NULL)
+ ldapc_app->ldap.ldap_host = LDAPHOST;
+ if (ldapc_app->ldap.ldap_port == -1)
+ ldapc_app->ldap.ldap_port = ldapc_app->ldap.ldap_protocol ==
+    LDAPS ? LDAPS_PORT : LDAP_PORT;
+ if (ldapc_app->ldap.ldap_protocol == LDAP &&
+    (ldapc_app->ldap.ldap_flags & F_STARTTLS))
+ ldapc_app->ldap.ldap_protocol = LDAPTLS;
+ if (ldapc_app->ldap.ldap_capath == NULL)
+ ldapc_app->ldap.ldap_capath = tls_default_ca_cert_file();
+ if (strcmp(ldapc_app->name, "search") == 0) {
+ if (ldapc_app->ls.ls_basedn == NULL)
+ ldapc_app->ls.ls_basedn = "";
+ if (ldapc_app->ls.ls_scope == -1)
+ ldapc_app->ls.ls_scope = LDAP_SCOPE_SUBTREE;
+ if (ldapc_app->ls.ls_filter == NULL)
+ ldapc_app->ls.ls_filter = LDAPFILTER;
+ }
+
+ if (ldapc_app->ldap.ldap_flags & F_NEEDAUTH) {
+ if (ldapc_app->ldap.ldap_binddn == NULL) {
  log_warnx("missing -D binddn");
  usage();
  }
  if (secretfile != NULL) {
- if (ldap.ldap_secret != NULL)
+ if (ldapc_app->ldap.ldap_secret != NULL)
  errx(1, "conflicting -w/-y options");
 
  /* read password from stdin or file (first line) */
@@ -252,47 +291,39 @@ main(int argc, char *argv[])
  fclose(fp);
 
  passbuf[strcspn(passbuf, "\n")] = '\0';
- ldap.ldap_secret = passbuf;
+ ldapc_app->ldap.ldap_secret = passbuf;
  }
- if (ldap.ldap_secret == NULL) {
+ if (ldapc_app->ldap.ldap_secret == NULL) {
  if (readpassphrase("Password: ",
     passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL)
  errx(1, "failed to read LDAP password");
- ldap.ldap_secret = passbuf;
+ ldapc_app->ldap.ldap_secret = passbuf;
  }
  }
 
  if (pledge("stdio inet unix rpath dns", NULL) == -1)
  err(1, "pledge");
 
- /* optional search filter */
- if (argc && strchr(argv[0], '=') != NULL) {
- ls.ls_filter = argv[0];
- argc--;
- argv++;
- }
- /* search attributes */
- if (argc)
- ls.ls_attr = argv;
-
- if (ldapc_connect(&ldap) == -1)
+ if (ldapc_connect(&ldapc_app->ldap) == -1)
  errx(1, "LDAP connection failed");
 
- if (pledge("stdio", NULL) == -1)
+ if (pledge(ldapc_app->pledge, NULL) == -1)
  err(1, "pledge");
 
- if (ldapc_search(&ldap, &ls) == -1)
- errx(1, "LDAP search failed");
+ if (ldapc_app->exec(argc, argv) == -1)
+ errx(1, "LDAP %s failed", ldapc_app->name);
 
- ldapc_disconnect(&ldap);
- aldap_free_url(&ldap.ldap_url);
+ ldapc_disconnect(&ldapc_app->ldap);
+ aldap_free_url(&ldapc_app->ldap.ldap_url);
 
  return (0);
 }
 
 int
-ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
+ldapc_search(int argc, char *argv[])
 {
+ struct ldapc *ldap;
+ struct ldapc_search *ls;
  struct aldap_page_control *pg = NULL;
  struct aldap_message *m;
  const char *errstr;
@@ -302,6 +333,19 @@ ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
  int ret, code, fail = 0;
  size_t i;
 
+ ldap = &ldapc_app->ldap;
+ ls = &ldapc_app->ls;
+
+ /* optional search filter */
+ if (argc && strchr(argv[0], '=') != NULL) {
+ ls->ls_filter = argv[0];
+ argc--;
+ argv++;
+ }
+ /* search attributes */
+ if (argc)
+ ls->ls_attr = argv;
+
  if (ldap->ldap_flags & F_LDIF)
  printf("version: 1\n");
  do {
@@ -675,7 +719,7 @@ ldapc_resultcode(enum result_code code)
  default:
  return ("UNKNOWN_ERROR");
  }
-};
+}
 
 int
 ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
@@ -713,6 +757,9 @@ ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
  ldap->ldap_host = lu->host;
  if (lu->port)
  ldap->ldap_port = lu->port;
+ /* Ignore search elements if we're not searching */
+ if (ls == NULL)
+ return (0);
 
  /* The distinguished name has to be URL-encoded */
  if (lu->dn != NULL && ls->ls_basedn != NULL &&
@@ -791,3 +838,29 @@ url_decode(char *url)
 
  return (url);
 }
+
+__dead void
+usage(void)
+{
+ size_t i;
+ extern char *__progname;
+
+ if (ldapc_app != NULL) {
+ fprintf(stderr, "usage: %s %s %s %s %s\n",
+    __progname, ldapc_app->name,
+    "[-vWxZ] [-c CAfile] [-D binddn] [-H host] [-w secret]",
+    "[-y secretfile]",
+    ldapc_app->usage == NULL ? "" : ldapc_app->usage);
+ exit(1);
+ }
+ fprintf(stderr, "usage:\n");
+ for (i = 0; i < (sizeof(ldapc_apps)/sizeof(*ldapc_apps)); i++) {
+ fprintf(stderr, "%*s %s %s %s %s\n",
+    (int) (sizeof("usage:") + strlen(__progname)), __progname,
+    ldapc_apps[i].name,
+    "[-vWxZ] [-c CAfile] [-D binddn] [-H host] [-w secret]",
+    "[-y secretfile]",
+    ldapc_apps[i].usage == NULL ? "" : ldapc_apps[i].usage);
+ }
+ exit(1);
+}

Reply | Threaded
Open this post in threaded view
|

Re: ldap(1) Add delete support [1/1]

Martijn van Duren-5
Part 2: adding the actual delete command.

This still misses the documentation, I'll send that later.

diff --git a/aldap.c b/aldap.c
index 39ced8f..a460f9a 100644
--- a/aldap.c
+++ b/aldap.c
@@ -247,6 +247,34 @@ fail:
  return (-1);
 }
 
+int
+aldap_delete(struct aldap *ldap, const char *dn)
+{
+ struct ber_element *root = NULL, *elm;
+
+ if ((root = ber_add_sequence(NULL)) == NULL)
+ goto fail;
+
+ elm = ber_printf_elements(root, "dst", ++ldap->msgid, dn, BER_CLASS_APP,
+    LDAP_REQ_DELETE_30);
+
+ if (elm == NULL)
+ goto fail;
+
+ LDAP_DEBUG("aldap_delete", root);
+
+ if (aldap_send(ldap, root) == -1) {
+ root = NULL;
+ goto fail;
+ }
+ return (ldap->msgid);
+fail:
+ ber_free_elements(root);
+
+ ldap->err = ALDAP_ERR_OPERATION_FAILED;
+ return (-1);
+}
+
 int
 aldap_search(struct aldap *ldap, char *basedn, enum scope scope, char *filter,
     char **attrs, int typesonly, int sizelimit, int timelimit,
@@ -449,7 +477,7 @@ parsefail:
 }
 
 struct aldap_page_control *
-aldap_parse_page_control(struct ber_element *control, size_t len)
+aldap_parse_page_control(struct ber_element *control, size_t len)
 {
  char *oid, *s;
  char *encoded;
diff --git a/aldap.h b/aldap.h
index 81fe770..1ac86ec 100644
--- a/aldap.h
+++ b/aldap.h
@@ -225,6 +225,7 @@ int aldap_req_starttls(struct aldap *);
 int aldap_bind(struct aldap *, char *, char *);
 int aldap_unbind(struct aldap *);
 int aldap_search(struct aldap *, char *, enum scope, char *, char **, int, int, int, struct aldap_page_control *);
+int aldap_delete(struct aldap *, const char *);
 int aldap_get_errno(struct aldap *, const char **);
 
 int aldap_get_resultcode(struct aldap_message *);
diff --git a/ldapclient.c b/ldapclient.c
index 9f29997..503bcd1 100644
--- a/ldapclient.c
+++ b/ldapclient.c
@@ -81,6 +81,12 @@ struct ldapc_search {
  char **ls_attr;
 };
 
+struct ldapc_delete {
+ char *ld_file;
+ int ld_recursive;
+ int ld_sizelimit;
+};
+
 struct ldapc_app {
  const char *name;
  const char *optstring;
@@ -90,12 +96,16 @@ struct ldapc_app {
  struct ldapc ldap;
  union {
  struct ldapc_search ls;
+ struct ldapc_delete ld;
  };
 };
 
 __dead void usage(void);
 int ldapc_connect(struct ldapc *);
 int ldapc_search(int, char *[]);
+int ldapc_delete(int, char *[]);
+int ldapc_do_delete(char *);
+int ldapc_children(char *, char **[]);
 int ldapc_printattr(struct ldapc *, const char *,
     const struct ber_octetstring *);
 void ldapc_disconnect(struct ldapc *);
@@ -107,7 +117,9 @@ const char *url_decode(char *);
 struct ldapc_app ldapc_apps[] = {
  {"search", "Lb:s:l:z:", "[-L] [-b basedn] [-s scope] [-l timelimit] "
     "[-z sizelimit] [filter] [attributes ...]", "stdio", ldapc_search,
-    {0}, {0}}
+    {0}, {0}},
+ {"delete", "f:rz:", "[-r] [-f file] [-z sizelimit]", "rpath stdio",
+    ldapc_delete, {0}, {0}}
 };
 struct ldapc_app *ldapc_app = NULL;
 
@@ -167,6 +179,12 @@ main(int argc, char *argv[])
  ldapc_app->ldap.ldap_binddn = optarg;
  ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
  break;
+ case 'f':
+ if (strcmp(ldapc_app->name, "delete") == 0) {
+ ldapc_app->ld.ld_file = optarg;
+ break;
+ }
+ usage();
  case 'H':
  url = optarg;
  break;
@@ -185,6 +203,12 @@ main(int argc, char *argv[])
  break;
  }
  usage();
+ case 'r':
+ if (strcmp(ldapc_app->name, "delete") == 0) {
+ ldapc_app->ld.ld_recursive = 1;
+ break;
+ }
+ usage();
  case 's':
  if (strcmp(ldapc_app->name, "search") == 0) {
  if (strcasecmp("base", optarg) == 0)
@@ -228,6 +252,12 @@ main(int argc, char *argv[])
  if (errstr != NULL)
  errx(1, "sizelimit %s", errstr);
  break;
+ } else if (strcmp(ldapc_app->name, "delete") == 0) {
+ ldapc_app->ld.ld_sizelimit = strtonum(optarg, 0,
+    INT_MAX, &errstr);
+ if (errstr != NULL)
+ errx(1, "sizelimit %s", errstr);
+ break;
  }
  usage();
  default:
@@ -319,6 +349,190 @@ main(int argc, char *argv[])
  return (0);
 }
 
+int
+ldapc_delete(int argc, char *argv[])
+{
+ FILE *file;
+ struct ldapc *ldap;
+ struct ldapc_delete *ld;
+ int i;
+ ssize_t dnlen;
+ size_t dnsize = 0;
+ char *dn = NULL;
+
+ ldap = &ldapc_app->ldap;
+ ld = &ldapc_app->ld;
+
+ if (ld->ld_file != NULL) {
+ if ((file = fopen(ld->ld_file, "r")) == NULL) {
+ log_warn("Failed to open %s", ld->ld_file);
+ goto fail;
+ }
+ if (pledge("stdio", NULL) == -1)
+ err(1, "pledge");
+
+ while ((dnlen = getline(&dn, &dnsize, file)) != -1) {
+ if (dn[dnlen - 1] == '\n')
+ dn[dnlen - 1] = '\0';
+ if (ldapc_do_delete(dn) == -1)
+ goto fail;
+ }
+ if (ferror(file)) {
+ log_warn("Error reading file %s", ld->ld_file);
+ goto fail;
+ }
+ fclose(file);
+ }
+ for (i = 0; i < argc; i++) {
+ if (ldapc_do_delete(argv[i]) == -1)
+ goto fail;
+ }
+
+ return (0);
+fail:
+ ldapc_disconnect(ldap);
+
+ return (-1);
+}
+
+int
+ldapc_do_delete(char *dn)
+{
+ struct ldapc *ldap;
+ struct ldapc_delete *ld;
+ struct aldap_message *m;
+ const char *errstr;
+ char **children = NULL;
+ int nchildren = 0;
+ int code;
+
+ ldap = &ldapc_app->ldap;
+ ld = &ldapc_app->ld;
+
+ if (aldap_delete(ldap->ldap_al, dn) == -1) {
+ aldap_get_errno(ldap->ldap_al, &errstr);
+ log_warnx("LDAP delete failed: %s", errstr);
+ return (-1);
+ }
+ if ((m = aldap_parse(ldap->ldap_al)) == NULL)
+ return (-1);
+
+ if (ldap->ldap_al->msgid != m->msgid) {
+ log_warnx("LDAP delete unexpected message id: %d (expected %d)",
+    m->msgid, ldap->ldap_al->msgid);
+ goto fail;
+ }
+ if (m->message_type != LDAP_RES_DELETE) {
+ log_warnx("LDAP delete unexpected message "
+    "type: %d (expected %d)", m->message_type,
+    LDAP_RES_DELETE);
+ goto fail;
+ }
+ code = aldap_get_resultcode(m);
+ if (code == LDAP_NOT_ALLOWED_ON_NONLEAF && ld->ld_recursive) {
+ log_debug("deleting children of %s", dn);
+ nchildren = ldapc_children(dn, &children);
+ if (nchildren == 0)
+ log_warnx("Unexpected empty set of children for %s",
+    dn);
+ if (nchildren < 1)
+ goto fail;
+ while (nchildren > 0) {
+ if (ldapc_do_delete(children[nchildren - 1]) == -1)
+ goto fail;
+ free(children[--nchildren]);
+ }
+ free(children);
+ children = NULL;
+ return ldapc_do_delete(dn);
+ }
+ if (code != LDAP_SUCCESS) {
+ log_warnx("LDAP delete failed: %s(%d)",
+    ldapc_resultcode(code), code);
+ goto fail;
+ }
+ log_debug("deleted entry %s", dn);
+ aldap_freemsg(m);
+ return (0);
+fail:
+ while (nchildren > 0)
+ free(children[--nchildren]);
+ free(children);
+ aldap_freemsg(m);
+ return (-1);
+}
+
+int
+ldapc_children(char *dn, char *(*children[]))
+{
+ struct ldapc *ldap;
+ struct ldapc_delete *ld;
+ struct aldap_page_control *pg = NULL;
+ struct aldap_message *m;
+ char *noattr[] = {"1.1", NULL};
+ const char *errstr;
+ char **tchildren;
+ int nchildren = 0;
+ int childrenlen = 0;
+ int code;
+
+ ldap = &ldapc_app->ldap;
+ ld = &ldapc_app->ld;
+ *children = NULL;
+
+ do {
+ if (aldap_search(ldap->ldap_al, dn, LDAP_SCOPE_ONELEVEL,
+    "(objectClass=*)", noattr, 0, ld->ld_sizelimit, 0,
+    pg) == -1) {
+ aldap_get_errno(ldap->ldap_al, &errstr);
+ log_warnx("LDAP delete failed: %s", errstr);
+ return (-1);
+ }
+ if (pg != NULL) {
+ aldap_freepage(pg);
+ pg = NULL;
+ }
+
+ while ((m = aldap_parse(ldap->ldap_al)) != NULL) {
+ if (ldap->ldap_al->msgid != m->msgid)
+ goto fail;
+ if ((code = aldap_get_resultcode(m)) != LDAP_SUCCESS) {
+ log_warnx("LDAP delete failed: %s(%d)",
+    ldapc_resultcode(code), code);
+ goto fail;
+ }
+ if (m->message_type == LDAP_RES_SEARCH_RESULT) {
+ if (m->page != NULL && m->page->cookie_len != 0)
+ pg = m->page;
+ aldap_freemsg(m);
+ break;
+ }
+ if (m->message_type != LDAP_RES_SEARCH_ENTRY)
+ goto fail;
+ if (childrenlen <= nchildren) {
+ childrenlen += 50;
+ tchildren = realloc(*children, childrenlen);
+ if (tchildren == NULL)
+ goto fail;
+ *children = tchildren;
+ }
+ (*children)[nchildren] = strdup(aldap_get_dn(m));
+ if ((*children)[nchildren] == NULL)
+ goto fail;
+ nchildren++;
+ aldap_freemsg(m);
+ }
+ } while (pg != NULL);
+
+ return nchildren;
+fail:
+ aldap_freemsg(m);
+ while (nchildren > 0)
+ free((*children)[--nchildren]);
+ free(*children);
+ return (-1);
+}
+
 int
 ldapc_search(int argc, char *argv[])
 {

Reply | Threaded
Open this post in threaded view
|

Re: ldap(1) Add delete support [0/1]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
Here's the accompanying documentation diff for the restructure.

diff --git a/ldap.1 b/ldap.1
index bb65ea4..0225f78 100644
--- a/ldap.1
+++ b/ldap.1
@@ -40,30 +40,10 @@ The
 .Nm
 utility is a simple LDAP client.
 It queries an LDAP server to perform a command and outputs the results
-in the LDAP Data Interchange Format (LDIF).
-.Bl -tag -width Ds
-.It Cm search Ar options Oo Ar filter Oc Op Ar attributes ...
-Perform a directory search request.
-The optional
-.Ar filter
-argument specifies the LDAP filter for the directory search.
-The default is
-.Ar (objectClass=*)
-and the format must comply to the
-.Dq String Representation of Search Filters
-as described in RFC 4515.
-If one or more
-.Ar attribute
-options are specified,
-.Nm
-restricts the output to the specified attributes.
-.El
+if applicable.
 .Pp
-The options are as follows:
+All commands support the following options:
 .Bl -tag -width Ds
-.It Fl b Ar basedn
-Use the specified distinguished name (dn) as the starting point for
-directory search requests.
 .It Fl c Ar CAfile
 When TLS is enabled, load the CA bundle for certificate verification
 from the specified file.
@@ -95,7 +75,8 @@ Each of
 .Ar basedn , attribute , scope
 and
 .Ar filter
-may be omitted,
+may be omitted and are ignored for any other command besides
+.Cm search,
 but the preceding
 .Sq /
 or
@@ -123,6 +104,52 @@ The host argument is required to be a URL-encoded path, for example
 for
 .Pa /var/run/ldapi .
 .El
+.It Fl v
+Product more verbose output.
+.It Fl W
+Prompt for the bind secret with echo turned off.
+.It Fl w Ar secret
+Specify the bind secret on the command line.
+.It Fl x
+Use simple authentication.
+This is the default as
+.Nm
+does not support SASL authentication.
+.It Fl y Ar secretfile
+Read the bind secret from the first line of the specified file or from
+standard input if the
+.Ar secretfile
+argument is
+.Sq - .
+The file must not be world-readable if it is a regular file.
+.It Fl Z
+Enable TLS using the StartTLS operation.
+.El
+.Ss SEARCH
+The
+.Cm search
+command performs a directory search request.
+The optional
+.Ar filter
+argument specifies the LDAP filter for the directory search.
+The default is
+.Ar (objectClass=*)
+and the format must comply to the
+.Dq String Representation of Search Filters
+as described in RFC 4515.
+If one or more
+.Ar attribute
+options are specified,
+.Nm
+restricts the output to the specified attributes.
+.Pp
+The
+.Cm search
+specific options are as follows:
+.Bl -tag -width Ds
+.It Fl b Ar basedn
+Use the specified distinguished name (dn) as the starting point for
+directory search requests.
 .It Fl L
 Output the directory search result in a standards-compliant version of
 the LDAP Data Interchange Format (LDIF).
@@ -152,26 +179,6 @@ or
 The default is
 .Ic sub
 for subtree searches.
-.It Fl v
-Product more verbose output.
-.It Fl W
-Prompt for the bind secret with echo turned off.
-.It Fl w Ar secret
-Specify the bind secret on the command line.
-.It Fl x
-Use simple authentication.
-This is the default as
-.Nm
-does not support SASL authentication.
-.It Fl y Ar secretfile
-Read the bind secret from the first line of the specified file or from
-standard input if the
-.Ar secretfile
-argument is
-.Sq - .
-The file must not be world-readable if it is a regular file.
-.It Fl Z
-Enable TLS using the StartTLS operation.
 .It Fl z Ar sizelimit
 Request the server to limit the search result to a maximum number of
 .Ar sizelimit

Reply | Threaded
Open this post in threaded view
|

Re: ldap(1) Add delete support [1/1]

Martijn van Duren-5
In reply to this post by Martijn van Duren-5
And the man support for delete, applies on top of the restructure diff.

diff --git a/ldap.1 b/ldap.1
index 0225f78..296d282 100644
--- a/ldap.1
+++ b/ldap.1
@@ -35,6 +35,17 @@
 .Op Fl z Ar sizelimit
 .Op Ar filter
 .Op Ar attributes ...
+.Nm
+.Cm delete
+.Op Fl rvWxZ
+.Op Fl c Ar CAfile
+.Op Fl D Ar binddn
+.Op Fl f Ar file
+.Op Fl H Ar host
+.Op Fl w Ar secret
+.Op Fl y Ar secretfile
+.Op Fl z Ar sizelimit
+.Op Ar DN ...
 .Sh DESCRIPTION
 The
 .Nm
@@ -186,6 +197,37 @@ entries.
 The default value is 0.
 for no limit.
 .El
+.Ss DELETE
+The
+.Cm delete
+command performs a directory delete request.
+The
+.Ar DN
+arguments are used as a list of DNs to remove from the server.
+If a delete request fails all following delete requests are canceled.
+The
+.Cm delete
+specific options are as follows:
+.Bl -tag -width Ds
+.It Fl f Ar file
+Get a list of DNs from
+.Ar file .
+.Ar File
+is parsed before the
+.Ar DN
+list.
+.It Fl r
+Do a recursive delete.
+.It Fl z Ar sizelimit
+Request the server to limit the search result to a maximum number of
+.Ar sizelimit
+entries.
+The default value is 0.
+for no limit.
+This option is only used in combination with the
+.Fl r
+flag.
+.El
 .Sh FILES
 .Bl -tag -width "/etc/ssl/cert.pemXXX" -compact
 .It Pa /etc/ssl/cert.pem

Reply | Threaded
Open this post in threaded view
|

Re: ldap(1) Add delete support [0/1]

Claudio Jeker
In reply to this post by Martijn van Duren-5
On Thu, Feb 14, 2019 at 08:32:11AM +0100, Martijn van Duren wrote:
> I would like to see some more functionality in ldap(1), so I started of
> with delete, because that's seems to be the easiest/shortest to
> implement and the diffs are big enough as is.
>
> I split it up in 2 diffs. This is the first one, which restructures
> ldap(1) to make use of a per command environment.
>
> Thoughts? OK?

I'm not thrilled by the way you do the getopt handling.
Having dynamic getopt strings is intransparent. It will also fail the
moment a flag is reused with a different meaning.
I would prefer a more straight forward solution with one single getopt
option string and conflict resolution during or after option parsing.
 
Also I would use some kind of enum or define for the mode instead of using
strcmp to know the mode.

> martijn@
>
> diff --git a/ldapclient.c b/ldapclient.c
> index 9763b8e..9f29997 100644
> --- a/ldapclient.c
> +++ b/ldapclient.c
> @@ -52,6 +52,8 @@
>  #define F_NEEDAUTH 0x04
>  #define F_LDIF 0x08
>  
> +#define GETOPT_COMMON "c:D:H:vWw:xy:Z"
> +
>  #define LDAPHOST "localhost"
>  #define LDAPFILTER "(objectClass=*)"
>  #define LDIF_LINELENGTH 79
> @@ -79,9 +81,21 @@ struct ldapc_search {
>   char **ls_attr;
>  };
>  
> +struct ldapc_app {
> + const char *name;
> + const char *optstring;
> + const char *usage;
> + const char *pledge;
> + int (*exec)(int, char *[]);
> + struct ldapc ldap;
> + union {
> + struct ldapc_search ls;
> + };
> +};
> +
>  __dead void usage(void);
>  int ldapc_connect(struct ldapc *);
> -int ldapc_search(struct ldapc *, struct ldapc_search *);
> +int ldapc_search(int, char *[]);
>  int ldapc_printattr(struct ldapc *, const char *,
>      const struct ber_octetstring *);
>  void ldapc_disconnect(struct ldapc *);
> @@ -90,115 +104,132 @@ int ldapc_parseurl(struct ldapc *, struct ldapc_search *,
>  const char *ldapc_resultcode(enum result_code);
>  const char *url_decode(char *);
>  
> -__dead void
> -usage(void)
> -{
> - extern char *__progname;
> -
> - fprintf(stderr,
> -"usage: %s search [-LvWxZ] [-b basedn] [-c CAfile] [-D binddn] [-H host]\n"
> -"    [-l timelimit] [-s scope] [-w secret] [-y secretfile] [-z sizelimit]\n"
> -"    [filter] [attributes ...]\n",
> -    __progname);
> -
> - exit(1);
> -}
> +struct ldapc_app ldapc_apps[] = {
> + {"search", "Lb:s:l:z:", "[-L] [-b basedn] [-s scope] [-l timelimit] "
> +    "[-z sizelimit] [filter] [attributes ...]", "stdio", ldapc_search,
> +    {0}, {0}}
> +};
> +struct ldapc_app *ldapc_app = NULL;
>  
>  int
>  main(int argc, char *argv[])
>  {
> + char optstr[BUFSIZ];
>   char passbuf[LDAPPASSMAX];
>   const char *errstr, *url = NULL, *secretfile = NULL;
>   struct stat st;
> - struct ldapc ldap;
> - struct ldapc_search ls;
>   int ch;
>   int verbose = 1;
> + size_t i;
>   FILE *fp;
>  
>   if (pledge("stdio inet unix tty rpath dns", NULL) == -1)
>   err(1, "pledge");
>  
> - log_init(verbose, 0);
> + if (strlcpy(optstr, GETOPT_COMMON, sizeof(optstr)) >= sizeof(optstr))
> + errx(1, "strlcpy");
>  
> - memset(&ldap, 0, sizeof(ldap));
> - memset(&ls, 0, sizeof(ls));
> - ls.ls_scope = -1;
> - ldap.ldap_port = -1;
> + log_init(verbose, 0);
>  
> - /*
> - * Check the command.  Currently only "search" is supported but
> - * it could be extended with others such as add, modify, or delete.
> - */
>   if (argc < 2)
>   usage();
> - else if (strcmp("search", argv[1]) == 0)
> - ldap.ldap_req = LDAP_REQ_SEARCH;
> - else
> + for (i = 0; i < sizeof(ldapc_apps)/sizeof(*ldapc_apps); i++) {
> + if (strcmp(ldapc_apps[i].name, argv[1]) == 0) {
> + ldapc_app = &ldapc_apps[i];
> + break;
> + }
> + }
> + if (ldapc_app == NULL)
>   usage();
>   argc--;
>   argv++;
>  
> - while ((ch = getopt(argc, argv, "b:c:D:H:Ll:s:vWw:xy:Zz:")) != -1) {
> + if (strcmp(ldapc_app->name, "search") == 0)
> + ldapc_app->ls.ls_scope = -1;
> + ldapc_app->ldap.ldap_port = -1;
> +
> +
> + if (strlcat(optstr, ldapc_app->optstring, sizeof(optstr)) >=
> +    sizeof(optstr))
> + errx(1, "strlcat optstr");
> + while ((ch = getopt(argc, argv, optstr)) != -1) {
>   switch (ch) {
>   case 'b':
> - ls.ls_basedn = optarg;
> - break;
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + ldapc_app->ls.ls_basedn = optarg;
> + break;
> + }
> + usage();
>   case 'c':
> - ldap.ldap_capath = optarg;
> + ldapc_app->ldap.ldap_capath = optarg;
>   break;
>   case 'D':
> - ldap.ldap_binddn = optarg;
> - ldap.ldap_flags |= F_NEEDAUTH;
> + ldapc_app->ldap.ldap_binddn = optarg;
> + ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
>   break;
>   case 'H':
>   url = optarg;
>   break;
>   case 'L':
> - ldap.ldap_flags |= F_LDIF;
> - break;
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + ldapc_app->ldap.ldap_flags |= F_LDIF;
> + break;
> + }
> + usage();
>   case 'l':
> - ls.ls_timelimit = strtonum(optarg, 0, INT_MAX,
> -    &errstr);
> - if (errstr != NULL)
> - errx(1, "timelimit %s", errstr);
> - break;
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + ldapc_app->ls.ls_timelimit = strtonum(optarg, 0,
> +    INT_MAX, &errstr);
> + if (errstr != NULL)
> + errx(1, "timelimit %s", errstr);
> + break;
> + }
> + usage();
>   case 's':
> - if (strcasecmp("base", optarg) == 0)
> - ls.ls_scope = LDAP_SCOPE_BASE;
> - else if (strcasecmp("one", optarg) == 0)
> - ls.ls_scope = LDAP_SCOPE_ONELEVEL;
> - else if (strcasecmp("sub", optarg) == 0)
> - ls.ls_scope = LDAP_SCOPE_SUBTREE;
> - else
> - errx(1, "invalid scope: %s", optarg);
> - break;
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + if (strcasecmp("base", optarg) == 0)
> + ldapc_app->ls.ls_scope =
> +    LDAP_SCOPE_BASE;
> + else if (strcasecmp("one", optarg) == 0)
> + ldapc_app->ls.ls_scope =
> +    LDAP_SCOPE_ONELEVEL;
> + else if (strcasecmp("sub", optarg) == 0)
> + ldapc_app->ls.ls_scope =
> +    LDAP_SCOPE_SUBTREE;
> + else
> + errx(1, "invalid scope: %s", optarg);
> + break;
> + }
> + usage();
>   case 'v':
>   verbose++;
>   break;
>   case 'w':
> - ldap.ldap_secret = optarg;
> - ldap.ldap_flags |= F_NEEDAUTH;
> + ldapc_app->ldap.ldap_secret = optarg;
> + ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
>   break;
>   case 'W':
> - ldap.ldap_flags |= F_NEEDAUTH;
> + ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
>   break;
>   case 'x':
>   /* provided for compatibility */
>   break;
>   case 'y':
>   secretfile = optarg;
> - ldap.ldap_flags |= F_NEEDAUTH;
> + ldapc_app->ldap.ldap_flags |= F_NEEDAUTH;
>   break;
>   case 'Z':
> - ldap.ldap_flags |= F_STARTTLS;
> + ldapc_app->ldap.ldap_flags |= F_STARTTLS;
>   break;
>   case 'z':
> - ls.ls_sizelimit = strtonum(optarg, 0, INT_MAX,
> -    &errstr);
> - if (errstr != NULL)
> - errx(1, "sizelimit %s", errstr);
> - break;
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + ldapc_app->ls.ls_sizelimit = strtonum(optarg, 0,
> +    INT_MAX, &errstr);
> + if (errstr != NULL)
> + errx(1, "sizelimit %s", errstr);
> + break;
> + }
> + usage();
>   default:
>   usage();
>   }
> @@ -208,33 +239,41 @@ main(int argc, char *argv[])
>  
>   log_setverbose(verbose);
>  
> - if (url != NULL && ldapc_parseurl(&ldap, &ls, url) == -1)
> - errx(1, "ldapurl");
> + if (url != NULL && strcmp(ldapc_app->name, "search") == 0) {
> + if (ldapc_parseurl(&ldapc_app->ldap, &ldapc_app->ls, url) == -1)
> + errx(1, "ldapurl");
> + } else if (url != NULL && strcmp(ldapc_app->name, "search") != 0) {
> + if (ldapc_parseurl(&ldapc_app->ldap, NULL, url) == -1)
> + errx(1, "ldapurl");
> + }
>  
>   /* Set the default after parsing URL and/or options */
> - if (ldap.ldap_host == NULL)
> - ldap.ldap_host = LDAPHOST;
> - if (ldap.ldap_port == -1)
> - ldap.ldap_port = ldap.ldap_protocol == LDAPS ?
> -    LDAPS_PORT : LDAP_PORT;
> - if (ldap.ldap_protocol == LDAP && (ldap.ldap_flags & F_STARTTLS))
> - ldap.ldap_protocol = LDAPTLS;
> - if (ldap.ldap_capath == NULL)
> - ldap.ldap_capath = tls_default_ca_cert_file();
> - if (ls.ls_basedn == NULL)
> - ls.ls_basedn = "";
> - if (ls.ls_scope == -1)
> - ls.ls_scope = LDAP_SCOPE_SUBTREE;
> - if (ls.ls_filter == NULL)
> - ls.ls_filter = LDAPFILTER;
> -
> - if (ldap.ldap_flags & F_NEEDAUTH) {
> - if (ldap.ldap_binddn == NULL) {
> + if (ldapc_app->ldap.ldap_host == NULL)
> + ldapc_app->ldap.ldap_host = LDAPHOST;
> + if (ldapc_app->ldap.ldap_port == -1)
> + ldapc_app->ldap.ldap_port = ldapc_app->ldap.ldap_protocol ==
> +    LDAPS ? LDAPS_PORT : LDAP_PORT;
> + if (ldapc_app->ldap.ldap_protocol == LDAP &&
> +    (ldapc_app->ldap.ldap_flags & F_STARTTLS))
> + ldapc_app->ldap.ldap_protocol = LDAPTLS;
> + if (ldapc_app->ldap.ldap_capath == NULL)
> + ldapc_app->ldap.ldap_capath = tls_default_ca_cert_file();
> + if (strcmp(ldapc_app->name, "search") == 0) {
> + if (ldapc_app->ls.ls_basedn == NULL)
> + ldapc_app->ls.ls_basedn = "";
> + if (ldapc_app->ls.ls_scope == -1)
> + ldapc_app->ls.ls_scope = LDAP_SCOPE_SUBTREE;
> + if (ldapc_app->ls.ls_filter == NULL)
> + ldapc_app->ls.ls_filter = LDAPFILTER;
> + }
> +
> + if (ldapc_app->ldap.ldap_flags & F_NEEDAUTH) {
> + if (ldapc_app->ldap.ldap_binddn == NULL) {
>   log_warnx("missing -D binddn");
>   usage();
>   }
>   if (secretfile != NULL) {
> - if (ldap.ldap_secret != NULL)
> + if (ldapc_app->ldap.ldap_secret != NULL)
>   errx(1, "conflicting -w/-y options");
>  
>   /* read password from stdin or file (first line) */
> @@ -252,47 +291,39 @@ main(int argc, char *argv[])
>   fclose(fp);
>  
>   passbuf[strcspn(passbuf, "\n")] = '\0';
> - ldap.ldap_secret = passbuf;
> + ldapc_app->ldap.ldap_secret = passbuf;
>   }
> - if (ldap.ldap_secret == NULL) {
> + if (ldapc_app->ldap.ldap_secret == NULL) {
>   if (readpassphrase("Password: ",
>      passbuf, sizeof(passbuf), RPP_REQUIRE_TTY) == NULL)
>   errx(1, "failed to read LDAP password");
> - ldap.ldap_secret = passbuf;
> + ldapc_app->ldap.ldap_secret = passbuf;
>   }
>   }
>  
>   if (pledge("stdio inet unix rpath dns", NULL) == -1)
>   err(1, "pledge");
>  
> - /* optional search filter */
> - if (argc && strchr(argv[0], '=') != NULL) {
> - ls.ls_filter = argv[0];
> - argc--;
> - argv++;
> - }
> - /* search attributes */
> - if (argc)
> - ls.ls_attr = argv;
> -
> - if (ldapc_connect(&ldap) == -1)
> + if (ldapc_connect(&ldapc_app->ldap) == -1)
>   errx(1, "LDAP connection failed");
>  
> - if (pledge("stdio", NULL) == -1)
> + if (pledge(ldapc_app->pledge, NULL) == -1)
>   err(1, "pledge");
>  
> - if (ldapc_search(&ldap, &ls) == -1)
> - errx(1, "LDAP search failed");
> + if (ldapc_app->exec(argc, argv) == -1)
> + errx(1, "LDAP %s failed", ldapc_app->name);
>  
> - ldapc_disconnect(&ldap);
> - aldap_free_url(&ldap.ldap_url);
> + ldapc_disconnect(&ldapc_app->ldap);
> + aldap_free_url(&ldapc_app->ldap.ldap_url);
>  
>   return (0);
>  }
>  
>  int
> -ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
> +ldapc_search(int argc, char *argv[])
>  {
> + struct ldapc *ldap;
> + struct ldapc_search *ls;
>   struct aldap_page_control *pg = NULL;
>   struct aldap_message *m;
>   const char *errstr;
> @@ -302,6 +333,19 @@ ldapc_search(struct ldapc *ldap, struct ldapc_search *ls)
>   int ret, code, fail = 0;
>   size_t i;
>  
> + ldap = &ldapc_app->ldap;
> + ls = &ldapc_app->ls;
> +
> + /* optional search filter */
> + if (argc && strchr(argv[0], '=') != NULL) {
> + ls->ls_filter = argv[0];
> + argc--;
> + argv++;
> + }
> + /* search attributes */
> + if (argc)
> + ls->ls_attr = argv;
> +
>   if (ldap->ldap_flags & F_LDIF)
>   printf("version: 1\n");
>   do {
> @@ -675,7 +719,7 @@ ldapc_resultcode(enum result_code code)
>   default:
>   return ("UNKNOWN_ERROR");
>   }
> -};
> +}
>  
>  int
>  ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
> @@ -713,6 +757,9 @@ ldapc_parseurl(struct ldapc *ldap, struct ldapc_search *ls, const char *url)
>   ldap->ldap_host = lu->host;
>   if (lu->port)
>   ldap->ldap_port = lu->port;
> + /* Ignore search elements if we're not searching */
> + if (ls == NULL)
> + return (0);
>  
>   /* The distinguished name has to be URL-encoded */
>   if (lu->dn != NULL && ls->ls_basedn != NULL &&
> @@ -791,3 +838,29 @@ url_decode(char *url)
>  
>   return (url);
>  }
> +
> +__dead void
> +usage(void)
> +{
> + size_t i;
> + extern char *__progname;
> +
> + if (ldapc_app != NULL) {
> + fprintf(stderr, "usage: %s %s %s %s %s\n",
> +    __progname, ldapc_app->name,
> +    "[-vWxZ] [-c CAfile] [-D binddn] [-H host] [-w secret]",
> +    "[-y secretfile]",
> +    ldapc_app->usage == NULL ? "" : ldapc_app->usage);
> + exit(1);
> + }
> + fprintf(stderr, "usage:\n");
> + for (i = 0; i < (sizeof(ldapc_apps)/sizeof(*ldapc_apps)); i++) {
> + fprintf(stderr, "%*s %s %s %s %s\n",
> +    (int) (sizeof("usage:") + strlen(__progname)), __progname,
> +    ldapc_apps[i].name,
> +    "[-vWxZ] [-c CAfile] [-D binddn] [-H host] [-w secret]",
> +    "[-y secretfile]",
> +    ldapc_apps[i].usage == NULL ? "" : ldapc_apps[i].usage);
> + }
> + exit(1);
> +}
>

--
:wq Claudio