passdb-ldap.c revision 9fa4ac629eb23f1ef80cca8dbb98059819f49656
/* Copyright (c) 2003-2016 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) {
auth_request_log_warning(auth_request, AUTH_SUBSYS_DB,
"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) {
auth_request_log_error(auth_request, AUTH_SUBSYS_DB,
"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->extra_fields, "nopassword")) {
auth_request_log_info(auth_request, AUTH_SUBSYS_DB,
"No password returned (and no nopassword)");
passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH;
} 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->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;
const char *str;
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) {
str = "invalid credentials";
if (auth_request->set->debug_passwords) {
str = t_strconcat(str, " (given password: ",
auth_request->mech_password,
")", NULL);
}
auth_request_log_info(auth_request, AUTH_SUBSYS_DB,
"%s", str);
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 {
auth_request_log_error(auth_request, AUTH_SUBSYS_DB,
"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. */
auth_request_log_info(auth_request, AUTH_SUBSYS_DB,
"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
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);
auth_request_log_error(auth_request, AUTH_SUBSYS_DB,
"pass_filter matched multiple objects, aborting");
passdb_result = PASSDB_RESULT_INTERNAL_FAILURE;
}
if (auth_request->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_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->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;
string_t *str;
request->require_password = require_password;
srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
str = t_str_new(512);
auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape);
srequest->base = p_strdup(auth_request->pool, str_c(str));
str_truncate(str, 0);
auth_request_var_expand(str, conn->set.pass_filter,
auth_request, ldap_escape);
srequest->filter = p_strdup(auth_request->pool, str_c(str));
srequest->attr_map = &conn->pass_attr_map;
srequest->attributes = conn->pass_attr_names;
auth_request_log_debug(auth_request, AUTH_SUBSYS_DB, "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;
string_t *str;
srequest->request.type = LDAP_REQUEST_TYPE_SEARCH;
str = t_str_new(512);
auth_request_var_expand(str, conn->set.base, auth_request, ldap_escape);
srequest->base = p_strdup(auth_request->pool, str_c(str));
str_truncate(str, 0);
auth_request_var_expand(str, conn->set.pass_filter,
auth_request, ldap_escape);
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;
auth_request_log_debug(auth_request, AUTH_SUBSYS_DB,
"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;
brequest->request.type = LDAP_REQUEST_TYPE_BIND;
dn = t_str_new(512);
auth_request_var_expand(dn, conn->set.auth_bind_userdn, auth_request, ldap_escape);
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.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;
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