view src/lib-dict-extra/dict-ldap.c @ 20066:f215c409c86d

lib-dict: Moved dict-ldap to lib-dict-extra This also allows moving lib-ldap away from LIBDOVECOT_SUBDIRS in src/Makefile.am, which was wrong because it's not really part of libdovecot.la.
author Timo Sirainen <timo.sirainen@dovecot.fi>
date Thu, 21 Apr 2016 18:58:10 +0300
parents src/lib-dict/dict-ldap.c@0ef427d75bab
children af1d619d2f55
line wrap: on
line source

/* Copyright (c) 2016 Dovecot authors, see the included COPYING memcached */

#include "lib.h"
#include "array.h"
#include "module-dir.h"
#include "str.h"
#include "istream.h"
#include "ostream.h"
#include "var-expand.h"
#include "connection.h"
#include "llist.h"
#include "ldap-client.h"
#include "dict.h"
#include "dict-private.h"
#include "dict-ldap-settings.h"

struct ldap_dict;

struct dict_ldap_op {
	struct ldap_dict *dict;
	const struct dict_ldap_map *map;
	pool_t pool;
	unsigned long txid;
	struct dict_lookup_result res;
	dict_lookup_callback_t *callback;
	void *callback_ctx;
};

struct ldap_dict {
	struct dict dict;
	struct dict_ldap_settings *set;

	const char *uri;
	const char *username;
	const char *base_dn;
	enum ldap_scope scope;

	pool_t pool;

	struct ldap_client *client;
	struct ioloop *ioloop, *prev_ioloop;

	unsigned long last_txid;
	unsigned int pending;

	struct ldap_dict *prev,*next;
};

static
struct ldap_dict *ldap_dict_list;

static
void ldap_dict_lookup_async(struct dict *dict, const char *key,
			     dict_lookup_callback_t *callback, void *context);


static bool
dict_ldap_map_match(const struct dict_ldap_map *map, const char *path,
		   ARRAY_TYPE(const_string) *values, unsigned int *pat_len_r,
		   unsigned int *path_len_r, bool partial_ok, bool recurse)
{
	const char *path_start = path;
	const char *pat, *attribute, *p;
	unsigned int len;

	array_clear(values);
	pat = map->pattern;
	while (*pat != '\0' && *path != '\0') {
		if (*pat == '$') {
			/* variable */
			pat++;
			if (*pat == '\0') {
				/* pattern ended with this variable,
				   it'll match the rest of the path */
				len = strlen(path);
				if (partial_ok) {
					/* iterating - the last field never
					   matches fully. if there's a trailing
					   '/', drop it. */
					pat--;
					if (path[len-1] == '/') {
						attribute = t_strndup(path, len-1);
						array_append(values, &attribute, 1);
					} else {
						array_append(values, &path, 1);
					}
				} else {
					array_append(values, &path, 1);
					path += len;
				}
				*path_len_r = path - path_start;
				*pat_len_r = pat - map->pattern;
				return TRUE;
			}
			/* pattern matches until the next '/' in path */
			p = strchr(path, '/');
			if (p != NULL) {
				attribute = t_strdup_until(path, p);
				array_append(values, &attribute, 1);
				path = p;
			} else {
				/* no '/' anymore, but it'll still match a
				   partial */
				array_append(values, &path, 1);
				path += strlen(path);
				pat++;
			}
		} else if (*pat == *path) {
			pat++;
			path++;
		} else {
			return FALSE;
		}
	}

	*path_len_r = path - path_start;
	*pat_len_r = pat - map->pattern;

	if (*pat == '\0')
		return *path == '\0';
	else if (!partial_ok)
		return FALSE;
	else {
		/* partial matches must end with '/'. */
		if (pat != map->pattern && pat[-1] != '/')
			return FALSE;
		/* if we're not recursing, there should be only one $variable
		   left. */
		if (recurse)
			return TRUE;
		return pat[0] == '$' && strchr(pat, '/') == NULL;
	}
}

static const struct dict_ldap_map *
ldap_dict_find_map(struct ldap_dict *dict, const char *path,
		  ARRAY_TYPE(const_string) *values)
{
	const struct dict_ldap_map *maps;
	unsigned int i, count, len;

	t_array_init(values, dict->set->max_attribute_count);
	maps = array_get(&dict->set->maps, &count);
	for (i = 0; i < count; i++) {
		if (dict_ldap_map_match(&maps[i], path, values,
				       &len, &len, FALSE, FALSE))
			return &maps[i];
	}
	return NULL;
}

static
int dict_ldap_connect(struct ldap_dict *dict, const char **error_r)
{
	struct ldap_client_settings set;
	memset(&set, 0, sizeof(set));
	set.uri = dict->set->uri;
	set.bind_dn = dict->set->bind_dn;
	set.password = dict->set->password;
	set.timeout_secs = dict->set->timeout;
	set.max_idle_time_secs = dict->set->max_idle_time;
	set.debug = dict->set->debug;
	set.require_ssl = dict->set->require_ssl;
	set.start_tls = dict->set->start_tls;
	return ldap_client_init(&set, &dict->client, error_r);
}

