NOTE: not working diff -r c4f56ed9dae0 src/auth/auth-request.c --- a/src/auth/auth-request.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/auth-request.c Mon May 31 18:07:48 2010 +0100 @@ -2,7 +2,7 @@ #include "auth-common.h" #include "ioloop.h" -#include "buffer.h" +#include "array.h" #include "hash.h" #include "sha1.h" #include "hex-binary.h" @@ -50,6 +50,7 @@ request->mech_name = mech == NULL ? NULL : mech->mech_name; request->callback = callback; request->context = context; + p_array_init(&request->passdb_passwords, request->pool, 4); return request; } @@ -68,6 +69,7 @@ request->refcount = 1; request->last_access = ioloop_time; request->set = global_auth_settings; + p_array_init(&request->passdb_passwords, request->pool, 4); return request; } @@ -261,11 +263,44 @@ request->mech->auth_continue(request, data, data_size); } +void auth_request_reset_passdb_lookup(struct auth_request *request) +{ + array_clear(&request->passdb_passwords); + + if (request->extra_fields != NULL) + auth_stream_reply_reset(request->extra_fields); +} + +static void auth_request_save_cache_passwords(struct auth_request *request, + string_t *str) +{ + struct passdb_module *passdb = request->passdb->passdb; + char *const *passwords; + unsigned int i, count; + + passwords = array_get(&request->passdb_passwords, &count); + for (i = 0; i < count; i++) { + if (*passwords[i] != '{') { + /* cached passwords must have a known scheme */ + str_append_c(str, '{'); + str_append(str, passdb->default_pass_scheme); + str_append_c(str, '}'); + } + if (strchr(passwords[i], '\t') != NULL) + i_panic("%s: Password contains TAB", request->user); + if (strchr(passwords[i], '\n') != NULL) + i_panic("%s: Password contains LF", request->user); + str_append(str, passwords[i]); + str_append_c(str, '\t'); + } +} + static void auth_request_save_cache(struct auth_request *request, enum passdb_result result) { struct passdb_module *passdb = request->passdb->passdb; const char *extra_fields, *encoded_password; + char *password; string_t *str; switch (result) { @@ -281,6 +316,7 @@ return success. */ return; case PASSDB_RESULT_INTERNAL_FAILURE: + case PASSDB_RESULT_LAST_FAILURE: i_unreached(); } @@ -302,41 +338,29 @@ return; } - if (!request->no_password && request->passdb_password == NULL) { + if (!request->no_password && + array_count(&request->passdb_passwords) == 0) { /* passdb didn't provide the correct password */ if (result != PASSDB_RESULT_OK || request->mech_password == NULL) return; - /* we can still cache valid password lookups though. - strdup() it so that mech_password doesn't get - cleared too early. */ + /* we're doing plaintext authentication, so we can still cache + valid lookups using the user-given password. strdup() it so + that mech_password doesn't get cleared too early. */ if (!password_generate_encoded(request->mech_password, request->user, CACHED_PASSWORD_SCHEME, &encoded_password)) i_unreached(); - request->passdb_password = - p_strconcat(request->pool, "{"CACHED_PASSWORD_SCHEME"}", - encoded_password, NULL); + password = p_strconcat(request->pool, "{"CACHED_PASSWORD_SCHEME"}", + encoded_password, NULL); + array_append(&request->passdb_passwords, &password, 1); } /* save all except the currently given password in cache */ str = t_str_new(256); - if (request->passdb_password != NULL) { - if (*request->passdb_password != '{') { - /* cached passwords must have a known scheme */ - str_append_c(str, '{'); - str_append(str, passdb->default_pass_scheme); - str_append_c(str, '}'); - } - if (strchr(request->passdb_password, '\t') != NULL) - i_panic("%s: Password contains TAB", request->user); - if (strchr(request->passdb_password, '\n') != NULL) - i_panic("%s: Password contains LF", request->user); - str_append(str, request->passdb_password); - } - + auth_request_save_cache_passwords(request, str); if (extra_fields != NULL && *extra_fields != '\0') { str_append_c(str, '\t'); str_append(str, extra_fields); @@ -367,7 +391,7 @@ request->requested_login_user = NULL; request->skip_password_check = TRUE; - request->passdb_password = NULL; + array_clear(&request->passdb_passwords); if (!request->passdb->set->pass) { /* skip the passdb lookup, we're authenticated now. */ @@ -384,10 +408,12 @@ auth_request_handle_passdb_callback(enum passdb_result *result, struct auth_request *request) { - if (request->passdb_password != NULL) { - safe_memset(request->passdb_password, 0, - strlen(request->passdb_password)); - } + char *const *passwords; + unsigned int i, count; + + passwords = array_get(&request->passdb_passwords, &count); + for (i = 0; i < count; i++) + safe_memset(passwords[i], 0, strlen(passwords[i])); if (request->passdb->set->deny && *result != PASSDB_RESULT_USER_UNKNOWN) { @@ -410,7 +436,7 @@ /* this wasn't the final passdb lookup, continue to next passdb */ request->passdb = request->passdb->next; - request->passdb_password = NULL; + array_clear(&request->passdb_passwords); return FALSE; } } @@ -425,7 +451,7 @@ *result != PASSDB_RESULT_USER_DISABLED) { /* try next passdb. */ request->passdb = request->passdb->next; - request->passdb_password = NULL; + auth_request_reset_passdb_lookup(request); if (*result == PASSDB_RESULT_INTERNAL_FAILURE) { /* remember that we have had an internal failure. at @@ -433,9 +459,6 @@ successfully login. */ request->passdb_internal_failure = TRUE; } - if (request->extra_fields != NULL) - auth_stream_reply_reset(request->extra_fields); - return FALSE; } else if (request->passdb_internal_failure) { /* last passdb lookup returned internal failure. it may have @@ -560,17 +583,23 @@ } } -static void +static bool auth_request_lookup_credentials_finish(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { if (!auth_request_handle_passdb_callback(&result, request)) { + if (result != PASSDB_RESULT_LAST_FAILURE) { + /* see if we can get more credentials */ + return FALSE; + } + /* try next passdb */ auth_request_lookup_credentials(request, request->credentials_scheme, request->private_callback.lookup_credentials); + return TRUE; } else { if (request->set->debug_passwords && result == PASSDB_RESULT_OK) { @@ -578,45 +607,91 @@ "Credentials: %s", binary_to_hex(credentials, size)); } - request->private_callback. + return request->private_callback. lookup_credentials(result, credentials, size, request); } } -void auth_request_lookup_credentials_callback(enum passdb_result result, +static bool +auth_request_lookup_credentials_try_use_cache(struct auth_request *request, + bool use_expired) +{ + ARRAY_TYPE(const_string) cache_passwords; + const char *const *passwords, *password, *scheme, *cache_key; + unsigned int i, count; + const unsigned char *credentials; + size_t size; + enum passdb_result result; + + cache_key = passdb_cache == NULL ? NULL : + request->passdb->passdb->cache_key; + if (cache_key == NULL) + return FALSE; + + if (!passdb_cache_lookup_credentials(request, cache_key, + &cache_passwords, + &result, use_expired)) + return FALSE; + + if (use_expired) { + auth_request_log_info(request, "passdb", + "Fallbacking to expired data from cache"); + } + + if (result != PASSDB_RESULT_OK) { + /* negative cache hit */ + return auth_request_lookup_credentials_finish(result, NULL, 0, + request); + } + + /* successful cache lookup with valid passwords */ + result = PASSDB_RESULT_LAST_FAILURE; + passwords = array_get(&cache_passwords, &count); + for (i = 0; i < count; i++) { + password = passwords[i]; + scheme = password_get_scheme(&password); + i_assert(scheme != NULL); + + if (!passdb_get_credentials(request, password, scheme, + &credentials, &size)) { + result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE; + continue; + } + if (auth_request_lookup_credentials_finish(PASSDB_RESULT_OK, + credentials, + size, request)) + return TRUE; + } + return auth_request_lookup_credentials_finish(result, NULL, 0, request); +} + +bool auth_request_lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) { - const char *cache_cred, *cache_scheme; - i_assert(request->state == AUTH_REQUEST_STATE_PASSDB); auth_request_set_state(request, AUTH_REQUEST_STATE_MECH_CONTINUE); - if (result != PASSDB_RESULT_INTERNAL_FAILURE) + if (result == PASSDB_RESULT_LAST_FAILURE) { + /* no more results */ + } else if (result != PASSDB_RESULT_INTERNAL_FAILURE) auth_request_save_cache(request, result); else { /* lookup failed. if we're looking here only because the request was expired in cache, fallback to using cached expired record. */ - const char *cache_key = request->passdb->passdb->cache_key; - - if (passdb_cache_lookup_credentials(request, cache_key, - &cache_cred, &cache_scheme, - &result, TRUE)) { - auth_request_log_info(request, "passdb", - "Fallbacking to expired data from cache"); - passdb_handle_credentials( - result, cache_cred, cache_scheme, - auth_request_lookup_credentials_finish, - request); - return; - } + if (auth_request_lookup_credentials_try_use_cache(request, TRUE)) + return TRUE; } - auth_request_lookup_credentials_finish(result, credentials, size, - request); + if (!auth_request_lookup_credentials_finish(result, credentials, size, + request)) { + request->state = AUTH_REQUEST_STATE_PASSDB; + return FALSE; + } + return TRUE; } void auth_request_lookup_credentials(struct auth_request *request, @@ -624,35 +699,23 @@ lookup_credentials_callback_t *callback) { struct passdb_module *passdb = request->passdb->passdb; - const char *cache_key, *cache_cred, *cache_scheme; - enum passdb_result result; i_assert(request->state == AUTH_REQUEST_STATE_MECH_CONTINUE); request->credentials_scheme = p_strdup(request->pool, scheme); request->private_callback.lookup_credentials = callback; - cache_key = passdb_cache == NULL ? NULL : passdb->cache_key; - if (cache_key != NULL) { - if (passdb_cache_lookup_credentials(request, cache_key, - &cache_cred, &cache_scheme, - &result, FALSE)) { - passdb_handle_credentials( - result, cache_cred, cache_scheme, - auth_request_lookup_credentials_finish, - request); - return; - } - } - + if (auth_request_lookup_credentials_try_use_cache(request, FALSE)) + return; auth_request_set_state(request, AUTH_REQUEST_STATE_PASSDB); if (passdb->iface.lookup_credentials == NULL) { /* this passdb doesn't support credentials */ auth_request_log_debug(request, "password", "passdb doesn't support credential lookups"); - auth_request_lookup_credentials_callback( + bool ret = auth_request_lookup_credentials_callback( PASSDB_RESULT_SCHEME_NOT_AVAILABLE, NULL, 0, request); + i_assert(ret); } else if (passdb->blocking) { passdb_blocking_lookup_credentials(request); } else { @@ -986,25 +1049,20 @@ auth_request_set_password(struct auth_request *request, const char *value, const char *default_scheme, bool noscheme) { - if (request->passdb_password != NULL) { - auth_request_log_error(request, - request->passdb->passdb->iface.name, - "Multiple password values not supported"); - return; - } + char *password; /* if the password starts with '{' it most likely contains also '}'. check it anyway to make sure, because we assert-crash later if it doesn't exist. this could happen if plaintext passwords are used. */ if (*value == '{' && !noscheme && strchr(value, '}') != NULL) - request->passdb_password = p_strdup(request->pool, value); + password = p_strdup(request->pool, value); else { i_assert(default_scheme != NULL); - request->passdb_password = - p_strdup_printf(request->pool, "{%s}%s", - default_scheme, value); + password = p_strdup_printf(request->pool, "{%s}%s", + default_scheme, value); } + array_append(&request->passdb_passwords, &password, 1); } static void auth_request_set_reply_field(struct auth_request *request, @@ -1088,11 +1146,15 @@ request->no_failure_delay = TRUE; } else if (strcmp(name, "nopassword") == 0) { /* NULL password - anything goes */ - const char *password = request->passdb_password; + char *const *passwords; + const char *password; + unsigned int count; - if (password != NULL) { + passwords = array_get(&request->passdb_passwords, &count); + if (count > 0) { + password = passwords[0]; (void)password_get_scheme(&password); - if (*password != '\0') { + if (*password != '\0' || count > 1) { auth_request_log_error(request, request->passdb->passdb->iface.name, "nopassword set but password is " @@ -1101,7 +1163,7 @@ } } request->no_password = TRUE; - request->passdb_password = NULL; + array_clear(&request->passdb_passwords); } else if (strcmp(name, "allow_nets") == 0) { auth_request_validate_networks(request, value); } else if (strncmp(name, "userdb_", 7) == 0) { diff -r c4f56ed9dae0 src/auth/auth-request.h --- a/src/auth/auth-request.h Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/auth-request.h Mon May 31 18:07:48 2010 +0100 @@ -45,7 +45,8 @@ /* realm for the request, may be specified by some auth mechanisms */ const char *realm; char *mech_password; /* set if verify_plain() is called */ - char *passdb_password; /* set after password lookup if successful */ + /* list of all valid passwords. set after passdb lookup if successful */ + ARRAY_TYPE(string) passdb_passwords; /* extra_fields are returned in authentication reply. Fields prefixed with "userdb_" are skipped. If prefetch userdb is used, it uses the "userdb_" prefixed fields. */ @@ -140,6 +141,7 @@ void auth_request_initial(struct auth_request *request); void auth_request_continue(struct auth_request *request, const unsigned char *data, size_t data_size); +void auth_request_reset_passdb_lookup(struct auth_request *request); void auth_request_verify_plain(struct auth_request *request, const char *password, @@ -196,10 +198,11 @@ void auth_request_verify_plain_callback(enum passdb_result result, struct auth_request *request); -void auth_request_lookup_credentials_callback(enum passdb_result result, +bool auth_request_lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, - struct auth_request *request); + struct auth_request *request) + ATTR_WARN_UNUSED_RESULT; void auth_request_set_credentials(struct auth_request *request, const char *scheme, const char *data, set_credentials_callback_t *callback); diff -r c4f56ed9dae0 src/auth/auth-worker-client.c --- a/src/auth/auth-worker-client.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/auth-worker-client.c Mon May 31 18:07:48 2010 +0100 @@ -1,6 +1,7 @@ /* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */ #include "auth-common.h" +#include "array.h" #include "base64.h" #include "ioloop.h" #include "network.h" @@ -92,6 +93,8 @@ { struct auth_worker_client *client = request->context; struct auth_stream_reply *reply; + char *const *passwords; + unsigned int i, count; string_t *str; if (request->passdb_failure && result == PASSDB_RESULT_OK) @@ -109,9 +112,10 @@ } if (result != PASSDB_RESULT_INTERNAL_FAILURE) { auth_stream_reply_add(reply, NULL, request->user); - auth_stream_reply_add(reply, NULL, - request->passdb_password == NULL ? "" : - request->passdb_password); + passwords = array_get(&request->passdb_passwords, &count); + for (i = 0; i < count; i++) + auth_stream_reply_add(reply, NULL, passwords[i]); + auth_stream_reply_add(reply, NULL, ""); if (request->extra_fields != NULL) { const char *fields = auth_stream_reply_export(request->extra_fields); @@ -182,7 +186,7 @@ return TRUE; } -static void +static bool lookup_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request) @@ -228,6 +232,7 @@ auth_request_unref(&request); auth_worker_client_check_throttle(client); auth_worker_client_unref(&client); + return TRUE; } static bool diff -r c4f56ed9dae0 src/auth/db-sql.c --- a/src/auth/db-sql.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/db-sql.c Mon May 31 18:07:48 2010 +0100 @@ -23,6 +23,7 @@ DEF_STR(update_query), DEF_STR(iterate_query), DEF_STR(default_pass_scheme), + DEF_BOOL(allow_multiple_rows), { 0, NULL, 0 } }; @@ -34,7 +35,8 @@ .user_query = "SELECT home, uid, gid FROM users WHERE username = '%n' AND domain = '%d'", .update_query = "UPDATE users SET password = '%w' WHERE username = '%n' AND domain = '%d'", .iterate_query = "SELECT username, domain FROM users", - .default_pass_scheme = "MD5" + .default_pass_scheme = "MD5", + .allow_multiple_rows = FALSE }; static struct sql_connection *connections = NULL; diff -r c4f56ed9dae0 src/auth/db-sql.h --- a/src/auth/db-sql.h Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/db-sql.h Mon May 31 18:07:48 2010 +0100 @@ -11,6 +11,8 @@ const char *update_query; const char *iterate_query; const char *default_pass_scheme; + + bool allow_multiple_rows; }; struct sql_connection { diff -r c4f56ed9dae0 src/auth/mech-apop.c --- a/src/auth/mech-apop.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-apop.c Mon May 31 18:07:48 2010 +0100 @@ -43,7 +43,7 @@ return memcmp(digest, request->response_digest, 16) == 0; } -static void +static bool apop_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -53,10 +53,9 @@ switch (result) { case PASSDB_RESULT_OK: - if (verify_credentials(request, credentials, size)) - auth_request_success(auth_request, NULL, 0); - else - auth_request_fail(auth_request); + if (!verify_credentials(request, credentials, size)) + return FALSE; + auth_request_success(auth_request, NULL, 0); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); @@ -65,6 +64,7 @@ auth_request_fail(auth_request); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-cram-md5.c --- a/src/auth/mech-cram-md5.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-cram-md5.c Mon May 31 18:07:48 2010 +0100 @@ -105,7 +105,7 @@ return TRUE; } -static void credentials_callback(enum passdb_result result, +static bool credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { @@ -114,10 +114,9 @@ switch (result) { case PASSDB_RESULT_OK: - if (verify_credentials(request, credentials, size)) - auth_request_success(auth_request, NULL, 0); - else - auth_request_fail(auth_request); + if (!verify_credentials(request, credentials, size)) + return FALSE; + auth_request_success(auth_request, NULL, 0); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); @@ -126,6 +125,7 @@ auth_request_fail(auth_request); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-digest-md5.c --- a/src/auth/mech-digest-md5.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-digest-md5.c Mon May 31 18:07:48 2010 +0100 @@ -491,7 +491,7 @@ return !failed; } -static void credentials_callback(enum passdb_result result, +static bool credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) { @@ -500,10 +500,8 @@ switch (result) { case PASSDB_RESULT_OK: - if (!verify_credentials(request, credentials, size)) { - auth_request_fail(auth_request); - return; - } + if (!verify_credentials(request, credentials, size)) + return FALSE; request->authenticated = TRUE; auth_request->callback(auth_request, @@ -518,6 +516,7 @@ auth_request_fail(auth_request); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-ntlm.c --- a/src/auth/mech-ntlm.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-ntlm.c Mon May 31 18:07:48 2010 +0100 @@ -57,7 +57,7 @@ return memcmp(lm_response, client_response, LM_RESPONSE_SIZE) == 0; } -static void +static bool lm_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -67,10 +67,9 @@ switch (result) { case PASSDB_RESULT_OK: - if (lm_verify_credentials(request, credentials, size)) - auth_request_success(auth_request, NULL, 0); - else - auth_request_fail(auth_request); + if (!lm_verify_credentials(request, credentials, size)) + return FALSE; + auth_request_success(auth_request, NULL, 0); break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); @@ -79,6 +78,7 @@ auth_request_fail(auth_request); break; } + return TRUE; } static int @@ -138,7 +138,7 @@ } } -static void +static bool ntlm_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -152,16 +152,14 @@ ret = ntlm_verify_credentials(request, credentials, size); if (ret > 0) { auth_request_success(auth_request, NULL, 0); - return; + return TRUE; } - if (ret < 0) { - auth_request_fail(auth_request); - return; - } + if (ret < 0) + return FALSE; break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); - return; + return TRUE; default: break; } @@ -170,6 +168,7 @@ try with LM credentials */ auth_request_lookup_credentials(auth_request, "LANMAN", lm_credentials_callback); + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-otp.c --- a/src/auth/mech-otp.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-otp.c Mon May 31 18:07:48 2010 +0100 @@ -55,7 +55,7 @@ answer, strlen(answer)); } -static void +static bool skey_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -71,9 +71,10 @@ auth_request_fail(auth_request); break; } + return TRUE; } -static void +static bool otp_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -91,6 +92,7 @@ skey_credentials_callback); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-rpa.c --- a/src/auth/mech-rpa.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-rpa.c Mon May 31 18:07:48 2010 +0100 @@ -434,7 +434,7 @@ return memcmp(response, request->user_response, sizeof(response)) == 0; } -static void +static bool rpa_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -447,14 +447,13 @@ switch (result) { case PASSDB_RESULT_OK: if (!verify_credentials(request, credentials, size)) - auth_request_fail(auth_request); - else { - token4 = mech_rpa_build_token4(request, &token4_size); - auth_request->callback(auth_request, - AUTH_CLIENT_RESULT_CONTINUE, - token4, token4_size); - request->phase = 2; - } + return FALSE; + + token4 = mech_rpa_build_token4(request, &token4_size); + auth_request->callback(auth_request, + AUTH_CLIENT_RESULT_CONTINUE, + token4, token4_size); + request->phase = 2; break; case PASSDB_RESULT_INTERNAL_FAILURE: auth_request_internal_failure(auth_request); @@ -463,6 +462,7 @@ auth_request_fail(auth_request); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/mech-skey.c --- a/src/auth/mech-skey.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/mech-skey.c Mon May 31 18:07:48 2010 +0100 @@ -61,7 +61,7 @@ answer, strlen(answer)); } -static void +static bool otp_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -77,9 +77,10 @@ auth_request_fail(auth_request); break; } + return TRUE; } -static void +static bool skey_credentials_callback(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *auth_request) @@ -97,6 +98,7 @@ otp_credentials_callback); break; } + return TRUE; } static void diff -r c4f56ed9dae0 src/auth/passdb-blocking.c --- a/src/auth/passdb-blocking.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-blocking.c Mon May 31 18:07:48 2010 +0100 @@ -1,6 +1,7 @@ /* Copyright (c) 2005-2010 Dovecot authors, see the included COPYING file */ #include "auth-common.h" +#include "array.h" #include "str.h" #include "auth-worker-server.h" #include "password-scheme.h" @@ -13,8 +14,12 @@ auth_worker_reply_parse_args(struct auth_request *request, const char *const *args) { - if (**args != '\0') - request->passdb_password = p_strdup(request->pool, *args); + char *password; + + for (; **args != '\0'; args++) { + password = p_strdup(request->pool, *args); + array_append(&request->passdb_passwords, &password, 1); + } args++; if (*args != NULL) { @@ -33,14 +38,14 @@ args = t_strsplit(reply, "\t"); if (strcmp(*args, "OK") == 0 && args[1] != NULL && args[2] != NULL) { - /* OK \t user \t password [\t extra] */ + /* OK \t user <\t password>* [\t extra] */ auth_request_set_field(request, "user", args[1], NULL); auth_worker_reply_parse_args(request, args + 2); return PASSDB_RESULT_OK; } if (strcmp(*args, "FAIL") == 0 && args[1] != NULL) { - /* FAIL \t result [\t user \t password [\t extra]] */ + /* FAIL \t result [\t user <\t password>* [\t extra]] */ ret = atoi(args[1]); if (ret == PASSDB_RESULT_OK) { /* shouldn't happen */ @@ -95,23 +100,33 @@ { struct auth_request *request = context; enum passdb_result result; + char *const *passwords; + unsigned int i, count; const char *password = NULL, *scheme = NULL; - result = auth_worker_reply_parse(request, reply); - if (result == PASSDB_RESULT_OK && request->passdb_password != NULL) { - password = request->passdb_password; + passwords = array_get(&request->passdb_passwords, &count); + if (result != PASSDB_RESULT_OK) + count = 0; + + for (i = 0; i < count; i++) { + password = passwords[i]; scheme = password_get_scheme(&password); if (scheme == NULL) { auth_request_log_error(request, "blocking", "Received reply from worker without " "password scheme"); result = PASSDB_RESULT_INTERNAL_FAILURE; + continue; } + if (passdb_handle_credentials(PASSDB_RESULT_OK, password, scheme, + auth_request_lookup_credentials_callback, + request)) + return; } - passdb_handle_credentials(result, password, scheme, - auth_request_lookup_credentials_callback, - request); + passdb_handle_credentials_last(result, password, scheme, + auth_request_lookup_credentials_callback, + request); auth_request_unref(&request); return TRUE; } diff -r c4f56ed9dae0 src/auth/passdb-cache.c --- a/src/auth/passdb-cache.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-cache.c Mon May 31 18:07:48 2010 +0100 @@ -1,6 +1,7 @@ /* Copyright (c) 2004-2010 Dovecot authors, see the included COPYING file */ #include "auth-common.h" +#include "array.h" #include "password-scheme.h" #include "passdb.h" #include "passdb-cache.h" @@ -35,7 +36,7 @@ if (passdb_cache == NULL || key == NULL) return FALSE; - /* value = password \t ... */ + /* value = * ... */ value = auth_cache_lookup(passdb_cache, request, key, &node, &expired, &neg_expired); if (value == NULL || (expired && !use_expired)) { @@ -54,26 +55,32 @@ list = t_strsplit(value, "\t"); - cached_pw = list[0]; - if (*cached_pw == '\0') { + if (*list[0] == '\0') { /* NULL password */ auth_request_log_info(request, "cache", "NULL password access"); ret = 1; } else { - scheme = password_get_scheme(&cached_pw); - i_assert(scheme != NULL); + /* try all cached passwords */ + do { + cached_pw = list[0]; + list++; - ret = auth_request_password_verify(request, password, cached_pw, - scheme, "cache"); + scheme = password_get_scheme(&cached_pw); + i_assert(scheme != NULL); - if (ret == 0 && (node->last_success || neg_expired)) { - /* a) the last authentication was successful. assume - that the password was changed and cache is expired. - b) negative TTL reached, use it for password - mismatches too. */ - node->last_success = FALSE; - return FALSE; - } + ret = auth_request_password_verify(request, password, + cached_pw, scheme, + "cache"); + } while (ret <= 0 && *list[0] != '\0'); + } + + if (ret <= 0 && (node->last_success || neg_expired)) { + /* a) the last authentication was successful. assume + that the password was changed and cache is expired. + b) negative TTL reached, use it for password + mismatches too. */ + node->last_success = FALSE; + return FALSE; } node->last_success = ret > 0; @@ -87,13 +94,14 @@ } bool passdb_cache_lookup_credentials(struct auth_request *request, - const char *key, const char **password_r, - const char **scheme_r, + const char *key, + ARRAY_TYPE(const_string) *passwords_r, enum passdb_result *result_r, bool use_expired) { const char *value, *const *list; struct auth_cache_node *node; + unsigned int i; bool expired, neg_expired; if (passdb_cache == NULL) @@ -110,19 +118,25 @@ if (*value == '\0') { /* negative cache entry */ + t_array_init(passwords_r, 1); *result_r = PASSDB_RESULT_USER_UNKNOWN; - *password_r = NULL; - *scheme_r = NULL; return TRUE; } list = t_strsplit(value, "\t"); - auth_request_set_fields(request, list + 1, NULL); + /* list begins with passwords and continues until an empty field */ + for (i = 0; *list[i] != '\0'; i++) ; + + t_array_init(passwords_r, i + 1); + for (i = 0; *list[i] != '\0'; i++) + array_append(passwords_r, &list[i], 1); + + auth_request_set_fields(request, list + i + 1, NULL); *result_r = PASSDB_RESULT_OK; - *password_r = *list[0] == '\0' ? NULL : list[0]; - *scheme_r = password_get_scheme(password_r); - i_assert(*scheme_r != NULL || *password_r == NULL); + + /*FIXME: if (i == 0) + *result_r = PASSDB_RESULT_SCHEME_NOT_AVAILABLE;*/ return TRUE; } diff -r c4f56ed9dae0 src/auth/passdb-cache.h --- a/src/auth/passdb-cache.h Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-cache.h Mon May 31 18:07:48 2010 +0100 @@ -10,8 +10,8 @@ const char *password, enum passdb_result *result_r, int use_expired); bool passdb_cache_lookup_credentials(struct auth_request *request, - const char *key, const char **password_r, - const char **scheme_r, + const char *key, + ARRAY_TYPE(const_string) *passwords_r, enum passdb_result *result_r, bool use_expired); diff -r c4f56ed9dae0 src/auth/passdb-ldap.c --- a/src/auth/passdb-ldap.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-ldap.c Mon May 31 18:07:48 2010 +0100 @@ -5,6 +5,7 @@ #if defined(PASSDB_LDAP) && (defined(BUILTIN_LDAP) || defined(PLUGIN_BUILD)) +#include "array.h" #include "ioloop.h" #include "hash.h" #include "str.h" @@ -59,9 +60,10 @@ LDAPMessage *res) { enum passdb_result passdb_result; - const char *password = NULL, *scheme; - int ret; + char *const *passwords; + unsigned int i, count; + passwords = array_get(&auth_request->passdb_passwords, &count); if (res == NULL) { passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; } else if (ldap_request->entries == 0) { @@ -72,38 +74,67 @@ auth_request_log_error(auth_request, "ldap", "pass_filter matched multiple objects, aborting"); passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; - } else if (auth_request->passdb_password == NULL && - !auth_request->no_password) { + } else if (count == 0 && !auth_request->no_password) { auth_request_log_info(auth_request, "ldap", "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; + for (i = 0; i < count; i++) { + if (ldap_check_password(ldap_request, passwords[i])) + return; + } + passdb_result = PASSDB_RESULT_LAST_FAILURE; } + if (auth_request->credentials_scheme != NULL) { + passdb_handle_credentials_last(passdb_result, NULL, NULL, + ldap_request->callback.lookup_credentials, + auth_request); + } else { + ldap_request->callback.verify_plain(passdb_result, + auth_request); + } +} + +static bool +ldap_check_password(struct passdb_ldap_request *ldap_request, + const char *password) +{ + struct auth_request *auth_request = + ldap_request->request.ldap.auth_request; + const char *scheme; + + /* passdb_passwords may be cleared on the way, + so we'll need to strdup. */ + password = t_strdup(password); + 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); + lookup_credentials_callback_t *callback = + ldap_request->callback.lookup_credentials; + + if (passdb_handle_credentials(PASSDB_RESULT_OK, password, + scheme, callback, auth_request)) + return TRUE; + } else if (password != NULL) { + if (auth_request_password_verify(auth_request, + auth_request->mech_password, + password, scheme, "ldap") > 0) + return TRUE; } else { - if (password != NULL) { - ret = auth_request_password_verify(auth_request, - auth_request->mech_password, - password, scheme, "ldap"); - passdb_result = ret > 0 ? PASSDB_RESULT_OK : - PASSDB_RESULT_PASSWORD_MISMATCH; + if (auth_request->no_password) { + /* all passwords are valid */ + return TRUE; } - ldap_request->callback.verify_plain(passdb_result, - auth_request); + auth_request_log_info(auth_request, "ldap", + "Empty password returned without nopassword"); } + return FALSE; } static void diff -r c4f56ed9dae0 src/auth/passdb-passwd-file.c --- a/src/auth/passdb-passwd-file.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-passwd-file.c Mon May 31 18:07:48 2010 +0100 @@ -104,9 +104,8 @@ } passwd_file_save_results(request, pu, &crypted_pass, &scheme); - - passdb_handle_credentials(PASSDB_RESULT_OK, crypted_pass, scheme, - callback, request); + passdb_handle_credentials_last(PASSDB_RESULT_OK, crypted_pass, scheme, + callback, request); } static struct passdb_module * diff -r c4f56ed9dae0 src/auth/passdb-sql.c --- a/src/auth/passdb-sql.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-sql.c Mon May 31 18:07:48 2010 +0100 @@ -5,12 +5,14 @@ #ifdef PASSDB_SQL +#include "array.h" #include "str.h" #include "strescape.h" #include "var-expand.h" #include "safe-memset.h" #include "password-scheme.h" #include "auth-cache.h" +#include "passdb-cache.h" #include "db-sql.h" #include @@ -52,78 +54,146 @@ } } -static void sql_query_callback(struct sql_result *result, - struct passdb_sql_request *sql_request) +static int sql_query_next_row(struct sql_result *result, + struct passdb_sql_request *sql_request) { struct auth_request *auth_request = sql_request->auth_request; - enum passdb_result passdb_result; - const char *password, *scheme; + struct passdb_module *_module = auth_request->passdb->passdb; + struct sql_passdb_module *module = (struct sql_passdb_module *)_module; int ret; - passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; - password = NULL; + ret = sql_result_next_row(result); + if (ret <= 0) { + if (ret == 0) + return 0; - ret = sql_result_next_row(result); - if (ret < 0) { auth_request_log_error(auth_request, "sql", "Password query failed: %s", sql_result_get_error(result)); - } else if (ret == 0) { - auth_request_log_info(auth_request, "sql", "unknown user"); - passdb_result = PASSDB_RESULT_USER_UNKNOWN; - } else { - sql_query_save_results(result, sql_request); + return -1; + } - /* Note that we really want to check if the password field is - found. Just checking if password is set isn't enough, - because with proxies we might want to return NULL as - password. */ - if (sql_result_find_field(result, "password") < 0) { - auth_request_log_error(auth_request, "sql", - "Password query must return a field named " - "'password'"); - } else if (sql_result_next_row(result) > 0) { - auth_request_log_error(auth_request, "sql", - "Password query returned multiple matches"); - } else if (auth_request->passdb_password == NULL && - !auth_request->no_password) { - auth_request_log_info(auth_request, "sql", - "Empty password returned without 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; + sql_query_save_results(result, sql_request); + + /* Note that we really want to check if the password field is + found. Just checking if password is set isn't enough, + because with proxies we might want to return NULL as + password. */ + if (sql_result_find_field(result, "password") < 0) { + auth_request_log_error(auth_request, "sql", + "Password query must return a field named 'password'"); + return -1; + } + + if (!module->conn->set.allow_multiple_rows) { + if (sql_result_next_row(result) > 0) { + auth_request_log_error(auth_request, "sql", + "Password query returned multiple matches " + "and allow_multiple_rows=no"); + return -1; } } + return 1; +} + +static bool +sql_check_password(struct passdb_sql_request *sql_request, const char *password, + enum passdb_result passdb_result) +{ + struct auth_request *auth_request = sql_request->auth_request; + const char *scheme; 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, - sql_request->callback.lookup_credentials, - auth_request); - auth_request_unref(&auth_request); - return; + lookup_credentials_callback_t *callback = + sql_request->callback.lookup_credentials; + + if (passdb_handle_credentials(passdb_result, password, + scheme, callback, auth_request)) + return TRUE; + } else if (password != NULL) { + if (auth_request_password_verify(auth_request, + auth_request->mech_password, + password, scheme, "sql") > 0) + return TRUE; + } else { + if (auth_request->no_password) { + /* all passwords are valid */ + return TRUE; + } + + auth_request_log_info(auth_request, "sql", + "Empty password returned without nopassword"); + } + return FALSE; +} + +static void sql_query_callback(struct sql_result *result, + struct passdb_sql_request *sql_request) +{ + struct auth_request *auth_request = sql_request->auth_request; + enum passdb_result passdb_result; + const char *user, *password; + char *const *passwords; + unsigned int count, row; + int ret; + bool send_last = FALSE; + + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + + user = auth_request->user; + for (row = 0;; row++) { + ret = sql_query_next_row(result, sql_request); + if (ret == 0 && row > 0) { + /* no more rows. */ + send_last = TRUE; + passdb_result = PASSDB_RESULT_PASSWORD_MISMATCH; + break; + } + + switch (ret) { + case 1: + /* passdb_passwords may be cleared on the way, + so we'll need to strdup. */ + passwords = array_get(&auth_request->passdb_passwords, + &count); + i_assert(count <= 1); + password = count == 0 ? NULL : t_strdup(passwords[0]); + passdb_result = PASSDB_RESULT_OK; + break; + case 0: + auth_request_log_info(auth_request, "sql", + "unknown user"); + passdb_result = PASSDB_RESULT_USER_UNKNOWN; + password = NULL; + break; + default: + passdb_result = PASSDB_RESULT_INTERNAL_FAILURE; + password = NULL; + break; + } + + if (sql_check_password(sql_request, password, passdb_result)) + break; + /* see if there's another row */ + auth_request_reset_passdb_lookup(auth_request); } - /* verify plain */ - if (password == NULL) { + if (auth_request->credentials_scheme == NULL) sql_request->callback.verify_plain(passdb_result, auth_request); - auth_request_unref(&auth_request); - return; + else if (send_last) { + lookup_credentials_callback_t *callback = + sql_request->callback.lookup_credentials; + + if (!passdb_handle_credentials(PASSDB_RESULT_LAST_FAILURE, + NULL, NULL, callback, + auth_request)) + i_unreached(); } - ret = auth_request_password_verify(auth_request, - auth_request->mech_password, - password, scheme, "sql"); - - sql_request->callback.verify_plain(ret > 0 ? PASSDB_RESULT_OK : - PASSDB_RESULT_PASSWORD_MISMATCH, - auth_request); auth_request_unref(&auth_request); } @@ -247,6 +317,11 @@ flags = sql_get_flags(module->conn->db); module->module.blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0; + if (module->conn->set.allow_multiple_rows && passdb_cache != NULL) { + i_fatal("sql: allow_multiple_rows=yes " + "is incompatible with auth cache"); + } + if (!module->module.blocking || worker) sql_connect(module->conn->db); } diff -r c4f56ed9dae0 src/auth/passdb-vpopmail.c --- a/src/auth/passdb-vpopmail.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb-vpopmail.c Mon May 31 18:07:48 2010 +0100 @@ -102,8 +102,8 @@ return; } - passdb_handle_credentials(PASSDB_RESULT_OK, password, "CLEARTEXT", - callback, request); + passdb_handle_credentials_last(PASSDB_RESULT_OK, password, "CLEARTEXT", + callback, request); safe_memset(password, 0, strlen(password)); } diff -r c4f56ed9dae0 src/auth/passdb.c --- a/src/auth/passdb.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb.c Mon May 31 18:07:48 2010 +0100 @@ -129,7 +129,7 @@ return TRUE; } -void passdb_handle_credentials(enum passdb_result result, +bool passdb_handle_credentials(enum passdb_result result, const char *password, const char *scheme, lookup_credentials_callback_t *callback, struct auth_request *auth_request) @@ -137,10 +137,8 @@ const unsigned char *credentials = NULL; size_t size = 0; - if (result != PASSDB_RESULT_OK) { - callback(result, NULL, 0, auth_request); - return; - } + if (result != PASSDB_RESULT_OK) + return callback(result, NULL, 0, auth_request); if (password == NULL) { auth_request_log_info(auth_request, "password", @@ -152,7 +150,21 @@ result = PASSDB_RESULT_SCHEME_NOT_AVAILABLE; } - callback(result, credentials, size, auth_request); + return callback(result, credentials, size, auth_request); +} + +void passdb_handle_credentials_last(enum passdb_result result, + const char *password, const char *scheme, + lookup_credentials_callback_t *callback, + struct auth_request *auth_request) +{ + if (!passdb_handle_credentials(result, password, scheme, + callback, auth_request)) { + if (!passdb_handle_credentials(PASSDB_RESULT_LAST_FAILURE, + NULL, NULL, callback, + auth_request)) + i_unreached(); + } } static struct passdb_module * diff -r c4f56ed9dae0 src/auth/passdb.h --- a/src/auth/passdb.h Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/passdb.h Mon May 31 18:07:48 2010 +0100 @@ -15,6 +15,7 @@ PASSDB_RESULT_USER_UNKNOWN = -3, PASSDB_RESULT_USER_DISABLED = -4, PASSDB_RESULT_PASS_EXPIRED = -5, + PASSDB_RESULT_LAST_FAILURE = -6, PASSDB_RESULT_PASSWORD_MISMATCH = 0, PASSDB_RESULT_OK = 1 @@ -22,7 +23,11 @@ typedef void verify_plain_callback_t(enum passdb_result result, struct auth_request *request); -typedef void lookup_credentials_callback_t(enum passdb_result result, +/* Returns TRUE if successful, FALSE if more credentials are wanted + (ie. support for multiple passwords). If FALSE is returned, the caller must + call this function again. If there are no more results, + result=PASSDB_RESULT_END_OF_LIST */ +typedef bool lookup_credentials_callback_t(enum passdb_result result, const unsigned char *credentials, size_t size, struct auth_request *request); @@ -82,10 +87,15 @@ const unsigned char **credentials_r, size_t *size_r); -void passdb_handle_credentials(enum passdb_result result, +bool passdb_handle_credentials(enum passdb_result result, const char *password, const char *scheme, lookup_credentials_callback_t *callback, - struct auth_request *auth_request); + struct auth_request *auth_request) + ATTR_WARN_UNUSED_RESULT; +void passdb_handle_credentials_last(enum passdb_result result, + const char *password, const char *scheme, + lookup_credentials_callback_t *callback, + struct auth_request *auth_request); struct passdb_module * passdb_preinit(pool_t pool, const char *driver, const char *args); diff -r c4f56ed9dae0 src/auth/userdb-sql.c --- a/src/auth/userdb-sql.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/userdb-sql.c Mon May 31 18:07:48 2010 +0100 @@ -9,6 +9,7 @@ #include "strescape.h" #include "var-expand.h" #include "auth-cache.h" +#include "passdb-cache.h" #include "db-sql.h" #include @@ -247,6 +248,11 @@ flags = sql_get_flags(module->conn->db); _module->blocking = (flags & SQL_DB_FLAG_BLOCKING) != 0; + if (module->conn->set.allow_multiple_rows && passdb_cache != NULL) { + i_fatal("sql: allow_multiple_rows=yes " + "is incompatible with auth cache"); + } + if (!_module->blocking || worker) sql_connect(module->conn->db); } diff -r c4f56ed9dae0 src/auth/userdb-static.c --- a/src/auth/userdb-static.c Mon May 31 16:46:08 2010 +0100 +++ b/src/auth/userdb-static.c Mon May 31 18:07:48 2010 +0100 @@ -148,7 +148,7 @@ callback(USERDB_RESULT_OK, auth_request); } -static void +static bool static_credentials_callback(enum passdb_result result, const unsigned char *credentials ATTR_UNUSED, size_t size ATTR_UNUSED, @@ -180,6 +180,7 @@ } i_free(ctx); + return TRUE; } static void static_lookup(struct auth_request *auth_request,