summaryrefslogtreecommitdiffstats
path: root/src/auth/passdb-ldap.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/auth/passdb-ldap.c')
-rw-r--r--src/auth/passdb-ldap.c519
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