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
@@ -156,12 +156,30 @@ if test $status_postgres != no; then
fi
status_postgres=no ],
[/usr/local/pgsql/lib /usr/pgsql/lib])
fi
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
AC_ARG_WITH([readline],
AC_HELP_STRING([--with-readline],
[use readline]),
[status_readline=${withval}],
[status_readline=maybe])
@@ -212,12 +230,13 @@ Smap configured with the following settings:
TCP wrappers ............................. $status_tcpwrap
Mailutils ................................ $status_mailutils
Guile .................................... $status_guile
MySQL .................................... $status_mysql
Postgres ................................. $status_postgres
+LDAP ..................................... $status_ldap
Readline ................................. $status_readline
*******************************************************************
EOF
],[
status_tcpwrap=$status_tcpwrap
@@ -226,12 +245,13 @@ if test $status_mailutils = yes; then
else
status_mailutils=$status_mailutils
fi
status_guile=$status_guile
status_mysql=$status_mysql
status_postgres=$status_postgres
+status_ldap=$status_ldap
status_readline=$status_readline
])
AC_CONFIG_FILES([Makefile
include/Makefile
include/smap/Makefile
@@ -242,8 +262,9 @@ AC_CONFIG_FILES([Makefile
modules/echo/Makefile
modules/sed/Makefile
modules/mailutils/Makefile
modules/guile/Makefile
modules/mysql/Makefile
modules/postgres/Makefile
+ modules/ldap/Makefile
doc/Makefile])
AC_OUTPUT
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
@@ -37,18 +37,29 @@ struct smap_option {
enum smap_opt_type type;
void *data;
union {
long value;
const char **enumstr;
} v;
- int (*func) (struct smap_option *, const char *);
+ int (*func) (struct smap_option const *, const char *, char **);
};
#define SMAP_OPTSTR(s) #s, (sizeof(#s) - 1)
#define SMAP_PARSEOPT_PARSE_ARGV0 0x01
#define SMAP_PARSEOPT_PERMUTE 0x02
-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);
#endif
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
@@ -31,13 +31,13 @@ struct wordsplit {
void (*ws_error)(const char *, ...)
__attribute__ ((__format__ (__printf__, 1, 2)));
void (*ws_debug)(const char *, ...)
__attribute__ ((__format__ (__printf__, 1, 2)));
const char **ws_env;
- const char *(*ws_getvar) (const char *, size_t, void *);
+ char *(*ws_getvar) (const char *, size_t, void *);
void *ws_closure;
const char *ws_input;
size_t ws_len;
size_t ws_endp;
int ws_errno;
@@ -132,11 +132,13 @@ int wordsplit_c_quote_char(int c);
size_t wordsplit_c_quoted_length(const char *str, int quote_hex,
int *quote);
void wordsplit_sh_unquote_copy(char *dst, const char *src, size_t n);
void wordsplit_c_unquote_copy(char *dst, const char *src, size_t n);
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);
const char *wordsplit_strerror(struct wordsplit *ws);
#endif
diff --git a/lib/parseopt.c b/lib/parseopt.c
index 9b516be..cc0c9a6 100644
--- a/lib/parseopt.c
+++ b/lib/parseopt.c
@@ -18,46 +18,63 @@
# include <config.h>
#endif
#include <smap/parseopt.h>
#include <smap/diag.h>
#include <string.h>
-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)
{
size_t len = strlen(str);
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;
str += 2;
isbool = 1;
} else {
isbool = 0;
*value = str;
}
for (; opt->name; opt++) {
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();
switch (opt->type) {
case smap_opt_long:
case smap_opt_string:
case smap_opt_const_string:
case smap_opt_enum:
if (!eq)
continue;
- *value = str + opt->len + 1;
+ *value = vp;
break;
case smap_opt_null:
if (eq)
- *value = str + opt->len + 1;
+ *value = vp;
else
*value = NULL;
break;
default:
if (eq)
@@ -78,109 +95,135 @@ find_value(const char **enumstr, const char *value)
if (strcmp(*enumstr, value) == 0)
return i;
return -1;
}
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)
{
int i;
long n;
char *s;
int rc = 0;
const char *modname = argv[0];
for (i = (flags & SMAP_PARSEOPT_PARSE_ARGV0) ? 0 : 1;
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) {
if (flags & SMAP_PARSEOPT_PERMUTE) {
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;
if (p) {
char *tmp = argv[j];
argv[j] = argv[i];
argv[i] = tmp;
+ --i;
} else
break;
} else
break;
} else {
smap_error("%s: %s: unknown option",
modname, argv[i]);
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;
+ }
}
if (pindex)
*pindex = i;
return rc;
}
diff --git a/lib/wordsplit.c b/lib/wordsplit.c
index 1064053..3788c08 100644
--- a/lib/wordsplit.c
+++ b/lib/wordsplit.c
@@ -45,12 +45,15 @@
#define ISALPHA(c) (ISUPPER(c) || ISLOWER(c))
#define ISDIGIT(c) ('0' <= ((unsigned) (c)) && ((unsigned) (c)) <= '9')
#define ISXDIGIT(c) (strchr("abcdefABCDEF", c)!=NULL)
#define ISALNUM(c) (ISALPHA(c) || ISDIGIT(c))
#define ISPRINT(c) (' ' <= ((unsigned) (c)) && ((unsigned) (c)) <= 127)
+#define ISVARBEG(c) (ISALPHA(c) || c == '_')
+#define ISVARCHR(c) (ISALNUM(c) || c == '_')
+
#define ALLOC_INIT 128
#define ALLOC_INCR 128
static void
_wsplt_alloc_die(struct wordsplit *wsp)
{
@@ -641,20 +644,20 @@ wordsplit_find_env(struct wordsplit *wsp, const char *name, size_t len)
static int
expvar(struct wordsplit *wsp, const char *str, size_t len,
struct wordsplit_node **ptail, const char **pend, int flg)
{
size_t i = 0;
const char *defstr = NULL;
- const char *value;
+ char *value;
const char *vptr;
struct wordsplit_node *newnode;
const char *start = str - 1;
- 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;
*pend = str + i - 1;
} else if (str[0] == '{') {
str++;
len--;
for (i = 1; i < len; i++)
@@ -708,33 +711,35 @@ expvar(struct wordsplit *wsp, const char *str, size_t len,
wsp->ws_errno = WRDSE_UNDEF;
if (wsp->ws_flags & WRDSF_SHOWERR)
wordsplit_perror(wsp);
return 1;
} else {
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);
+ }
}
/* FIXME: handle defstr */
if (value) {
if (flg & _WSNF_QUOTE) {
if (wsnode_new(wsp, &newnode))
return 1;
wsnode_insert(wsp, newnode, *ptail, 0);
*ptail = newnode;
newnode->flags = _WSNF_WORD | _WSNF_NOEXPAND | flg;
- newnode->v.word = strdup(value);
+ newnode->v.word = value;
if (!newnode->v.word)
return _wsplt_nomem(wsp);
} else if (*value == 0) {
+ free(value);
/* Empty string is a special case */
if (wsnode_new(wsp, &newnode))
return 1;
wsnode_insert(wsp, newnode, *ptail, 0);
*ptail = newnode;
newnode->flags = _WSNF_NULL;
@@ -742,27 +747,26 @@ expvar(struct wordsplit *wsp, const char *str, size_t len,
struct wordsplit ws;
int i;
ws.ws_delim = wsp->ws_delim;
if (wordsplit(value, &ws,
WRDSF_NOVAR | WRDSF_NOCMD |
- WRDSF_DELIM | WRDSF_SQUEEZE_DELIMS))
- {
+ WRDSF_DELIM | WRDSF_SQUEEZE_DELIMS)) {
wordsplit_free(&ws);
return 1;
}
+ free(value);
for (i = 0; i < ws.ws_wordc; i++) {
if (wsnode_new(wsp, &newnode))
return 1;
wsnode_insert(wsp, newnode, *ptail, 0);
*ptail = newnode;
newnode->flags = _WSNF_WORD |
_WSNF_NOEXPAND |
(i + 1 <
- ws.
- ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
+ ws.ws_wordc ? (flg & ~_WSNF_JOIN) : flg);
newnode->v.word = strdup(ws.ws_wordv[i]);
if (!newnode->v.word)
return _wsplt_nomem(wsp);
}
wordsplit_free(&ws);
}
@@ -1380,6 +1384,71 @@ const char *
wordsplit_strerror(struct wordsplit *ws)
{
if (ws->ws_errno < _wordsplit_nerrs)
return _wordsplit_errstr[ws->ws_errno];
return N_("unknown error");
}
+
+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
@@ -23,7 +23,18 @@ endif
if MYSQL_COND
MYSQL_DIR=mysql
endif
if POSTGRES_COND
POSTGRES_DIR=postgres
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