/* This file is part of Eclat.
Copyright (C) 2012-2018 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
#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
};