diff options
author | Sergey Poznyakoff <gray@gnu.org> | 2014-08-24 17:34:16 +0300 |
---|---|---|
committer | Sergey Poznyakoff <gray@gnu.org> | 2014-08-24 17:34:16 +0300 |
commit | 124b8aa2497703558fcebe5b10b675e1d426759d (patch) | |
tree | da644de0ac17c7e53e7df5e666765606f7a4026d | |
parent | 1cb788cfec3dca18a2a06f30fb1e38826a51e89d (diff) | |
download | smap-124b8aa2497703558fcebe5b10b675e1d426759d.tar.gz smap-124b8aa2497703558fcebe5b10b675e1d426759d.tar.bz2 |
New module: ldap
* configure.ac: Detect LDAP. New option --with-ldap
* include/smap/parseopt.h (smap_option)<func>: Change signature.
(SMAP_DELIM_EQ,SMAP_DELIM_WS,SMAP_DELIM_MASK)
(SMAP_PARSE_SUCCESS,SMAP_PARSE_NOENT)
(SMAP_PARSE_INVAL): New defines.
(smap_parseline): New proto.
* include/smap/wordsplit.h (wordsplit)<ws_getvar>: Remove const
from the return value: the function should allocate memory.
(wordsplit_varnames): New proto.
* lib/wordsplit.c (ISVARBEG,ISVARCHR): New macros.
(wordsplit_varnames): New function
(expvar): ws_getvar allocates memory.
* lib/parseopt.c (find_opt): Take flags as additional argument.
Support case-insensitive comparison and whitespace delimiters.
(smap_parseline): New function.
(smap_parseopt): Rewrite using smap_parseline.
* modules/Makefile.am: Add ldap module.
* modules/ldap/Makefile.am: New file.
* modules/ldap/ldap.c: New file.
* modules/mysql/Makefile.am: Minor change.
* modules/mysql/mysql.c: Minor change.
* src/srvman.c (smap_server_new): Save url.
-rw-r--r-- | configure.ac | 21 | ||||
-rw-r--r-- | include/smap/parseopt.h | 15 | ||||
-rw-r--r-- | include/smap/wordsplit.h | 4 | ||||
-rw-r--r-- | lib/parseopt.c | 191 | ||||
-rw-r--r-- | lib/wordsplit.c | 97 | ||||
-rw-r--r-- | modules/Makefile.am | 13 | ||||
-rw-r--r-- | modules/ldap/Makefile.am | 23 | ||||
-rw-r--r-- | modules/ldap/ldap.c | 974 | ||||
-rw-r--r-- | modules/mysql/Makefile.am | 1 | ||||
-rw-r--r-- | modules/mysql/mysql.c | 2 | ||||
-rw-r--r-- | src/srvman.c | 1 |
11 files changed, 1249 insertions, 93 deletions
diff --git a/configure.ac b/configure.ac index aca7ee6..66c9a92 100644 --- a/configure.ac +++ b/configure.ac @@ -161,2 +161,20 @@ AM_CONDITIONAL([POSTGRES_COND],[test $status_postgres = yes]) +# LDAP +AC_ARG_WITH([ldap], + AC_HELP_STRING([--with-ldap], + [build LDAP module]), + [status_ldap=${withval}], + [status_ldap=maybe]) + +if test $status_ldap != no; then + AC_CHECK_LIB(ldap, ldap_bind, + [status_ldap=yes], + [if test $status_ldap = yes; then + AC_MSG_ERROR([the required library libldap is not found]) + else + status_ldap=no + fi]) +fi +AM_CONDITIONAL([LDAP_COND],[test $status_postgres = yes]) + # Readline @@ -217,2 +235,3 @@ MySQL .................................... $status_mysql Postgres ................................. $status_postgres +LDAP ..................................... $status_ldap Readline ................................. $status_readline @@ -231,2 +250,3 @@ status_mysql=$status_mysql status_postgres=$status_postgres +status_ldap=$status_ldap status_readline=$status_readline @@ -247,2 +267,3 @@ AC_CONFIG_FILES([Makefile modules/postgres/Makefile + modules/ldap/Makefile doc/Makefile]) diff --git a/include/smap/parseopt.h b/include/smap/parseopt.h index b951343..4785511 100644 --- a/include/smap/parseopt.h +++ b/include/smap/parseopt.h @@ -42,3 +42,3 @@ struct smap_option { } v; - int (*func) (struct smap_option *, const char *); + int (*func) (struct smap_option const *, const char *, char **); }; @@ -50,3 +50,14 @@ struct smap_option { -int smap_parseopt(struct smap_option *opt, int argc, char **argv, +#define SMAP_DELIM_EQ 0x00 +#define SMAP_DELIM_WS 0x01 +#define SMAP_DELIM_MASK 0x01 +#define SMAP_IGNORECASE 0x10 + +#define SMAP_PARSE_SUCCESS 0 +#define SMAP_PARSE_NOENT 1 +#define SMAP_PARSE_INVAL 2 + +int smap_parseline(struct smap_option const *opt, const char *line, int delim, + char **errmsg); +int smap_parseopt(struct smap_option const *opt, int argc, char **argv, int flags, int *index); diff --git a/include/smap/wordsplit.h b/include/smap/wordsplit.h index 0c257c3..3b27d25 100644 --- a/include/smap/wordsplit.h +++ b/include/smap/wordsplit.h @@ -36,3 +36,3 @@ struct wordsplit { const char **ws_env; - const char *(*ws_getvar) (const char *, size_t, void *); + char *(*ws_getvar) (const char *, size_t, void *); void *ws_closure; @@ -137,2 +137,4 @@ void wordsplit_c_quote_copy(char *dst, const char *src, int quote_hex); +int wordsplit_varnames(const char *input, char ***ret_names, int af); + void wordsplit_perror(struct wordsplit *ws); diff --git a/lib/parseopt.c b/lib/parseopt.c index 9b516be..cc0c9a6 100644 --- a/lib/parseopt.c +++ b/lib/parseopt.c @@ -23,4 +23,5 @@ -static struct smap_option * -find_opt(struct smap_option *opt, const char *str, const char **value) +static struct smap_option const * +find_opt(struct smap_option const *opt, const char *str, const char **value, + int flags) { @@ -28,4 +29,7 @@ find_opt(struct smap_option *opt, const char *str, const char **value) int isbool; + int delim = flags & SMAP_DELIM_MASK; - if (len > 2 && memcmp(str, "no", 2) == 0) { + if (len > 2 && (flags & SMAP_IGNORECASE + ? strncasecmp + : strncmp)(str, "no", 2) == 0) { *value = NULL; @@ -40,5 +44,18 @@ find_opt(struct smap_option *opt, const char *str, const char **value) if (len >= opt->len - && memcmp(opt->name, str, opt->len) == 0 + && (flags & SMAP_IGNORECASE + ? strncasecmp + : strncmp)(opt->name, str, opt->len) == 0 && (!isbool || opt->type == smap_opt_bool)) { - int eq = str[opt->len] == '='; + int eq; + const char *vp; + + if (delim == SMAP_DELIM_EQ) { + eq = str[opt->len] == '='; + vp = str + opt->len + 1; + } else if (delim == SMAP_DELIM_WS) { + vp = str + opt->len; + eq = isspace(*vp); + if (eq) do ++vp; while (*vp && isspace(*vp)); + } else + abort(); @@ -51,3 +68,3 @@ find_opt(struct smap_option *opt, const char *str, const char **value) continue; - *value = str + opt->len + 1; + *value = vp; break; @@ -56,3 +73,3 @@ find_opt(struct smap_option *opt, const char *str, const char **value) if (eq) - *value = str + opt->len + 1; + *value = vp; else @@ -83,3 +100,76 @@ find_value(const char **enumstr, const char *value) int -smap_parseopt(struct smap_option *opt, int argc, char **argv, int flags, +smap_parseline(struct smap_option const *opt, const char *line, int flags, + char **errmsg) +{ + int rc = SMAP_PARSE_SUCCESS; + long n; + char *s; + const char *value; + struct smap_option const *p = find_opt(opt, line, &value, flags); + + if (!p) + return SMAP_PARSE_NOENT; + + switch (p->type) { + case smap_opt_long: + n = strtol(value, &s, 0); + if (*s) { + *errmsg = "not a valid number"; + rc = SMAP_PARSE_INVAL; + break; + } + *(long*)p->data = n; + break; + + case smap_opt_const: + *(long*)p->data = p->v.value; + break; + + case smap_opt_const_string: + *(const char**)p->data = value; + break; + + case smap_opt_string: + *(const char**)p->data = strdup(value); + break; + + case smap_opt_bool: + if (p->v.value) { + if (value) + *(int*)p->data |= p->v.value; + else + *(int*)p->data &= ~p->v.value; + } else + *(int*)p->data = value != NULL; + break; + + case smap_opt_bitmask: + *(int*)p->data |= p->v.value; + break; + + case smap_opt_bitmask_rev: + *(int*)p->data &= ~p->v.value; + break; + + case smap_opt_enum: + n = find_value(p->v.enumstr, value); + if (n == -1) { + *errmsg = "invalid value"; + rc = SMAP_PARSE_INVAL; + break; + } + *(int*)p->data = n; + break; + + case smap_opt_null: + break; + } + + if (p->func && p->func(p, value, errmsg)) + rc = SMAP_PARSE_INVAL; + return rc; +} + +int +smap_parseopt(struct smap_option const *opt, int argc, char **argv, int flags, int *pindex) @@ -94,6 +184,10 @@ smap_parseopt(struct smap_option *opt, int argc, char **argv, int flags, i < argc; i++) { - const char *value; - struct smap_option *p = find_opt(opt, argv[i], &value); - - if (!p) { + char *errmsg; + int res; + + res = smap_parseline(opt, argv[i], SMAP_DELIM_EQ, &errmsg); + + if (res == SMAP_PARSE_SUCCESS) + continue; + else if (res == SMAP_PARSE_NOENT) { if (pindex) { @@ -101,5 +195,10 @@ smap_parseopt(struct smap_option *opt, int argc, char **argv, int flags, int j; + struct smap_option const *p = NULL; + const char *value; for (j = i + 1; j < argc; j++) - if ((p = find_opt(opt, argv[j], &value))) + if ((p = find_opt(opt, + argv[j], + &value, + SMAP_DELIM_EQ))) break; @@ -110,2 +209,3 @@ smap_parseopt(struct smap_option *opt, int argc, char **argv, int flags, argv[i] = tmp; + --i; } else @@ -118,65 +218,8 @@ smap_parseopt(struct smap_option *opt, int argc, char **argv, int flags, rc = 1; - continue; } - } - - switch (p->type) { - case smap_opt_long: - n = strtol(value, &s, 0); - if (*s) { - smap_error("%s: %s: %s is not a valid number", - modname, p->name, value); - rc = 1; - continue; - } - *(long*)p->data = n; - break; - - case smap_opt_const: - *(long*)p->data = p->v.value; - break; - - case smap_opt_const_string: - *(const char**)p->data = value; - break; - - case smap_opt_string: - *(const char**)p->data = strdup(value); - break; - - case smap_opt_bool: - if (p->v.value) { - if (value) - *(int*)p->data |= p->v.value; - else - *(int*)p->data &= ~p->v.value; - } else - *(int*)p->data = value != NULL; - break; - - case smap_opt_bitmask: - *(int*)p->data |= p->v.value; - break; - - case smap_opt_bitmask_rev: - *(int*)p->data &= ~p->v.value; - break; - - case smap_opt_enum: - n = find_value(p->v.enumstr, value); - if (n == -1) { - smap_error("%s: %s: invalid value %s", - modname, p->name, value); - rc = 1; - continue; - } - *(int*)p->data = n; - break; - - case smap_opt_null: - break; - } - - if (p->func && p->func (p, value)) + } else if (res == SMAP_PARSE_INVAL) { + smap_error("%s: %s: %s", + modname, argv[i], errmsg); rc = 1; + } } diff --git a/lib/wordsplit.c b/lib/wordsplit.c index 1064053..3788c08 100644 --- a/lib/wordsplit.c +++ b/lib/wordsplit.c @@ -50,2 +50,5 @@ +#define ISVARBEG(c) (ISALPHA(c) || c == '_') +#define ISVARCHR(c) (ISALNUM(c) || c == '_') + #define ALLOC_INIT 128 @@ -646,3 +649,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, const char *defstr = NULL; - const char *value; + char *value; const char *vptr; @@ -651,5 +654,5 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, - if (ISALPHA(str[0]) || str[0] == '_') { + if (ISVARBEG(str[0])) { for (i = 1; i < len; i++) - if (!(ISALNUM(str[i]) || str[i] == '_')) + if (!ISVARCHR(str[i])) break; @@ -713,10 +716,11 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, if (wsp->ws_flags & WRDSF_WARNUNDEF) - wsp-> - ws_error(_ - ("warning: undefined variable `%.*s'"), - i, str); + wsp->ws_error(_("warning: undefined variable `%.*s'"), + i, str); if (wsp->ws_flags & WRDSF_KEEPUNDEF) value = NULL; - else - value = ""; + else { + value = strdup(""); + if (!value) + return _wsplt_nomem(wsp); + } } @@ -730,3 +734,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg; - newnode->v.word = strdup(value); + newnode->v.word = value; if (!newnode->v.word) @@ -734,2 +738,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, } else if (*value == 0) { + free(value); /* Empty string is a special case */ @@ -747,4 +752,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, WRDSF_NOVAR | WRDSF_NOCMD | - WRDSF_DELIM | WRDSF_SQUEEZE_DELIMS)) - { + WRDSF_DELIM | WRDSF_SQUEEZE_DELIMS)) { wordsplit_free(&ws); @@ -752,2 +756,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, } + free(value); for (i = 0; i < ws.ws_wordc; i++) { @@ -760,4 +765,3 @@ expvar(struct wordsplit *wsp, const char *str, size_t len, (i + 1 < - ws. - ws_wordc ? (flg & ~_WSNF_JOIN) : flg); + ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg); newnode->v.word = strdup(ws.ws_wordv[i]); @@ -1385 +1389,66 @@ wordsplit_strerror(struct wordsplit *ws) } + +int +wordsplit_varnames(const char *input, char ***ret_names, int af) +{ + const char *p; + size_t count; + char **names; + size_t i = 0; + + if (!input) { + errno = EINVAL; + return -1; + } + + for (p = input; *p; p++) { + if (*p == '\\') + ++p; + else if (*p == '$') + ++count; + } + + if (af && *ret_names) { + names = *ret_names; + for (i = 0; names[i]; i++) + ; + names = realloc(names, (i + count + 1) * sizeof(names)); + } else + names = calloc(count + 1, sizeof(names[0])); + if (!names) + return -1; + *ret_names = names; + + for (p = input; *p; ) { + if (*p == '\\') + ++p; + else if (*p == '$') { + char const *kw; + size_t len; + + if (*++p == 0) + break; + else if (*p == '{') { + kw = ++p; + while (*p && !(*p == ':' || *p == '}')) + ++p; + } else if (ISVARBEG(*p)) { + kw = p++; + while (*p && ISVARCHR(*p)) + ++p; + } else + continue; + len = p - kw; + if (len) { + if (!(names[i] = malloc(len + 1))) + return 1; + memcpy(names[i], kw, len); + names[i][len] = 0; + ++i; + } + } else + ++p; + } + names[i] = NULL; + return 0; +} diff --git a/modules/Makefile.am b/modules/Makefile.am index e47c0c8..de7ff49 100644 --- a/modules/Makefile.am +++ b/modules/Makefile.am @@ -28,2 +28,13 @@ if POSTGRES_COND endif -SUBDIRS = echo sed $(MAILUTILS_DIR) $(GUILE_DIR) $(MYSQL_DIR) $(POSTGRES_DIR) +if LDAP_COND + LDAP_DIR=ldap +endif +SUBDIRS =\ + echo\ + sed\ + $(MAILUTILS_DIR)\ + $(GUILE_DIR)\ + $(MYSQL_DIR)\ + $(POSTGRES_DIR)\ + $(LDAP_DIR) + diff --git a/modules/ldap/Makefile.am b/modules/ldap/Makefile.am new file mode 100644 index 0000000..4ffbf7b --- /dev/null +++ b/modules/ldap/Makefile.am @@ -0,0 +1,23 @@ +# This file is part of Smap. +# Copyright (C) 2014 Sergey Poznyakoff +# +# Smap 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 3, or (at your option) +# any later version. +# +# Smap 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 Smap. If not, see <http://www.gnu.org/licenses/>. + +moddir=@SMAP_MODDIR@ + +mod_LTLIBRARIES=ldap.la +ldap_la_SOURCES=ldap.c +ldap_la_LIBADD=-lldap +AM_LDFLAGS = -module -avoid-version -no-undefined +AM_CPPFLAGS = -I$(top_srcdir)/include diff --git a/modules/ldap/ldap.c b/modules/ldap/ldap.c new file mode 100644 index 0000000..bb382eb --- /dev/null +++ b/modules/ldap/ldap.c @@ -0,0 +1,974 @@ +/* This file is part of Smap. + Copyright (C) 2014 Sergey Poznyakoff + + Smap 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 3, or (at your option) + any later version. + + Smap 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 Smap. If not, see <http://www.gnu.org/licenses/>. */ + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif +#include <sys/stat.h> +#include <fcntl.h> +#include <errno.h> +#include <ldap.h> +#include <smap/stream.h> +#include <smap/diag.h> +#include <smap/module.h> +#include <smap/parseopt.h> +#include <smap/wordsplit.h> + +static int dbgid; +static int ldap_debug_level; //FIXME + +enum tls_state { + tls_no, + tls_yes, + tls_only +}; + +/* module ldap ldap config-file=FILE uri=URI version=2|3 + tls=no|yes|only + base=BASEDN + binddon=DN + bindpw=PASS + bindpwfile=FILE + filter=FILTER + positive-reply=EXPR + negative-reply=EXPR + onerror-reply=EXPR + */ + +struct ldap_conf { + enum tls_state tls; + char *config_file; + char *uri; + char *base; + int protocol; + char *cacert; + char *filter; + char **attrs; + + char *binddn; + char *bindpw; + char *bindpwfile; + + char *positive_reply; + char *negative_reply; + char *onerror_reply; +}; + +struct ldap_db { + struct ldap_conf conf; + LDAP *ldap; +}; + +static struct ldap_conf def_conf; +static char dfl_positive_reply[] = "OK"; +static char dfl_negative_reply[] = "NOTFOUND"; +static char dfl_onerror_reply[] = "NOTFOUND"; + +static void +argz_free(char **a) +{ + int i; + + if (!a) + return; + for (i = 0; a[i]; i++) + free(a[i]); + free(a); +} + +static int +argz_copy(char ***dst, char **a) +{ + int i, n; + char **b; + + if (!a) { + *dst = NULL; + return 0; + } + + for (n = 0; a[n]; n++) + ; + + b = calloc(i + 1, sizeof(b[0])); + if (!b) + return -1; + for (i = 0; i < n; i++) { + b[i] = strdup(a[i]); + if (!b[i]) + return -1; + } + b[i] = NULL; + return 0; +} + +static void +ldap_conf_free(struct ldap_conf *cp) +{ + free(cp->config_file); + free(cp->uri); + free(cp->base); + free(cp->cacert); + free(cp->filter); + argz_free(cp->attrs); + + free(cp->binddn); + free(cp->bindpw); + free(cp->bindpwfile); + + free(cp->positive_reply); + free(cp->negative_reply); + free(cp->onerror_reply); +} + +struct ldap_conf * +ldap_conf_cpy(struct ldap_conf *dst, struct ldap_conf *src) +{ +#define STRCPY(a) do { \ + if (src->a) { \ + dst->a = strdup(src->a); \ + if (!dst->a) { \ + ldap_conf_free(dst); \ + return NULL; \ + } \ + } else \ + dst->a = src->a; \ +} while(0) + + + memset(dst, 0, sizeof(*dst)); + dst->tls = src->tls; + STRCPY(config_file); + STRCPY(uri); + STRCPY(base); + dst->protocol = src->protocol; + STRCPY(cacert); + STRCPY(filter); + if (argz_copy(&dst->attrs, src->attrs)) { + ldap_conf_free(dst); + return NULL; + } + + STRCPY(binddn); + STRCPY(bindpw); + STRCPY(bindpwfile); + + STRCPY(positive_reply); + STRCPY(negative_reply); + STRCPY(onerror_reply); + + return dst; +} + + +static int +parse_ldap_conf(const char *name, struct smap_option const *opt) +{ + int rc = 0; + FILE *fp; + char buf[1024]; + unsigned line; + char *p; + + fp = fopen(name, "r"); + if (!fp) { + smap_error("can't open LDAP config file %s: %s", + name, strerror(errno)); + return -1; + } + + line = 0; + while (p = fgets(buf, sizeof(buf), fp)) { + size_t len; + char *errmsg; + + ++line; + + while (*p && isspace(*p)) + ++p; + + if (*p == '#') + continue; + + len = strlen(p); + if (len) { + if (p[len-1] == '\n') + p[--len] = 0; + else if (!feof(fp)) { + smap_error("%s:%u: line too long, skipping"); + while (!feof(fp) && fgetc(fp) != '\n') + ; + ++line; + continue; + } + + while (len > 0 && isspace(p[len-1])) + --len; + } + + if (len == 0) + continue; + p[len] = 0; + + switch (smap_parseline(opt, p, SMAP_IGNORECASE|SMAP_DELIM_WS, + &errmsg)) { + case SMAP_PARSE_SUCCESS: + smap_debug(dbgid, 2, ("%s:%u: accepted line %s", + name, line, p)); + break; + + case SMAP_PARSE_INVAL: + smap_error("%s:%u: %s", name, line, errmsg); + rc = 1; + break; + + case SMAP_PARSE_NOENT: + smap_debug(dbgid, 2, ("%s:%u: unrecognized line %s", + name, line, p)); + } + } + + fclose(fp); + return rc; +} + +static int +readconf(struct smap_option const *opt, const char *val, char **errmsg) +{ + int rc = parse_ldap_conf(val, opt); + if (rc) + *errmsg = "parse error"; + return rc; +} + + +static int +mod_ldap_init(int argc, char **argv) +{ + struct smap_option init_option[] = { + { SMAP_OPTSTR(config-file), smap_opt_null, + &init_option, 0, readconf }, + { SMAP_OPTSTR(ssl-ca), smap_opt_string, + &def_conf.cacert }, + { SMAP_OPTSTR(tls-ca), smap_opt_string, + &def_conf.cacert }, + { SMAP_OPTSTR(uri), smap_opt_string, + &def_conf.uri }, + { SMAP_OPTSTR(base), smap_opt_string, + &def_conf.base }, + + { SMAP_OPTSTR(filter), smap_opt_string, + &def_conf.filter }, + + { SMAP_OPTSTR(binddn), smap_opt_string, + &def_conf.binddn }, + + { SMAP_OPTSTR(bindpw), smap_opt_string, + &def_conf.bindpw }, + { SMAP_OPTSTR(bindpwfile), smap_opt_string, + &def_conf.bindpwfile }, + + { SMAP_OPTSTR(positive-reply), smap_opt_string, + &def_conf.positive_reply }, + { SMAP_OPTSTR(negative-reply), smap_opt_string, + &def_conf.negative_reply }, + { SMAP_OPTSTR(onerror-reply), smap_opt_string, + &def_conf.onerror_reply }, + { NULL } + }; + + dbgid = smap_debug_alloc("ldap"); + + if (smap_parseopt(init_option, argc, argv, 0, NULL)) + return 1; + + return 0; +} + +static char * +argcv_concat(int wc, char **wv) +{ + char *res, *p; + size_t size = 0; + int i; + + for (i = 0; i < wc; i++) + size += strlen(wv[i]) + 1; + res = malloc(size); + if (!res) + return 0; + for (p = res, i = 0;;) { + strcpy(p, wv[i]); + p += strlen(wv[i]); + if (++i < wc) + *p++ = ' '; + else + break; + } + *p = 0; + return res; +} + +char * +parse_ldap_uri(const char *uri) +{ + LDAPURLDesc *ludlist, **ludp; + char **urls = NULL; + int nurls = 0; + char *ldapuri = NULL; + int rc; + + rc = ldap_url_parse(uri, &ludlist); + if (rc != LDAP_URL_SUCCESS) { + smap_error("cannot parse LDAP URL(s)=%s (%d)", + uri, rc); + return NULL; + } + + for (ludp = &ludlist; *ludp; ) { + LDAPURLDesc *lud = *ludp; + char **tmp; + + if (lud->lud_dn && lud->lud_dn[0] + && (lud->lud_host == NULL || lud->lud_host[0] == '\0')) { + /* if no host but a DN is provided, try + DNS SRV to gather the host list */ + char *domain = NULL, *hostlist = NULL; + size_t i; + struct wordsplit ws; + + if (ldap_dn2domain(lud->lud_dn, &domain) || + !domain) { + smap_error("DNS SRV: cannot convert " + "DN=\"%s\" into a domain", + lud->lud_dn); + goto dnssrv_free; + } + + rc = ldap_domain2hostlist(domain, &hostlist); + if (rc) { + smap_error("DNS SRV: cannot convert " + "domain=%s into a hostlist", + domain); + goto dnssrv_free; + } + + if (mu_wordsplit(hostlist, &ws, WRDSF_DEFFLAGS)) { + smap_error("DNS SRV: could not parse hostlist=\"%s\": %s", + hostlist, + mu_wordsplit_strerror(&ws)); + goto dnssrv_free; + } + + tmp = realloc(urls, sizeof(char *) * + (nurls + ws.ws_wordc + 1)); + if (!tmp) { + smap_error("DNS SRV %s", strerror(errno)); + goto dnssrv_free; + } + + urls = tmp; + urls[nurls] = NULL; + + for (i = 0; i < ws.ws_wordc; i++) { + char *p = malloc(strlen(lud->lud_scheme) + + strlen(ws.ws_wordv[i]) + + 3); + if (!p) { + smap_error("DNS SRV %s", + strerror(errno)); + goto dnssrv_free; + } + + strcpy(p, lud->lud_scheme); + strcat(p, "//"); + strcat(p, ws.ws_wordv[i]); + + urls[nurls + i + 1] = NULL; + urls[nurls + i] = p; + } + + nurls += i; + + dnssrv_free: + mu_wordsplit_free (&ws); + ber_memfree(hostlist); + ber_memfree(domain); + } else { + tmp = realloc(urls, sizeof(char *) * (nurls + 2)); + if (!tmp) { + smap_error("DNS SRV %s", strerror(errno)); + break; + } + urls = tmp; + urls[nurls + 1] = NULL; + + urls[nurls] = ldap_url_desc2str(lud); + if (!urls[nurls]) { + smap_error("DNS SRV %s", + strerror(errno)); + break; + } + nurls++; + } + + *ludp = lud->lud_next; + + lud->lud_next = NULL; + ldap_free_urldesc(lud); + } + + if (ludlist) { + ldap_free_urldesc(ludlist); + return NULL; + } else if (!urls) + return NULL; + ldapuri = argcv_concat(nurls, urls); + if (!ldapuri) + smap_error("%s", strerror(errno)); + ber_memvfree((void **)urls); + return ldapuri; +} + +static LDAP * +ldap_connect(struct ldap_conf *conf) +{ + int rc; + char *ldapuri = NULL; + LDAP *ld = NULL; + char *val; + unsigned long lval; + + if (ldap_debug_level) { + if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, + &ldap_debug_level) + != LBER_OPT_SUCCESS ) + smap_error("cannot set LBER_OPT_DEBUG_LEVEL %d", + ldap_debug_level); + + if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, + &ldap_debug_level) + != LDAP_OPT_SUCCESS ) + smap_error("could not set LDAP_OPT_DEBUG_LEVEL %d", + ldap_debug_level); + } + + if (conf->uri) { + ldapuri = parse_ldap_uri(conf->uri); + if (!ldapuri) + return NULL; + } + + smap_debug(dbgid, 1, ("constructed LDAP URI: %s", + ldapuri ? ldapuri : "<DEFAULT>")); + + rc = ldap_initialize(&ld, ldapuri); + if (rc != LDAP_SUCCESS) { + smap_error("cannot create LDAP session handle for " + "URI=%s (%d): %s", + ldapuri, rc, ldap_err2string(rc)); + free(ldapuri); + return NULL; + } + free(ldapuri); + + if (conf->protocol) + ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, + &conf->protocol); + + if (conf->tls != tls_no) { + rc = ldap_start_tls_s(ld, NULL, NULL); + if (rc != LDAP_SUCCESS) { + char *msg = NULL; + ldap_get_option(ld, + LDAP_OPT_DIAGNOSTIC_MESSAGE, + (void*)&msg); + smap_error("ldap_start_tls failed: %s", + ldap_err2string(rc)); + smap_error("TLS diagnostics: %s", msg); + ldap_memfree(msg); + + if (conf->tls == tls_only) { + ldap_unbind(ld); + return NULL; + } + /* try to continue anyway */ + } else if (conf->cacert) { + rc = ldap_set_option(ld, + LDAP_OPT_X_TLS_CACERTFILE, + conf->cacert); + if (rc != LDAP_SUCCESS) { + smap_error("setting of LDAP_OPT_X_TLS_CACERTFILE failed"); + if (conf->tls == tls_only) { + ldap_unbind(ld); + return NULL; + } + } + } + } + + /* FIXME: Timeouts, SASL, etc. */ + return ld; +} + +static int +full_read(int fd, char *file, char *buf, size_t size) +{ + while (size) { + ssize_t n; + + n = read(fd, buf, size); + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + smap_error("error reading from %s: %s", + file, strerror(errno)); + return -1; + } else if (n == 0) { + smap_error("short read from %s", file); + return -1; + } + + buf += n; + size -= n; + } + return 0; +} + +static int +get_passwd(struct ldap_conf *conf, struct berval *pwd, char **palloc) +{ + char *file; + + if (conf->bindpwfile) { + struct stat st; + int fd, rc; + char *mem, *p; + + fd = open(file, O_RDONLY); + if (fd == -1) { + smap_error("can't open password file %s: %s", + file, strerror(errno)); + return -1; + } + if (fstat(fd, &st)) { + smap_error("can't stat password file %s: %s", + file, strerror(errno)); + close(fd); + return -1; + } + mem = malloc(st.st_size + 1); + if (!mem) { + smap_error("can't allocate memory (%lu bytes)", + (unsigned long) st.st_size+1); + close(fd); + return -1; + } + rc = full_read(fd, file, mem, st.st_size); + close(fd); + if (rc) + return rc; + mem[st.st_size] = 0; + p = strchr(mem, '\n'); + if (p) + *p = 0; + *palloc = mem; + pwd->bv_val = mem; + } else + pwd->bv_val = conf->bindpw; + pwd->bv_len = pwd->bv_val ? strlen(pwd->bv_val) : 0; + return 0; +} + +static int +ldap_bind(LDAP *ld, struct ldap_conf *conf) +{ + int msgid, err, rc; + LDAPMessage *result; + LDAPControl **ctrls; + char msgbuf[256]; + char *matched = NULL; + char *info = NULL; + char **refs = NULL; + struct berval passwd; + char *alloc_ptr = NULL; + + if (get_passwd(conf, &passwd, &alloc_ptr)) + return 1; + + msgbuf[0] = 0; + + rc = ldap_sasl_bind(ld, conf->binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &msgid); + if (msgid == -1) { + smap_error("ldap_sasl_bind(SIMPLE) failed: %s", + ldap_err2string(rc)); + free(alloc_ptr); + return 1; + } + + if (ldap_result(ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1) { + smap_error("ldap_result failed"); + free(alloc_ptr); + return 1; + } + + rc = ldap_parse_result(ld, result, &err, &matched, &info, &refs, + &ctrls, 1); + if (rc != LDAP_SUCCESS) { + smap_error("ldap_parse_result failed: %s", + ldap_err2string(rc)); + free(alloc_ptr); + return 1; + } + + if (ctrls) + ldap_controls_free(ctrls); + + if (err != LDAP_SUCCESS + || msgbuf[0] + || (matched && matched[0]) + || (info && info[0]) + || refs) { + + smap_debug(dbgid, 1, ("ldap_bind: %s (%d)%s", + ldap_err2string(err), err, msgbuf)); + + if (matched && *matched) + smap_debug(dbgid, 1, ("matched DN: %s", matched)); + + if (info && *info) + smap_debug(dbgid, 1, ("additional info: %s", info)); + |