summaryrefslogtreecommitdiffstats
path: root/src/lib-dict-backend/dict-ldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib-dict-backend/dict-ldap.c')
-rw-r--r--src/lib-dict-backend/dict-ldap.c500
1 files changed, 500 insertions, 0 deletions
diff --git a/src/lib-dict-backend/dict-ldap.c b/src/lib-dict-backend/dict-ldap.c
new file mode 100644
index 0000000..433871b
--- /dev/null
+++ b/src/lib-dict-backend/dict-ldap.c
@@ -0,0 +1,500 @@
+/* Copyright (c) 2016-2018 Dovecot authors, see the included COPYING memcached */
+
+#include "lib.h"
+
+#if defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)
+
+#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"
+
+static const char *LDAP_ESCAPE_CHARS = "*,\\#+<>;\"()= ";
+
+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 *base_dn;
+ enum ldap_scope scope;
+
+ pool_t pool;
+
+ struct ldap_client *client;
+
+ unsigned long last_txid;
+ unsigned int pending;
+
+ struct ldap_dict *prev,*next;
+};
+
+static
+void ldap_dict_lookup_async(struct dict *dict,
+ const struct dict_op_settings *set, 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, size_t *pat_len_r,
+ size_t *path_len_r, bool partial_ok, bool recurse)
+{
+ const char *path_start = path;
+ const char *pat, *attribute, *p;
+ size_t 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_push_back(values,
+ &attribute);
+ } else {
+ array_push_back(values, &path);
+ }
+ } else {
+ array_push_back(values, &path);
+ 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_push_back(values, &attribute);
+ path = p;
+ } else {
+ /* no '/' anymore, but it'll still match a
+ partial */
+ array_push_back(values, &path);
+ 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;
+ size_t 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;
+ i_zero(&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);
+}
+
+#define IS_LDAP_ESCAPED_CHAR(c) \
+ ((((unsigned char)(c)) & 0x80) != 0 || strchr(LDAP_ESCAPE_CHARS, (c)) != NULL)
+
+static const char *ldap_escape(const char *str)
+{
+ string_t *ret = NULL;
+
+ for (const char *p = str; *p != '\0'; p++) {
+ if (IS_LDAP_ESCAPED_CHAR(*p)) {
+ if (ret == NULL) {
+ ret = t_str_new((size_t) (p - str) + 64);
+ str_append_data(ret, str, (size_t) (p - str));
+ }
+ str_printfa(ret, "\\%02X", (unsigned char)*p);
+ } else if (ret != NULL)
+ str_append_c(ret, *p);
+ }
+
+ return ret == NULL ? str : str_c(ret);
+}
+
+static bool
+ldap_dict_build_query(const struct dict_op_settings *set,
+ const struct dict_ldap_map *map,
+ ARRAY_TYPE(const_string) *values, bool priv,
+ string_t *query_r, const char **error_r)
+{
+ const char *template, *error;
+ ARRAY(struct var_expand_table) exp;
+ struct var_expand_table entry;
+
+ t_array_init(&exp, 8);
+ entry.key = '\0';
+ entry.value = ldap_escape(set->username);
+ entry.long_key = "username";
+ array_push_back(&exp, &entry);
+
+ 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;
+ const char *value = array_idx_elem(values, i);
+ const char *long_key = array_idx_elem(&map->ldap_attributes, i);
+
+ entry.value = ldap_escape(value);
+ entry.long_key = long_key;
+ array_push_back(&exp, &entry);
+ }
+
+ array_append_zero(&exp);
+
+ if (var_expand(query_r, template, array_front(&exp), &error) <= 0) {
+ *error_r = t_strdup_printf("Failed to expand %s: %s", template, error);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static
+int ldap_dict_init(struct dict *dict_driver, const char *uri,
+ const struct dict_settings *set ATTR_UNUSED,
+ 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->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;
+ return 0;
+}
+
+static
+void ldap_dict_deinit(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ ldap_client_deinit(&ctx->client);
+ pool_unref(&ctx->pool);
+}
+
+static void ldap_dict_wait(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ i_assert(ctx->dict.ioloop == NULL);
+
+ ctx->dict.prev_ioloop = current_ioloop;
+ ctx->dict.ioloop = io_loop_create();
+ dict_switch_ioloop(dict);
+
+ do {
+ io_loop_run(current_ioloop);
+ } while (ctx->pending > 0);
+
+ io_loop_set_current(ctx->dict.prev_ioloop);
+ dict_switch_ioloop(dict);
+ io_loop_set_current(ctx->dict.ioloop);
+ io_loop_destroy(&ctx->dict.ioloop);
+ ctx->dict.prev_ioloop = NULL;
+}
+
+static bool ldap_dict_switch_ioloop(struct dict *dict)
+{
+ struct ldap_dict *ctx = (struct ldap_dict *)dict;
+
+ ldap_client_switch_ioloop(ctx->client);
+ return ctx->pending > 0;
+}
+
+static
+void ldap_dict_lookup_done(const struct dict_lookup_result *result, void *ctx)
+{
+ struct dict_lookup_result *res = ctx;
+ res->ret = result->ret;
+ res->value = t_strdup(result->value);
+ res->error = t_strdup(result->error);
+}
+
+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) {
+ const char **new_values;
+
+ if (op->dict->set->debug > 0)
+ i_debug("ldap_dict_lookup_callback got attribute %s", op->map->value_attribute);
+ op->res.ret = 1;
+ new_values = p_new(op->pool, const char *, 2);
+ new_values[0] = p_strdup(op->pool, values[0]);
+ op->res.values = new_values;
+ op->res.value = op->res.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);
+ }
+ if (op->dict->dict.prev_ioloop != NULL)
+ io_loop_set_current(op->dict->dict.prev_ioloop);
+ op->callback(&op->res, op->callback_ctx);
+ if (op->dict->dict.prev_ioloop != NULL) {
+ io_loop_set_current(op->dict->dict.ioloop);
+ io_loop_stop(op->dict->dict.ioloop);
+ }
+ pool_unref(&pool);
+}
+
+static int
+ldap_dict_lookup(struct dict *dict, const struct dict_op_settings *set,
+ pool_t pool, const char *key,
+ const char **value_r, const char **error_r)
+{
+ struct dict_lookup_result res;
+
+ ldap_dict_lookup_async(dict, set, key, ldap_dict_lookup_done, &res);
+
+ ldap_dict_wait(dict);
+ if (res.ret < 0) {
+ *error_r = res.error;
+ return -1;
+ }
+ if (res.ret > 0)
+ *value_r = p_strdup(pool, res.value);
+ return res.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_atomic_inc(struct dict_transaction_context *ctx,
+ const char *key, long long diff);
+*/
+
+static
+void ldap_dict_lookup_async(struct dict *dict,
+ const struct dict_op_settings *set,
+ 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;
+ const char *error;
+
+ pool_t oppool = pool_alloconly_create("ldap dict lookup", 64);
+ string_t *query = str_new(oppool, 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;
+ 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 */
+ i_zero(&input);
+ input.base_dn = map->base_dn;
+ input.scope = map->scope_val;
+ if (!ldap_dict_build_query(set, map, &values, strncmp(key, DICT_PATH_PRIVATE, strlen(DICT_PATH_PRIVATE))==0, query, &error)) {
+ op->res.error = error;
+ callback(&op->res, context);
+ pool_unref(&oppool);
+ return;
+ }
+ input.filter = str_c(query);
+ 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);
+ }
+}
+
+struct dict dict_driver_ldap = {
+ .name = "ldap",
+ {
+ .init = ldap_dict_init,
+ .deinit = ldap_dict_deinit,
+ .wait = ldap_dict_wait,
+ .lookup = ldap_dict_lookup,
+ .lookup_async = ldap_dict_lookup_async,
+ .switch_ioloop = ldap_dict_switch_ioloop,
+ }
+};
+
+#ifndef BUILTIN_LDAP
+/* Building a plugin */
+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)
+{
+ ldap_clients_cleanup();
+ dict_driver_unregister(&dict_driver_ldap);
+}
+
+const char *dict_ldap_plugin_dependencies[] = { NULL };
+#endif
+
+#endif