aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergey Poznyakoff <gray@gnu.org>2014-08-24 17:34:16 +0300
committerSergey Poznyakoff <gray@gnu.org>2014-08-24 17:34:16 +0300
commit124b8aa2497703558fcebe5b10b675e1d426759d (patch)
treeda644de0ac17c7e53e7df5e666765606f7a4026d
parent1cb788cfec3dca18a2a06f30fb1e38826a51e89d (diff)
downloadsmap-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.ac21
-rw-r--r--include/smap/parseopt.h15
-rw-r--r--include/smap/wordsplit.h4
-rw-r--r--lib/parseopt.c191
-rw-r--r--lib/wordsplit.c97
-rw-r--r--modules/Makefile.am13
-rw-r--r--modules/ldap/Makefile.am23
-rw-r--r--modules/ldap/ldap.c974
-rw-r--r--modules/mysql/Makefile.am1
-rw-r--r--modules/mysql/mysql.c2
-rw-r--r--src/srvman.c1
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));
+