/* 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