static
const char* ldap_dict_build_query(struct ldap_dict *dict, const struct dict_ldap_map *map, ARRAY_TYPE(const_string) *values, bool priv)
{
	const char *template;
	ARRAY(struct var_expand_table) exp;
	struct var_expand_table entry;
	string_t *query = t_str_new(64);

	t_array_init(&exp, 8);
	entry.key = '\0';
	entry.value = dict->username;
	entry.long_key = "username";
	array_append(&exp, &entry, 1);

	if (priv) {
		template = t_strdup_printf("(&(%s=%s)%s)", map->username_attribute, "%{username}", map->filter);
	} else {
		template = map->filter;
	}

	for(size_t i = 0; i < array_count(values) && i < array_count(&(map->ldap_attributes)); i++) {
		struct var_expand_table entry;
		entry.value = *array_idx(values, i);
		entry.long_key = *array_idx(&(map->ldap_attributes), i);
		array_append(&exp, &entry, 1);
	}

	array_append_zero(&exp);

	var_expand(query, template, array_idx(&exp, 0));

	return str_c(query);
}

static
int ldap_dict_create(struct dict *dict_driver, const char *uri,
		     const struct dict_settings *set,
		     struct dict **dict_r, const char **error_r)
{
	pool_t pool = pool_alloconly_create("ldap dict", 2048);
	struct ldap_dict *dict = p_new(pool, struct ldap_dict, 1);
	dict->pool = pool;
	dict->dict = *dict_driver;
	dict->username = p_strdup(pool, set->username);
	dict->uri = p_strdup(pool, uri);
	dict->set = dict_ldap_settings_read(pool, uri, error_r);

	if (dict->set == NULL) {
		pool_unref(&pool);
		return -1;
	}

	if (dict_ldap_connect(dict, error_r) < 0) {
		pool_unref(&pool);
		return -1;
	}

	*dict_r = (struct dict*)dict;
	*error_r = NULL;

	DLLIST_PREPEND(&ldap_dict_list, dict);

	return 0;
}

static
int ldap_dict_init(struct dict *dict_driver, const char *uri,
		   const struct dict_settings *set,
		   struct dict **dict_r, const char **error_r)
{
	/* reuse possible existing entry */
	for(struct ldap_dict *ptr = ldap_dict_list;
	    ptr != NULL;
	    ptr = ptr->next) {
		if (strcmp(ptr->uri, uri) == 0) {
			*dict_r = (struct dict*)ptr;
			return 0;
		}
	}
	return ldap_dict_create(dict_driver, uri, set, dict_r, error_r);
}

static
void ldap_dict_deinit(struct dict *dict ATTR_UNUSED) {
}

static
int ldap_dict_wait(struct dict *dict) {
	struct ldap_dict *ctx = (struct ldap_dict *)dict;

	i_assert(ctx->ioloop == NULL);

	ctx->prev_ioloop = current_ioloop;
	ctx->ioloop = io_loop_create();
	ldap_client_switch_ioloop(ctx->client);

	do {
		io_loop_run(current_ioloop);
	} while (ctx->pending > 0);

	io_loop_set_current(ctx->prev_ioloop);
	ldap_client_switch_ioloop(ctx->client);
	io_loop_set_current(ctx->ioloop);
	io_loop_destroy(&ctx->ioloop);
	ctx->prev_ioloop = NULL;

	return 0;
}

static
void ldap_dict_lookup_done(const struct dict_lookup_result *result, void *ctx)
{
	struct dict_lookup_result *res = ctx;
	*res = *result;
}

static void
ldap_dict_lookup_callback(struct ldap_result *result, struct dict_ldap_op *op)
{
	pool_t pool = op->pool;
	struct ldap_search_iterator *iter;
	const struct ldap_entry *entry;

	op->dict->pending--;

	if (ldap_result_has_failed(result)) {
		op->res.ret = -1;
		op->res.error = ldap_result_get_error(result);
	} else {
		iter = ldap_search_iterator_init(result);
		entry = ldap_search_iterator_next(iter);
		if (entry != NULL) {
			if (op->dict->set->debug > 0)
				i_debug("ldap_dict_lookup_callback got dn %s", ldap_entry_dn(entry));
			/* try extract value */
			const char *const *values = ldap_entry_get_attribute(entry, op->map->value_attribute);
			if (values != NULL) {
				if (op->dict->set->debug > 0)
					i_debug("ldap_dict_lookup_callback got attribute %s", op->map->value_attribute);
				op->res.ret = 1;
				op->res.value = p_strdup(op->pool, values[0]);
			} else {
				if (op->dict->set->debug > 0)
					i_debug("ldap_dict_lookup_callback dit not get attribute %s", op->map->value_attribute);
				op->res.value = NULL;
			}
		}
		ldap_search_iterator_deinit(&iter);
	}
	op->callback(&(op->res), op->callback_ctx);
	pool_unref(&pool);
}

