/* This file is part of Eclat. Copyright (C) 2012, 2013 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 #include #include enum { tls_no, tls_yes, tls_only }; struct ldap_map { unsigned long ldap_version; char *uri; char *base; char *binddn; char *bindpw; char *passfile; int tls; int prompt; char *filter[2]; char *attr[2]; int dbg; LDAP *ld; }; static void ldap_map_free(int dbg, void *data); 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 struct grecs_keyword ldapmap_kw[] = { { "type", "'ldap", "Set map type", grecs_type_null }, { "key", "", "key expression", grecs_type_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) }, { "passfile", "filename", "Read bind password from ", grecs_type_string, GRECS_DFLT, NULL, offsetof(struct ldap_map, passfile) }, { "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[0]) }, { "reverse-filter", "arg", "Reverse filter expression", grecs_type_string, GRECS_DFLT, NULL, offsetof(struct ldap_map, filter[1]) }, { "attr", "arg", "Attribute to return", grecs_type_string, GRECS_DFLT, NULL, offsetof(struct ldap_map, attr[0]) }, { "reverse-attr", "arg", "Attribute to return from reverse lookup", grecs_type_string, GRECS_DFLT, NULL, offsetof(struct ldap_map, attr[1]) }, { "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) }, { "prompt", NULL, "Prompt for missing credentials", grecs_type_bool, GRECS_DFLT, NULL, offsetof(struct ldap_map, prompt) }, { NULL } }; static int ldap_map_config(int dbg, struct grecs_node *node, void *data) { int i; int rc = 0; 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; if (!ldap_map->uri) { grecs_error(&node->locus, 0, "LDAP URI not supplied"); rc = 1; } if (!ldap_map->filter[0] && !ldap_map->filter[1]) { grecs_error(&node->locus, 0, "LDAP filter not supplied"); rc = 1; } if (ldap_map->filter[0] && !ldap_map->attr[0]) { grecs_error(&node->locus, 0, "LDAP attr not supplied"); rc = 1; } if (ldap_map->filter[1] && !ldap_map->attr[1]) { grecs_error(&node->locus, 0, "LDAP reverse-attr not supplied"); rc = 1; } if (rc) { ldap_map_free(dbg, ldap_map); return eclat_map_failure; } *return_ldap_map = ldap_map; return eclat_map_ok; } static void ldap_map_confhelp() { static struct grecs_keyword ldapmap_top[] = { { "map", "name: string", "Configuration for an LDAP map", grecs_type_section, GRECS_INAC, NULL, 0, NULL, NULL, ldapmap_kw }, { NULL } }; grecs_print_statement_array(ldapmap_top, 1, 0, stdout); } static void ldap_map_free(int dbg, void *data) { struct ldap_map *map = data; free(map->uri); free(map->base); free(map->binddn); free(map->bindpw); free(map->filter[0]); free(map->filter[1]); free(map->attr[0]); free(map->attr[1]); free(data); } static void ldap_unbind(LDAP *ld); static void dns2txtacc(struct grecs_txtacc *acc, LDAPURLDesc *lud) { size_t i; char *domain, *hostlist; struct wordsplit ws; int rc; if (ldap_dn2domain(lud->lud_dn, &domain) || !domain) { err("DNS SRV: cannot convert DN=\"%s\" into a domain", lud->lud_dn); return; } rc = ldap_domain2hostlist(domain, &hostlist); ber_memfree(domain); if (rc) { err("DNS SRV: cannot convert domain=%s into a hostlist", domain); return; } rc = wordsplit(hostlist, &ws, WRDSF_DEFFLAGS); ber_memfree(hostlist); if (rc) { err("DNS SRV: could not parse hostlist=\"%s\": %s", hostlist, wordsplit_strerror(&ws)); return; } 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])); } wordsplit_free(&ws); } char * parse_ldap_uri(const char *uri) { 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; 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 */ dns2txtacc(acc, lud); } 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; 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; 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; char *user = NULL, *p; uid_t uid = getuid(); struct passwd *pw; const char *kw[3]; pw = getpwuid(uid); if (!pw) die(EX_UNAVAILABLE, "cannot determine user name (uid=%lu)", (unsigned long) uid); user = grecs_strdup(pw->pw_name); kw[0] = NULL; if (map->prompt && isatty(0)) { p = eclat_getans("User name", user, 0); free(user); user = p; } if (map->binddn) { kw[0] = "user"; kw[1] = user; kw[2] = NULL; p = eclat_expand_kw(map->binddn, kw); free(map->binddn); map->binddn = p; } else map->binddn = grecs_strdup(user); if (map->passfile) { char *filename = eclat_expand_kw(map->passfile, kw); FILE *fp = fopen(filename, "r"); if (!fp) { err("cannot open password file %s: %s", filename, strerror(errno)); free(filename); return eclat_map_failure; } else { char *buf = NULL; size_t size = 0; int rc = grecs_getline(&buf, &size, fp); if (rc <= 0) err("error reading password file %s: %s", filename, rc == 0 ? "empty file?" : strerror(errno)); else map->bindpw = buf; } fclose(fp); free(filename); } if (!map->bindpw && map->prompt && isatty(0)) map->bindpw = eclat_getans("User password", NULL, 1); free(user); 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 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; eclat_trimnl(p); ret[i] = p; } ret[i] = NULL; qsort(ret, i, sizeof(ret[0]), keycmp); ldap_value_free_len(values); return ret; } static int ldap_map_get(int dbg, int dir, 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 *kwe[3]; char *filter; if (!map->filter[dir]) return eclat_map_bad_dir; kwe[0] = "key"; kwe[1] = key; kwe[2] = NULL; filter = eclat_expand_kw(map->filter[dir], kwe); attrs[0] = (char*) map->attr[dir]; attrs[1] = NULL; rc = ldap_search_ext(map->ld, map->base, LDAP_SCOPE_SUBTREE, filter, attrs, 0, NULL, NULL, NULL, -1, &msgid); free(filter); 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_not_found; } ret = get_ldap_attrs(map->ld, msg, map->attr[dir]); 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, ldap_map_confhelp };