]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
lib-dict: Add ldap driver
authorAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 11 Apr 2016 06:40:22 +0000 (09:40 +0300)
committerAki Tuomi <aki.tuomi@dovecot.fi>
Mon, 11 Apr 2016 07:51:37 +0000 (10:51 +0300)
src/lib-dict/Makefile.am
src/lib-dict/dict-ldap-settings.c [new file with mode: 0644]
src/lib-dict/dict-ldap-settings.h [new file with mode: 0644]
src/lib-dict/dict-ldap.c [new file with mode: 0644]

index 9525164a2c407a40cd188f383ef0e24745485f1b..ddd4a130ce8ae9d8cb702ce190caa70e27493ca5 100644 (file)
@@ -7,6 +7,7 @@ AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib \
        -I$(top_srcdir)/src/lib-test \
        -I$(top_srcdir)/src/lib-fs \
+       -I$(top_srcdir)/src/lib-ldap \
        -I$(top_srcdir)/src/lib-sql \
        -I$(top_srcdir)/src/lib-settings \
        $(SQL_CFLAGS)
@@ -34,9 +35,21 @@ libdict_backend_a_SOURCES = \
 nodist_libdict_backend_a_SOURCES = \
        dict-drivers-register.c
 
+NOPLUGIN_LDFLAGS =
+libdict_ldap_la_LDFLAGS = -module -avoid-version $(LIBDOVECOT_LDAP)
+
+module_dictdir = $(moduledir)/dict
+module_dict_LTLIBRARIES = \
+       libdict_ldap.la
+
+libdict_ldap_la_SOURCES = \
+       dict-ldap.c \
+       dict-ldap-settings.c
+
 headers = \
        dict.h \
        dict-client.h \
+       dict-ldap-settings.h \
        dict-private.h \
        dict-sql.h \
        dict-sql-settings.h \
