diff options
Diffstat (limited to 'src/auth/passdb-ldap.c')
-rw-r--r-- | src/auth/passdb-ldap.c | 519 |
1 files changed, 519 insertions, 0 deletions
diff --git a/src/auth/passdb-ldap.c b/src/auth/passdb-ldap.c new file mode 100644 index 0000000..11b9ae8 --- /dev/null +++ b/src/auth/passdb-ldap.c @@ -0,0 +1,519 @@ +/* Copyright (c) 2003-2018 Dovecot authors, see the included COPYING file */ + +#include "auth-common.h" +#include "passdb.h" + +#if defined(PASSDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)) + +#include "ioloop.h" +#include "array.h" +#include "str.h" +#include "password-scheme.h" +#include "auth-cache.h" +#include "db-ldap.h" + +#include <ldap.h> + +struct ldap_passdb_module { + struct passdb_module module; + + struct ldap_connection *conn; +}; + +struct passdb_ldap_request { + union { + struct ldap_request ldap; + struct ldap_request_search search; + struct ldap_request_bind bind; + } request; + const char *dn; + + union { + verify_plain_callback_t *verify_plain; + lookup_credentials_callback_t *lookup_credentials; + } callback; + + unsigned int entries; + bool require_password; +}; + +static void +ldap_query_save_result(struct ldap_connection *conn, + struct auth_request *auth_request, + struct ldap_request_search *ldap_request, + LDAPMessage *res) +{ + struct db_ldap_result_iterate_context *ldap_iter; + const char *name, *const *values; + + ldap_iter = db_ldap_result_iterate_init(conn, ldap_request, res, FALSE); + while (db_ldap_result_iterate_next(ldap_iter, &name, &values)) { + if (values[0] == NULL) { + auth_request_set_null_field(auth_request, name); + continue; + } + if (values[1] != NULL) { + e_warning(authdb_event(auth_request), + "Multiple values found for '%s', " + "using value '%s'", name, values[0]); + } + auth_request_set_field(auth_request, name, values[0], + conn->set.default_pass_scheme); + } + db_ldap_result_iterate_deinit(&ldap_iter); +} + +static void +ldap_lookup_finish(struct auth_request *auth_request, + struct passdb_ldap_request *ldap_request, + LDAPMessage *res) +{ + enum passdb_result passdb_result; + const char *password = NULL, *scheme; + int ret; + + if (res == NULL) { + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + } else if (ldap_request->entries == 0) { + passdb_result = PASSDB_RESULT_USER_UNKNOWN; + auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); + } else if (ldap_request->entries > 1) { + e_error(authdb_event(auth_request), + "pass_filter matched multiple objects, aborting"); + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + } else if (auth_request->passdb_password == NULL && + ldap_request->require_password && + !auth_fields_exists(auth_request->fields.extra_fields, "nopassword")) { + passdb_result = auth_request_password_missing(auth_request); + } else { + /* passdb_password may change on the way, + so we'll need to strdup. */ + password = t_strdup(auth_request->passdb_password); + passdb_result = PASSDB_RESULT_OK; + } + + scheme = password_get_scheme(&password); + /* auth_request_set_field() sets scheme */ + i_assert(password == NULL || scheme != NULL); + + if (auth_request->wanted_credentials_scheme != NULL) { + passdb_handle_credentials(passdb_result, password, scheme, + ldap_request->callback.lookup_credentials, + auth_request); + } else { + if (password != NULL) { + ret = auth_request_password_verify(auth_request, + auth_request->mech_password, + password, scheme, AUTH_SUBSYS_DB); + passdb_result = ret > 0 ? PASSDB_RESULT_OK : + PASSDB_RESULT_PASSWORD_MISMATCH; + } + + ldap_request->callback.verify_plain(passdb_result, + auth_request); + } +} + +static void +ldap_lookup_pass_callback(struct ldap_connection *conn, + struct ldap_request *request, LDAPMessage *res) +{ + struct passdb_ldap_request *ldap_request = + (struct passdb_ldap_request *)request; + struct auth_request *auth_request = request->auth_request; + + if (res == NULL || ldap_msgtype(res) == LDAP_RES_SEARCH_RESULT) { + ldap_lookup_finish(auth_request, ldap_request, res); + auth_request_unref(&auth_request); + return; + } + + if (ldap_request->entries++ == 0) { + /* first entry */ + ldap_query_save_result(conn, auth_request, + &ldap_request->request.search, res); + } +} + +static void +ldap_auth_bind_callback(struct ldap_connection *conn, + struct ldap_request *ldap_request, LDAPMessage *res) +{ + struct passdb_ldap_request *passdb_ldap_request = + (struct passdb_ldap_request *)ldap_request; + struct auth_request *auth_request = ldap_request->auth_request; + enum passdb_result passdb_result; + int ret; + + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + + if (res != NULL) { + ret = ldap_result2error(conn->ld, res, 0); + if (ret == LDAP_SUCCESS) + passdb_result = PASSDB_RESULT_OK; + else if (ret == LDAP_INVALID_CREDENTIALS) { + auth_request_log_login_failure(auth_request, + AUTH_SUBSYS_DB, + AUTH_LOG_MSG_PASSWORD_MISMATCH" (for LDAP bind)"); + passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH; + } else if (ret == LDAP_NO_SUCH_OBJECT) { + passdb_result = PASSDB_RESULT_USER_UNKNOWN; + auth_request_log_unknown_user(auth_request, + AUTH_SUBSYS_DB); + } else { + e_error(authdb_event(auth_request), + "ldap_bind() failed: %s", + ldap_err2string(ret)); + } + } + + passdb_ldap_request->callback. + verify_plain(passdb_result, auth_request); + auth_request_unref(&auth_request); +} + +static void ldap_auth_bind(struct ldap_connection *conn, + struct ldap_request_bind *brequest) +{ + struct passdb_ldap_request *passdb_ldap_request = + (struct passdb_ldap_request *)brequest; + struct auth_request *auth_request = brequest->request.auth_request; + + if (*auth_request->mech_password == '\0') { + /* Assume that empty password fails. This is especially + important with Windows 2003 AD, which always returns success + with empty passwords. */ + e_info(authdb_event(auth_request), + "Login attempt with empty password"); + passdb_ldap_request->callback. + verify_plain(PASSDB_RESULT_PASSWORD_MISMATCH, + auth_request); + return; + } + + brequest->request.callback = ldap_auth_bind_callback; + db_ldap_request(conn, &brequest->request); +} + +static void passdb_ldap_request_fail(struct passdb_ldap_request *request, + enum passdb_result passdb_result) +{ + struct auth_request *auth_request = request->request.ldap.auth_request; + + if (auth_request->wanted_credentials_scheme != NULL) { + request->callback.lookup_credentials(passdb_result, NULL, 0, + auth_request); + } else { + request->callback.verify_plain(passdb_result, auth_request); + } + auth_request_unref(&auth_request); +} + +static void +ldap_bind_lookup_dn_fail(struct auth_request *auth_request, + struct passdb_ldap_request *request, + LDAPMessage *res) +{ + enum passdb_result passdb_result; + + if (res == NULL) + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + else if (request->entries == 0) { + passdb_result = PASSDB_RESULT_USER_UNKNOWN; + auth_request_log_unknown_user(auth_request, AUTH_SUBSYS_DB); + } else { + i_assert(request->entries > 1); + e_error(authdb_event(auth_request), + "pass_filter matched multiple objects, aborting"); + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + } + + passdb_ldap_request_fail(request, passdb_result); +} + +static void ldap_bind_lookup_dn_callback(struct ldap_connection *conn, + struct ldap_request *ldap_request, + LDAPMessage *res) +{ + struct passdb_ldap_request *passdb_ldap_request = + (struct passdb_ldap_request *)ldap_request; + struct auth_request *auth_request = ldap_request->auth_request; + struct passdb_ldap_request *brequest; + char *dn; + + if (res != NULL && ldap_msgtype(res) == LDAP_RES_SEARCH_ENTRY) { + if (passdb_ldap_request->entries++ > 0) { + /* too many replies */ + return; + } + + /* first entry */ + ldap_query_save_result(conn, auth_request, + &passdb_ldap_request->request.search, res); + + /* save dn */ + dn = ldap_get_dn(conn->ld, res); + passdb_ldap_request->dn = p_strdup(auth_request->pool, dn); + ldap_memfree(dn); + } else if (res == NULL || passdb_ldap_request->entries != 1) { + /* failure */ + ldap_bind_lookup_dn_fail(auth_request, passdb_ldap_request, res); + } else if (auth_request->fields.skip_password_check) { + /* we've already verified that the password matched - + we just wanted to get any extra fields */ + passdb_ldap_request->callback. + verify_plain(PASSDB_RESULT_OK, auth_request); + auth_request_unref(&auth_request); + } else { + /* create a new bind request */ + brequest = p_new(auth_request->pool, + struct passdb_ldap_request, 1); + brequest->dn = passdb_ldap_request->dn; + brequest->callback = passdb_ldap_request->callback; + brequest->request.bind.dn = brequest->dn; + brequest->request.bind.request.type = LDAP_REQUEST_TYPE_BIND; + brequest->request.bind.request.auth_request = auth_request; + + ldap_auth_bind(conn, &brequest->request.bind); + } +} + +static void ldap_lookup_pass(struct auth_request *auth_request, + struct passdb_ldap_request *request, + bool require_password) +{ + struct passdb_module *_module = auth_request->passdb->passdb; + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + struct ldap_connection *conn = module->conn; + struct ldap_request_search *srequest = &request->request.search; + const char **attr_names = (const char **)conn->pass_attr_names; + const char *error; + string_t *str; + + request->require_password = require_password; + srequest->request.type = LDAP_REQUEST_TYPE_SEARCH; + + str = t_str_new(512); + if (auth_request_var_expand(str, conn->set.base, auth_request, + ldap_escape, &error) <= 0) { + e_error(authdb_event(auth_request), + "Failed to expand base=%s: %s", conn->set.base, error); + passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); + return; + } + srequest->base = p_strdup(auth_request->pool, str_c(str)); + + str_truncate(str, 0); + if (auth_request_var_expand(str, conn->set.pass_filter, + auth_request, ldap_escape, &error) <= 0) { + e_error(authdb_event(auth_request), + "Failed to expand pass_filter=%s: %s", + conn->set.pass_filter, error); + passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); + return; + } + srequest->filter = p_strdup(auth_request->pool, str_c(str)); + srequest->attr_map = &conn->pass_attr_map; + srequest->attributes = conn->pass_attr_names; + + e_debug(authdb_event(auth_request), "pass search: " + "base=%s scope=%s filter=%s fields=%s", + srequest->base, conn->set.scope, + srequest->filter, attr_names == NULL ? "(all)" : + t_strarray_join(attr_names, ",")); + + srequest->request.callback = ldap_lookup_pass_callback; + db_ldap_request(conn, &srequest->request); +} + +static void ldap_bind_lookup_dn(struct auth_request *auth_request, + struct passdb_ldap_request *request) +{ + struct passdb_module *_module = auth_request->passdb->passdb; + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + struct ldap_connection *conn = module->conn; + struct ldap_request_search *srequest = &request->request.search; + const char *error; + string_t *str; + + srequest->request.type = LDAP_REQUEST_TYPE_SEARCH; + + str = t_str_new(512); + if (auth_request_var_expand(str, conn->set.base, auth_request, + ldap_escape, &error) <= 0) { + e_error(authdb_event(auth_request), + "Failed to expand base=%s: %s", conn->set.base, error); + passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); + return; + } + srequest->base = p_strdup(auth_request->pool, str_c(str)); + + str_truncate(str, 0); + if (auth_request_var_expand(str, conn->set.pass_filter, + auth_request, ldap_escape, &error) <= 0) { + e_error(authdb_event(auth_request), + "Failed to expand pass_filter=%s: %s", + conn->set.pass_filter, error); + passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); + return; + } + srequest->filter = p_strdup(auth_request->pool, str_c(str)); + + /* we don't need the attributes to perform authentication, but they + may contain some extra parameters. if a password is returned, + it's just ignored. */ + srequest->attr_map = &conn->pass_attr_map; + srequest->attributes = conn->pass_attr_names; + + e_debug(authdb_event(auth_request), + "bind search: base=%s filter=%s", + srequest->base, srequest->filter); + + srequest->request.callback = ldap_bind_lookup_dn_callback; + db_ldap_request(conn, &srequest->request); +} + +static void +ldap_verify_plain_auth_bind_userdn(struct auth_request *auth_request, + struct passdb_ldap_request *request) +{ + struct passdb_module *_module = auth_request->passdb->passdb; + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + struct ldap_connection *conn = module->conn; + struct ldap_request_bind *brequest = &request->request.bind; + string_t *dn; + const char *error; + + brequest->request.type = LDAP_REQUEST_TYPE_BIND; + + dn = t_str_new(512); + if (auth_request_var_expand(dn, conn->set.auth_bind_userdn, + auth_request, ldap_escape, &error) <= 0) { + e_error(authdb_event(auth_request), + "Failed to expand auth_bind_userdn=%s: %s", + conn->set.auth_bind_userdn, error); + passdb_ldap_request_fail(request, PASSDB_RESULT_INTERNAL_FAILURE); + return; + } + + brequest->dn = p_strdup(auth_request->pool, str_c(dn)); + ldap_auth_bind(conn, brequest); +} + +static void +ldap_verify_plain(struct auth_request *request, + const char *password ATTR_UNUSED, + verify_plain_callback_t *callback) +{ + struct passdb_module *_module = request->passdb->passdb; + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + struct ldap_connection *conn = module->conn; + struct passdb_ldap_request *ldap_request; + + /* reconnect if needed. this is also done by db_ldap_search(), but + with auth binds we'll have to do it ourself */ + if (db_ldap_connect(conn)< 0) { + callback(PASSDB_RESULT_INTERNAL_FAILURE, request); + return; + } + + ldap_request = p_new(request->pool, struct passdb_ldap_request, 1); + ldap_request->callback.verify_plain = callback; + + auth_request_ref(request); + ldap_request->request.ldap.auth_request = request; + + if (!conn->set.auth_bind) + ldap_lookup_pass(request, ldap_request, TRUE); + else if (conn->set.auth_bind_userdn == NULL) + ldap_bind_lookup_dn(request, ldap_request); + else + ldap_verify_plain_auth_bind_userdn(request, ldap_request); +} + +static void ldap_lookup_credentials(struct auth_request *request, + lookup_credentials_callback_t *callback) +{ + struct passdb_module *_module = request->passdb->passdb; + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + struct passdb_ldap_request *ldap_request; + bool require_password; + + ldap_request = p_new(request->pool, struct passdb_ldap_request, 1); + ldap_request->callback.lookup_credentials = callback; + + auth_request_ref(request); + ldap_request->request.ldap.auth_request = request; + + /* with auth_bind=yes we don't necessarily have a password. + this will fail actual password credentials lookups, but it's fine + for passdb lookups done by lmtp/doveadm */ + require_password = !module->conn->set.auth_bind; + ldap_lookup_pass(request, ldap_request, require_password); +} + +static struct passdb_module * +passdb_ldap_preinit(pool_t pool, const char *args) +{ + struct ldap_passdb_module *module; + struct ldap_connection *conn; + + module = p_new(pool, struct ldap_passdb_module, 1); + module->conn = conn = db_ldap_init(args, FALSE); + p_array_init(&conn->pass_attr_map, pool, 16); + db_ldap_set_attrs(conn, conn->set.pass_attrs, &conn->pass_attr_names, + &conn->pass_attr_map, + conn->set.auth_bind ? "password" : NULL); + module->module.blocking = conn->set.blocking; + module->module.default_cache_key = + auth_cache_parse_key(pool, + t_strconcat(conn->set.base, + conn->set.pass_attrs, + conn->set.pass_filter, NULL)); + module->module.default_pass_scheme = conn->set.default_pass_scheme; + return &module->module; +} + +static void passdb_ldap_init(struct passdb_module *_module) +{ + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + + if (!module->module.blocking || worker) + db_ldap_connect_delayed(module->conn); +} + +static void passdb_ldap_deinit(struct passdb_module *_module) +{ + struct ldap_passdb_module *module = + (struct ldap_passdb_module *)_module; + + db_ldap_unref(&module->conn); +} + +#ifndef PLUGIN_BUILD +struct passdb_module_interface passdb_ldap = +#else +struct passdb_module_interface passdb_ldap_plugin = +#endif +{ + "ldap", + + passdb_ldap_preinit, + passdb_ldap_init, + passdb_ldap_deinit, + + ldap_verify_plain, + ldap_lookup_credentials, + NULL +}; +#else +struct passdb_module_interface passdb_ldap = { + .name = "ldap" +}; +#endif |