From 9c97ac525930863abd56a77db9b3cdcf63c4cdd6 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Mon, 8 Oct 2012 00:54:16 +0300 Subject: Implement LDAP backend. * configure.ac: Check for openldap. * lib/Makefile.am (maps) [COND_LDAP]: Add ldapmap.c * lib/ldapmap.c: New file. * lib/libeclat.h (eclat_map_drv_ldap): New extern. * src/eclat.c (main) [WITH_LDAP]: Register eclat_map_drv_ldap. --- configure.ac | 29 ++- lib/Makefile.am | 7 +- lib/ldapmap.c | 550 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ lib/libeclat.h | 2 +- src/eclat.c | 3 + 5 files changed, 587 insertions(+), 4 deletions(-) create mode 100644 lib/ldapmap.c diff --git a/configure.ac b/configure.ac index fbb53ea..4cdaa05 100644 --- a/configure.ac +++ b/configure.ac @@ -74,7 +74,7 @@ AC_ARG_WITH([gdbm], if test $status_gdbm != "no"; then AC_CHECK_HEADERS(gdbm.h) AC_CHECK_LIB(gdbm, gdbm_open, [dbmlib=-lgdbm], [dbmlib=]) - if test "$ac_cv_header_getopt_h" = yes && test -n "$dbmlib"; then + if test "$ac_cv_header_gdbm_h" = yes && test -n "$dbmlib"; then MAPLIBS="$MAPLIBS $dbmlib" status_gdbm=yes AC_DEFINE(WITH_GDBM,1,[Use GDB GDBM]) @@ -85,7 +85,32 @@ if test $status_gdbm != "no"; then fi fi AM_CONDITIONAL([COND_GDBM],[test $status_gdbm = yes]) - + +# LDAP +AC_ARG_WITH(ldap, + [AC_HELP_STRING([--with-ldap], + [use OpenLDAP])], + [case $withval in + yes|no) status_ldap=$withval;; + *) AC_MSG_ERROR(bad value ${withval} for --with-ldap) ;; + esac], + [status_ldap=auto]) + +if test $status_ldap != no; then + AC_CHECK_HEADERS(ldap.h) + AC_CHECK_LIB(ldap, ldap_bind,[ldaplib=-lldap],[ldaplib=]) + if test "$ac_cv_header_ldap_h" = yes && test -n "$ldaplib"; then + MAPLIBS="$MAPLIBS $ldaplib" + status_ldap=yes + AC_DEFINE(WITH_LDAP,1,[Use OpenLDAP]) + elif test $status_ldap = yes; then + AC_MSG_ERROR([required library libldap (or its headers) is not found]) + else + status_ldap=no + fi +fi +AM_CONDITIONAL([COND_LDAP],[test $status_ldap = yes]) + # Grecs subsystem GRECS_SETUP([grecs],[tests getopt git2chg]) diff --git a/lib/Makefile.am b/lib/Makefile.am index 829ef1b..bdbab92 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -20,9 +20,14 @@ if COND_GDBM GDBMMAP=gdbmmap.c endif +if COND_LDAP + LDAPMAP=ldapmap.c +endif + maps = \ $(GDBMMAP)\ - filemap.c + filemap.c\ + $(LDAPMAP) libeclat_a_SOURCES=\ base64.c\ diff --git a/lib/ldapmap.c b/lib/ldapmap.c new file mode 100644 index 0000000..a686718 --- /dev/null +++ b/lib/ldapmap.c @@ -0,0 +1,550 @@ +/* This file is part of Eclat. + Copyright (C) 2012 Sergey Poznyakoff. + + Eclat 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. + + Eclat 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 Eclat. If not, see . */ + +#include "libeclat.h" +#include "wordsplit.h" +#include +#include +#include + +enum { tls_no, tls_yes, tls_only }; + +struct ldap_map { + unsigned long ldap_version; + char *uri; + char *base; + char *binddn; + char *bindpw; + int tls; + char *filter; + char *attr; + int dbg; + LDAP *ld; +}; + +static int +cb_null(enum grecs_callback_command cmd, + grecs_locus_t *locus, + void *varptr, + grecs_value_t *value, + void *cb_data) +{ + return 0; +} + +static int +cb_tls(enum grecs_callback_command cmd, + grecs_locus_t *locus, + void *varptr, + grecs_value_t *value, + void *cb_data) +{ + int *tls = varptr; + + if (cmd != grecs_callback_set_value) { + grecs_error(locus, 0, "Unexpected block statement"); + return 1; + } + if (!value || value->type != GRECS_TYPE_STRING) { + grecs_error(locus, 0, "expected a string"); + return 1; + } + if (strcmp(value->v.string, "yes") == 0) + *tls = tls_yes; + else if (strcmp(value->v.string, "no") == 0) + *tls = tls_no; + else if (strcmp(value->v.string, "only") == 0) + *tls = tls_only; + else + grecs_error(locus, 0, "wrong value for tls statement"); + return 0; +} + +static char *typestr; + +static struct grecs_keyword ldapmap_kw[] = { + { "type", "arg", "Set map type", + grecs_type_string, GRECS_DFLT, NULL, 0, cb_null }, + { "uri", "arg", + "Set LDAP URI", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, uri) }, + { "base", "arg", + "Set search base", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, base) }, + { "binddn", "arg", + "Set bind user name", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, binddn) }, + { "bindpw", "arg", + "Set bind password", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, bindpw) }, + { "tls", "arg", + "TLS mode", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, tls), cb_tls }, + { "filter", "arg", + "Filter expression", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, filter) }, + { "attr", "arg", + "Attribute to return", + grecs_type_string, GRECS_DFLT, + NULL, offsetof(struct ldap_map, attr) }, + { "debug", "n", + "Set LDAP debug level", + grecs_type_int, GRECS_DFLT, + NULL, offsetof(struct ldap_map, dbg) }, + { "ldap-version", "n", + "LDAP version to use", + grecs_type_int, GRECS_DFLT, + NULL, offsetof(struct ldap_map, ldap_version) }, + { NULL } +}; + +static int +ldap_map_config(int dbg, struct grecs_node *node, void *data) +{ + int i; + struct ldap_map *ldap_map, **return_ldap_map = data; + + ldap_map = grecs_zalloc(sizeof(*ldap_map)); + ldap_map->ldap_version = 3; + for (i = 0; ldapmap_kw[i].ident; i++) + ldapmap_kw[i].varptr = ldap_map; + if (grecs_tree_process(node->down, ldapmap_kw)) + return eclat_map_failure; + + *return_ldap_map = ldap_map; + + return eclat_map_ok; +} + + +static void +ldap_map_free(int dbg, void *data) +{ + free(data); +} + +static void ldap_unbind(LDAP *ld); + +char * +parse_ldap_uri(const char *uri) +{ + struct wordsplit ws; + LDAPURLDesc *ludlist, **ludp; + struct grecs_txtacc *acc; + char *ldapuri = NULL; + int rc; + + rc = ldap_url_parse(uri, &ludlist); + if (rc != LDAP_URL_SUCCESS) { + err("cannot parse LDAP URL(s)=%s (%d)", uri, rc); + return NULL; + } + + acc = grecs_txtacc_create(); + + 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; + + if (ldap_dn2domain(lud->lud_dn, &domain) || + !domain) { + err("DNS SRV: cannot convert " + "DN=\"%s\" into a domain", + lud->lud_dn); + goto dnssrv_free0; + } + + rc = ldap_domain2hostlist(domain, &hostlist); + if (rc) { + err("DNS SRV: cannot convert " + "domain=%s into a hostlist", + domain); + goto dnssrv_free0; + } + + if (wordsplit(hostlist, &ws, WRDSF_DEFFLAGS)) { + err("DNS SRV: could not parse " + "hostlist=\"%s\": %s", + hostlist, wordsplit_strerror(&ws)); + goto dnssrv_free; + } + + for (i = 0; i < ws.ws_wordc; i++) { + grecs_txtacc_grow(acc, lud->lud_scheme, + strlen(lud->lud_scheme)); + grecs_txtacc_grow(acc, "//", 2); + grecs_txtacc_grow(acc, ws.ws_wordv[i], + strlen(ws.ws_wordv[i])); + } + + dnssrv_free0: + wordsplit_free(&ws); + dnssrv_free: + ber_memfree(hostlist); + ber_memfree(domain); + } else { + char *s = ldap_url_desc2str(lud); + grecs_txtacc_grow(acc, s, strlen(s)); + } + + *ludp = lud->lud_next; + + lud->lud_next = NULL; + ldap_free_urldesc(lud); + } + + if (ludlist) { + ldap_free_urldesc(ludlist); + return NULL; + } + + grecs_txtacc_grow_char(acc, 0); + ldapuri = grecs_txtacc_finish(acc, 1); + grecs_txtacc_free(acc); + + if (!ldapuri) + return NULL; + return ldapuri; +} + +static LDAP * +ldap_connect(int dbg, struct ldap_map *map) +{ + int rc; + char *ldapuri = NULL; + LDAP *ld = NULL; + int protocol = LDAP_VERSION3; + char *val; + unsigned long lval; + + if (map->dbg) { + if (ber_set_option(NULL, LBER_OPT_DEBUG_LEVEL, &map->dbg) + != LBER_OPT_SUCCESS ) + err("cannot set LBER_OPT_DEBUG_LEVEL %d", &map->dbg); + + if (ldap_set_option(NULL, LDAP_OPT_DEBUG_LEVEL, &map->dbg) + != LDAP_OPT_SUCCESS ) + err("could not set LDAP_OPT_DEBUG_LEVEL %d", map->dbg); + } + + if (map->uri) { + ldapuri = parse_ldap_uri(map->uri); + if (!ldapuri) + return NULL; + } + debug(dbg, 2, ("constructed LDAP URI: %s", + ldapuri ? ldapuri : "")); + + rc = ldap_initialize(&ld, ldapuri); + if (rc != LDAP_SUCCESS) { + err("cannot create LDAP session handle for " + "URI=%s (%d): %s", + ldapuri, rc, ldap_err2string(rc)); + free(ldapuri); + return NULL; + } + free(ldapuri); + + switch (map->ldap_version) { + case 2: + protocol = LDAP_VERSION2; + break; + case 3: + protocol = LDAP_VERSION3; + break; + default: + err("invalid LDAP version configured, defaulting to 3"); + } + + ldap_set_option(ld, LDAP_OPT_PROTOCOL_VERSION, &protocol); + + if (map->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); + err("ldap_start_tls failed: %s", ldap_err2string(rc)); + err("TLS diagnostics: %s", msg); + ldap_memfree(msg); + + if (map->tls == tls_only) { + ldap_unbind(ld); + return NULL; + } + /* try to continue anyway */ + } + } + + /* FIXME: Timeouts, SASL, etc. */ + return ld; +} + +static int +ldap_bind(int dbg, struct ldap_map *map) +{ + int msgid, ec, rc; + LDAPMessage *result; + LDAPControl **ctrls; + char msgbuf[256]; + char *matched = NULL; + char *info = NULL; + char **refs = NULL; + struct berval passwd; + char *binddn; + + msgbuf[0] = 0; + + if (map->bindpw) { + passwd.bv_len = strlen(map->bindpw); + passwd.bv_val = map->bindpw; + } else { + passwd.bv_len = 0; + passwd.bv_val = NULL; + } + rc = ldap_sasl_bind(map->ld, map->binddn, LDAP_SASL_SIMPLE, &passwd, + NULL, NULL, &msgid); + if (msgid == -1) { + err("ldap_sasl_bind(SIMPLE) failed: %s", ldap_err2string(rc)); + return 1; + } + + if (ldap_result(map->ld, msgid, LDAP_MSG_ALL, NULL, &result ) == -1) { + err("ldap_result failed"); + return 1; + } + + rc = ldap_parse_result(map->ld, result, &ec, &matched, &info, &refs, + &ctrls, 1); + if (rc != LDAP_SUCCESS) { + err("ldap_parse_result failed: %s", ldap_err2string(rc)); + return 1; + } + + if (ctrls) + ldap_controls_free(ctrls); + + if (ec != LDAP_SUCCESS + || msgbuf[0] + || (matched && matched[0]) + || (info && info[0]) + || refs) { + /* FIXME: Use debug output for that */ + debug(dbg, 2, ("ldap_bind: %s (%d)%s", + ldap_err2string(ec), ec, msgbuf)); + + if (matched && *matched) + debug(dbg, 2, ("matched DN: %s", matched)); + + if (info && *info) + debug(dbg, 2, ("additional info: %s", info)); + + if (refs && *refs) { + int i; + debug(dbg, 3, ("referrals:")); + for (i = 0; refs[i]; i++) + debug(dbg, 3, ("%s", refs[i])); + } + } + + if (matched) + ber_memfree(matched); + if (info) + ber_memfree(info); + if (refs) + ber_memvfree((void **)refs); + + return !(ec == LDAP_SUCCESS); +} + +static void +ldap_unbind(LDAP *ld) +{ + if (ld) { + ldap_set_option(ld, LDAP_OPT_SERVER_CONTROLS, NULL); + ldap_unbind_ext(ld, NULL, NULL); + } +} + +static int +ldap_map_open(int dbg, void *data) +{ + struct ldap_map *map = data; + + map->ld = ldap_connect(dbg, map); + if (!map->ld) + return eclat_map_failure; + if (ldap_bind(dbg, map)) + return eclat_map_failure; + return eclat_map_ok; +} + +static int +ldap_map_close(int dbg, void *data) +{ + struct ldap_map *map = data; + ldap_unbind(map->ld); + return 0; +} + +static void +trimnl(char *s) +{ + size_t len = strlen(s); + while (len > 0 && s[len-1] == '\n') + --len; + s[len] = 0; +} + +static int +keycmp(const void *a, const void *b) +{ + return strcmp(*(char**)a, *(char**)b); +} + +static char ** +get_ldap_attrs(LDAP *ld, LDAPMessage *msg, const char *attr) +{ + int rc, i, count; + BerElement *ber = NULL; + struct berval bv; + char *ufn = NULL; + char **ret; + struct berval **values; + + rc = ldap_get_dn_ber(ld, msg, &ber, &bv); + ufn = ldap_dn2ufn(bv.bv_val); + ldap_memfree(ufn); + + values = ldap_get_values_len(ld, msg, attr); + if (!values) { + err("LDAP attribute `%s' has NULL value", attr); + return NULL; + } + + for (count = 0; values[count]; count++) + ; + + ret = grecs_calloc(count + 1, sizeof(ret[0])); + + for (i = 0; values[i]; i++) { + char *p = grecs_malloc(values[i]->bv_len + 1); + memcpy(p, values[i]->bv_val, values[i]->bv_len); + p[values[i]->bv_len] = 0; + trimnl(p); + ret[i] = p; + } + + if (i < count) { + while (--i >= 0) + free(ret[i]); + free(ret); + ret = NULL; + } else { + ret[i] = NULL; + qsort(ret, i, sizeof(ret[0]), keycmp); + } + + ldap_value_free_len(values); + return ret; +} + +static int +ldap_map_get(int dbg, void *data, const char *key, char **return_value) +{ + struct ldap_map *map = data; + int rc, i; + LDAPMessage *res, *msg; + ber_int_t msgid; + char *attrs[2]; + char **ret; + const char *kve[3]; + struct wordsplit ws; + + kve[0] = "key"; + kve[1] = key; + kve[2] = NULL; + + ws.ws_env = kve; + + if (wordsplit(map->filter, &ws, + WRDSF_NOSPLIT | WRDSF_NOCMD | + WRDSF_ENV | WRDSF_ENV_KV)) + die(EX_SOFTWARE, "error expanding filter: %s", + wordsplit_strerror(&ws)); + + attrs[0] = (char*) map->attr; + attrs[1] = NULL; + rc = ldap_search_ext(map->ld, map->base, LDAP_SCOPE_SUBTREE, + ws.ws_wordv[0], attrs, 0, + NULL, NULL, NULL, -1, &msgid); + wordsplit_free(&ws); + + if (rc != LDAP_SUCCESS) { + err("ldap_search_ext: %s", ldap_err2string(rc)); + return eclat_map_failure; + } + + rc = ldap_result(map->ld, msgid, LDAP_MSG_ALL, NULL, &res); + if (rc < 0) { + err("ldap_result failed"); + return eclat_map_failure; + } + + msg = ldap_first_entry(map->ld, res); + if (!msg) { + ldap_msgfree(res); + return eclat_map_failure; + } + + ret = get_ldap_attrs(map->ld, msg, map->attr); + + ldap_msgfree(res); + + if (!ret || !ret[0]) + return eclat_map_not_found; + + *return_value = ret[0]; + + for (i = 1; ret[i]; i++) + free(ret[i]); + free(ret); + return eclat_map_ok; +} + +struct eclat_map_drv eclat_map_drv_ldap = { + "ldap", + ldap_map_config, + ldap_map_open, + ldap_map_close, + ldap_map_get, + ldap_map_free +}; + diff --git a/lib/libeclat.h b/lib/libeclat.h index ddf4db9..2f4c32c 100644 --- a/lib/libeclat.h +++ b/lib/libeclat.h @@ -145,5 +145,5 @@ int eclat_get_string_node(struct grecs_node *node, const char *name, extern struct eclat_map_drv eclat_map_drv_file; extern struct eclat_map_drv eclat_map_drv_gdbm; - +extern struct eclat_map_drv eclat_map_drv_ldap; diff --git a/src/eclat.c b/src/eclat.c index f8bbaa5..ab3375a 100644 --- a/src/eclat.c +++ b/src/eclat.c @@ -596,6 +596,9 @@ main(int argc, char **argv) eclat_map_drv_register(&eclat_map_drv_file); #ifdef WITH_GDBM eclat_map_drv_register(&eclat_map_drv_gdbm); +#endif +#ifdef WITH_LDAP + eclat_map_drv_register(&eclat_map_drv_ldap); #endif config_init(); parse_options(argc, argv, &index); -- cgit v1.2.1