diff --git a/src/lib-dict/dict-ldap-settings.c b/src/lib-dict/dict-ldap-settings.c
new file mode 100644 (file)
index 0000000..2d49817
--- /dev/null
@@ -0,0 +1,291 @@
+/* Copyright (c) 2008-2016 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "array.h"
+#include "str.h"
+#include "settings.h"
+#include "dict-ldap-settings.h"
+
+#include <ctype.h>
+
+static const char *dict_ldap_commonName = "cn";
+static const char *dict_ldap_empty_filter = "";
+
+enum section_type {
+       SECTION_ROOT = 0,
+       SECTION_MAP,
+       SECTION_FIELDS
+};
+
+struct dict_ldap_map_attribute {
+       const char *name;
+       const char *variable;
+};
+
+struct setting_parser_ctx {
+       pool_t pool;
+       struct dict_ldap_settings *set;
+       enum section_type type;
+
+       struct dict_ldap_map cur_map;
+       ARRAY(struct dict_ldap_map_attribute) cur_attributes;
+};
+
+#undef DEF_STR
+#undef DEF_BOOL
+#undef DEF_UINT
+
+#define DEF_STR(name) DEF_STRUCT_STR(name, dict_ldap_map)
+#define DEF_BOOL(name) DEF_STRUCT_BOOL(name, dict_ldap_map)
+#define DEF_UINT(name) DEF_STRUCT_UINT(name ,dict_ldap_map)
+
+static const struct setting_def dict_ldap_map_setting_defs[] = {
+       DEF_STR(pattern),
+       DEF_STR(filter),
+       DEF_STR(filter_iter),
+       DEF_STR(username_attribute),
+       DEF_STR(value_attribute),
+       DEF_STR(base_dn),
+       DEF_STR(scope),
+       { 0, NULL, 0 }
+};
+
+static const char *pattern_read_name(const char **pattern)
+{
+       const char *p = *pattern, *name;
+
+       if (*p == '{') {
+               /* ${name} */
+               name = ++p;
+               p = strchr(p, '}');
+               if (p == NULL) {
+                       /* error, but allow anyway */
+                       *pattern += strlen(*pattern);
+                       return "";
+               }
+               *pattern = p + 1;
+       } else {
+               /* $name - ends at the first non-alnum_ character */
+               name = p;
+               for (; *p != '\0'; p++) {
+                       if (!i_isalnum(*p) && *p != '_')
+                               break;
+               }
+               *pattern = p;
+       }
+       name = t_strdup_until(name, p);
+       return name;
+}
+
+static const char *dict_ldap_attributes_map(struct setting_parser_ctx *ctx)
+{
+       struct dict_ldap_map_attribute *attributes;
+       string_t *pattern;
+       const char *p, *name;
+       unsigned int i, count;
+
+       /* go through the variables in the pattern, replace them with plain
+          '$' character and add its ldap attribute */
+       pattern = t_str_new(strlen(ctx->cur_map.pattern) + 1);
+       attributes = array_get_modifiable(&ctx->cur_attributes, &count);
+
+       p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, count);
+       for (p = ctx->cur_map.pattern; *p != '\0';) {
+               if (*p != '$') {
+                       str_append_c(pattern, *p);
+                       p++;
+                       continue;
+               }
+               p++;
+               str_append_c(pattern, '$');
+
+               name = pattern_read_name(&p);
+               for (i = 0; i < count; i++) {
+                       if (attributes[i].variable != NULL &&
+                           strcmp(attributes[i].variable, name) == 0)
+                               break;
+               }
+               if (i == count) {
+                       return t_strconcat("Missing LDAP attribute for variable: ",
+                                          name, NULL);
+               }
+
+               /* mark this attribute as used */
+               attributes[i].variable = NULL;
+               array_append(&ctx->cur_map.ldap_attributes,
+                            &attributes[i].name, 1);
+       }
+
+       /* make sure there aren't any unused attributes */
+       for (i = 0; i < count; i++) {
+               if (attributes[i].variable != NULL) {
+                       return t_strconcat("Unused variable: ",
+                                          attributes[i].variable, NULL);
+               }
+       }
+
+       if (ctx->set->max_attribute_count < count)
+               ctx->set->max_attribute_count = count;
+       ctx->cur_map.pattern = p_strdup(ctx->pool, str_c(pattern));
+       return NULL;
+}
+
+static const char *dict_ldap_map_finish(struct setting_parser_ctx *ctx)
+{
+       if (ctx->cur_map.pattern == NULL)
+               return "Missing setting: pattern";
+       if (ctx->cur_map.filter == NULL)
+               ctx->cur_map.filter = dict_ldap_empty_filter;
+       if (*ctx->cur_map.filter != '\0') {
+               const char *ptr = ctx->cur_map.filter;
+               if (*ptr != '(')
+                       return "Filter must start with (";
+               while(*ptr != '\0') ptr++;
+               ptr--;
+               if (*ptr != ')')
+                       return "Filter must end with )";
+       }
+       if (ctx->cur_map.value_attribute == NULL)
+               return "Missing setting: value_attribute";
+
+       if (ctx->cur_map.username_attribute == NULL) {
+               /* default to commonName */
+               ctx->cur_map.username_attribute = dict_ldap_commonName;
+       }
+       if (ctx->cur_map.scope == NULL) {
+               ctx->cur_map.scope_val = 2; /* subtree */
+       } else {
+               if (!strcasecmp(ctx->cur_map.scope, "one")) ctx->cur_map.scope_val = 1;
+               else if (!strcasecmp(ctx->cur_map.scope, "base")) ctx->cur_map.scope_val = 0;
+               else if (!strcasecmp(ctx->cur_map.scope, "subtree")) ctx->cur_map.scope_val = 2;
+               else return "Scope must be one, base or subtree";
+       }
+       if (!array_is_created(&ctx->cur_map.ldap_attributes)) {
+               /* no attributes besides value. allocate the array anyway. */
+               p_array_init(&ctx->cur_map.ldap_attributes, ctx->pool, 1);
+               if (strchr(ctx->cur_map.pattern, '$') != NULL)
+                       return "Missing attributes for pattern variables";
+       }
+       array_append(&ctx->set->maps, &ctx->cur_map, 1);
+       memset(&ctx->cur_map, 0, sizeof(ctx->cur_map));
+       return NULL;
+}
+
+static const char *
+parse_setting(const char *key, const char *value,
+             struct setting_parser_ctx *ctx)
+{
+       struct dict_ldap_map_attribute *attribute;
+
+       switch (ctx->type) {
+       case SECTION_ROOT:
+               if (strcmp(key, "uri") == 0) {
+                       ctx->set->uri = p_strdup(ctx->pool, value);
+                       return NULL;
+               }
+               if (strcmp(key, "bind_dn") == 0) {
+                       ctx->set->bind_dn = p_strdup(ctx->pool, value);
+                       return NULL;
+               }
+               if (strcmp(key, "password") == 0) {
+                       ctx->set->password = p_strdup(ctx->pool, value);
+                       return NULL;
+               }
+               if (strcmp(key, "timeout") == 0) {
+                       if (str_to_uint(value, &ctx->set->timeout) != 0) {
+                               return "Invalid timeout value";
+                       }
+                       return NULL;
+               }
+               if (strcmp(key, "max_idle_time") == 0) {
+                       if (str_to_uint(value, &ctx->set->max_idle_time) != 0) {
+                               return "Invalid max_idle_time value";
+                       }
+                       return NULL;
+               }
+               if (strcmp(key, "debug") == 0) {
+                       if (str_to_uint(value, &ctx->set->debug) != 0) {
+                               return "invalid debug value";
+                       }
+                       return NULL;
+               }
+               break;
+       case SECTION_MAP:
+               return parse_setting_from_defs(ctx->pool,
+                                              dict_ldap_map_setting_defs,
+                                              &ctx->cur_map, key, value);
+       case SECTION_FIELDS:
+               if (*value != '$') {
+                       return t_strconcat("Value is missing '$' for attribute: ",
+                                          key, NULL);
+               }
+               attribute = array_append_space(&ctx->cur_attributes);
+               attribute->name = p_strdup(ctx->pool, key);
+               attribute->variable = p_strdup(ctx->pool, value + 1);
+               return NULL;
+       }
+       return t_strconcat("Unknown setting: ", key, NULL);
+}
+
+static bool
+parse_section(const char *type, const char *name ATTR_UNUSED,
+             struct setting_parser_ctx *ctx, const char **error_r)
+{
+       switch (ctx->type) {
+       case SECTION_ROOT:
+               if (type == NULL)
+                       return FALSE;
+               if (strcmp(type, "map") == 0) {
+                       array_clear(&ctx->cur_attributes);
+                       ctx->type = SECTION_MAP;
+                       return TRUE;
+               }
+               break;
+       case SECTION_MAP:
+               if (type == NULL) {
+                       ctx->type = SECTION_ROOT;
+                       *error_r = dict_ldap_map_finish(ctx);
+                       return FALSE;
+               }
+               if (strcmp(type, "fields") == 0) {
+                       ctx->type = SECTION_FIELDS;
+                       return TRUE;
+               }
+               break;
+       case SECTION_FIELDS:
+               if (type == NULL) {
+                       ctx->type = SECTION_MAP;
+                       *error_r = dict_ldap_attributes_map(ctx);
+                       return FALSE;
+               }
+               break;
+       }
+       *error_r = t_strconcat("Unknown section: ", type, NULL);
+       return FALSE;
+}
+
+struct dict_ldap_settings *
+dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r)
+{
+       struct setting_parser_ctx ctx;
+
+       memset(&ctx, 0, sizeof(ctx));
+       ctx.pool = pool;
+       ctx.set = p_new(pool, struct dict_ldap_settings, 1);
+       t_array_init(&ctx.cur_attributes, 16);
+       p_array_init(&ctx.set->maps, pool, 8);
+
+       ctx.set->timeout = 30; /* default timeout */
+
+       if (!settings_read(path, NULL, parse_setting, parse_section,
+                          &ctx, error_r))
+               return NULL;
+
+       if (ctx.set->uri == NULL) {
+               *error_r = t_strdup_printf("Error in configuration file %s: "
+                                          "Missing ldap uri", path);
+               return NULL;
+       }
+
+       return ctx.set;
+}
diff --git a/src/lib-dict/dict-ldap-settings.h b/src/lib-dict/dict-ldap-settings.h
new file mode 100644 (file)
index 0000000..a56b300
--- /dev/null
@@ -0,0 +1,34 @@
+#ifndef DICT_LDAP_SETTINGS_H
+#define DICT_LDAP_SETTINGS_H
+
+struct dict_ldap_map {
+       /* pattern is in simplified form: all variables are stored as simple
+          '$' character. fields array is sorted by the variable index. */
+       const char *pattern;
+       const char *filter;
+       const char *filter_iter;
+       const char *username_attribute;
+       const char *value_attribute;
+       const char *base_dn;
+       const char *scope;
+       int scope_val;
+       unsigned int timeout;
+
+       ARRAY_TYPE(const_string) ldap_attributes;
+};
+
+struct dict_ldap_settings {
+       const char *uri;
+       const char *bind_dn;
+       const char *password;
+       unsigned int timeout;
+       unsigned int max_idle_time;
+       unsigned int debug;
+       unsigned int max_attribute_count;
+       ARRAY(struct dict_ldap_map) maps;
+};
+
+struct dict_ldap_settings *
+dict_ldap_settings_read(pool_t pool, const char *path, const char **error_r);
+
+#endif
diff --git a/src/lib-dict/dict-ldap.c b/src/lib-dict/dict-ldap.c
new file mode 100644 (file)
index 0000000..98d7762
--- /dev/null
@@ -0,0 +1,433 @@
+/* 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 "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 *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;
+};
+
+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 = dict->set->max_idle_time;
+       set.debug = dict->set->debug;
+       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_init(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->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;
+
+       return 0;
+}
+
+static
+void ldap_dict_deinit(struct dict *dict) {
+       struct ldap_dict *ctx = (struct ldap_dict *)dict;
+       ldap_client_deinit(&(ctx->client));
+}
+
+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) {
+                       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) {
+                               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 {
+                               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 = {0};
+       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 */
+                       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);
+}
+
+void dict_ldap_deinit(void)
+{
+       dict_driver_unregister(&dict_driver_ldap);
+}
+
+const char *dict_ldap_plugin_dependencies[] = { NULL };