static
int ldap_dict_lookup(struct dict *dict, pool_t pool,
		      const char *key, const char **value_r)
{
	struct dict_lookup_result res;
	pool_t orig_pool = pool;
	int ret;

	T_BEGIN {
		ldap_dict_lookup_async(dict, key, ldap_dict_lookup_done, &res);

		if ((ret = ldap_dict_wait(dict)) == 0) {
			if (res.ret == 0) {
				*value_r = p_strdup(orig_pool, res.value);
			} else ret = res.ret;
		}
	} T_END;
	return ret;
}

/*
static
struct dict_iterate_context *ldap_dict_iterate_init(struct dict *dict,
				const char *const *paths,
				enum dict_iterate_flags flags)
{
	return NULL;
}

static
bool ldap_dict_iterate(struct dict_iterate_context *ctx,
			const char **key_r, const char **value_r)
{
	return FALSE;
}

static
int ldap_dict_iterate_deinit(struct dict_iterate_context *ctx)
{
	return -1;
}

static
struct dict_transaction_context ldap_dict_transaction_init(struct dict *dict);

static
int ldap_dict_transaction_commit(struct dict_transaction_context *ctx,
				  bool async,
				  dict_transaction_commit_callback_t *callback,
				  void *context);
static
void ldap_dict_transaction_rollback(struct dict_transaction_context *ctx);

static
void ldap_dict_set(struct dict_transaction_context *ctx,
		    const char *key, const char *value);
static
void ldap_dict_unset(struct dict_transaction_context *ctx,
		      const char *key);
static
void ldap_dict_append(struct dict_transaction_context *ctx,
		       const char *key, const char *value);
static
void ldap_dict_atomic_inc(struct dict_transaction_context *ctx,
			   const char *key, long long diff);
*/

static
void ldap_dict_lookup_async(struct dict *dict, const char *key,
			     dict_lookup_callback_t *callback, void *context)
{
	struct ldap_search_input input;
	struct ldap_dict *ctx = (struct ldap_dict*)dict;
	struct dict_ldap_op *op;
	pool_t oppool = pool_alloconly_create("ldap dict lookup", 64);
	op = p_new(oppool, struct dict_ldap_op, 1);
	op->pool = oppool;
	op->dict = ctx;
	op->callback = callback;
	op->callback_ctx = context;
	op->txid = ctx->last_txid++;

	/* key needs to be transformed into something else */
	ARRAY_TYPE(const_string) values;
	T_BEGIN {
		const char *attributes[2] = {0, 0};
		t_array_init(&values, 8);
		const struct dict_ldap_map *map = ldap_dict_find_map(ctx, key, &values);

		if (map != NULL) {
			op->map = map;
			attributes[0] = map->value_attribute;
			/* build lookup */
			memset(&input, 0, sizeof(input));
			input.base_dn = map->base_dn;
			input.scope = map->scope_val;
			input.filter = ldap_dict_build_query(ctx, map, &values, strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE))==0);
			input.attributes = attributes;
			input.timeout_secs = ctx->set->timeout;
			ctx->pending++;
			ldap_search_start(ctx->client, &input, ldap_dict_lookup_callback, op);
		} else {
			op->res.error = "no such key";
			callback(&(op->res), context);
			pool_unref(&oppool);
		}
	} T_END;
}

struct dict dict_driver_ldap = {
	.name = "ldap",
	{
		ldap_dict_init,
		ldap_dict_deinit,
		ldap_dict_wait,
		ldap_dict_lookup,
		NULL, /*ldap_dict_iterate_init,*/
		NULL, /*ldap_dict_iterate,*/
		NULL, /*ldap_dict_iterate_deinit,*/
		NULL, /*ldap_transaction_init,*/
		NULL, /*ldap_transaction_commit,*/
		NULL, /*ldap_transaction_rollback,*/
		NULL, /*ldap_set,*/
		NULL, /*ldap_unset,*/
		NULL, /*ldap_append,*/
		NULL, /*ldap_atomic_inc,*/
		ldap_dict_lookup_async
	}
};

void dict_ldap_init(struct module *module ATTR_UNUSED);
void dict_ldap_deinit(void);

void dict_ldap_init(struct module *module ATTR_UNUSED)
{
	dict_driver_register(&dict_driver_ldap);
	ldap_dict_list = NULL;
}

void dict_ldap_deinit(void)
{
	dict_driver_unregister(&dict_driver_ldap);
	/* destroy all server connections */
	struct ldap_dict *ptr = ldap_dict_list;
	ldap_dict_list = NULL;

	while(ptr != NULL) {
		ldap_client_deinit(&(ptr->client));
		pool_t pool = ptr->pool;
		ptr = ptr->next;
		pool_unref(&pool);
	}
}

const char *dict_ldap_plugin_dependencies[] = { NULL };