Mercurial > dovecot > core-2.2
changeset 10301:fbff8ca77d2e HEAD
auth: Added auth failure penalty tracking based on remote IP address.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Tue, 10 Nov 2009 15:08:24 -0500 |
parents | 13da60f934da |
children | 7d9cd9b7da08 |
files | src/auth/Makefile.am src/auth/auth-penalty.c src/auth/auth-penalty.h src/auth/auth-request-handler.c src/auth/auth-request.c src/auth/auth-request.h src/auth/mech.h src/auth/passdb.h src/auth/userdb.h |
diffstat | 9 files changed, 226 insertions(+), 14 deletions(-) [+] |
line wrap: on
line diff
--- a/src/auth/Makefile.am Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/Makefile.am Tue Nov 10 15:08:24 2009 -0500 @@ -56,6 +56,7 @@ auth-master-connection.c \ mech-otp-skey-common.c \ mech-plain-common.c \ + auth-penalty.c \ auth-request.c \ auth-request-handler.c \ auth-settings.c \ @@ -112,6 +113,7 @@ auth-master-connection.h \ mech-otp-skey-common.h \ mech-plain-common.h \ + auth-penalty.h \ auth-request.h \ auth-request-handler.h \ auth-settings.h \
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/auth-penalty.c Tue Nov 10 15:08:24 2009 -0500 @@ -0,0 +1,131 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "ioloop.h" +#include "network.h" +#include "anvil-client.h" +#include "auth-request.h" +#include "auth-penalty.h" + +#include <stdio.h> + +struct auth_penalty_request { + struct auth_request *auth_request; + auth_penalty_callback_t *callback; +}; + +struct auth_penalty { + struct anvil_client *client; + + unsigned int disabled:1; +}; + +struct auth_penalty *auth_penalty_init(const char *path) +{ + struct auth_penalty *penalty; + + penalty = i_new(struct auth_penalty, 1); + penalty->client = anvil_client_init(path, NULL, + ANVIL_CLIENT_FLAG_HIDE_ENOENT); + if (anvil_client_connect(penalty->client, TRUE) < 0) + penalty->disabled = TRUE; + else { + anvil_client_cmd(penalty->client, t_strdup_printf( + "PENALTY-SET-EXPIRE-SECS\t%u", AUTH_PENALTY_TIMEOUT)); + } + return penalty; +} + +void auth_penalty_deinit(struct auth_penalty **_penalty) +{ + struct auth_penalty *penalty = *_penalty; + + *_penalty = NULL; + anvil_client_deinit(&penalty->client); + i_free(penalty); +} + +unsigned int auth_penalty_to_secs(unsigned int penalty) +{ + unsigned int i, secs = AUTH_PENALTY_INIT_SECS; + + for (i = 0; i < penalty; i++) + secs *= 2; + return secs < AUTH_PENALTY_MAX_SECS ? secs : AUTH_PENALTY_MAX_SECS; +} + +static void auth_penalty_anvil_callback(const char *reply, void *context) +{ + struct auth_penalty_request *request = context; + unsigned int penalty = 0; + unsigned long last_update = 0; + unsigned int secs, drop_penalty; + + if (reply == NULL) { + /* internal failure */ + } else if (sscanf(reply, "%u %lu", &penalty, &last_update) != 2) { + i_error("Invalid PENALTY-GET reply: %s", reply); + } else { + if ((time_t)last_update > ioloop_time) { + /* time moved backwards? */ + last_update = ioloop_time; + } + + /* update penalty. */ + drop_penalty = AUTH_PENALTY_MAX_PENALTY; + while (penalty > 0) { + secs = auth_penalty_to_secs(drop_penalty); + if (ioloop_time - last_update < secs) + break; + drop_penalty--; + penalty--; + } + } + + request->callback(penalty, request->auth_request); +} + +void auth_penalty_lookup(struct auth_penalty *penalty, + struct auth_request *auth_request, + auth_penalty_callback_t *callback) +{ + struct auth_penalty_request *request; + const char *ident; + + ident = net_ip2addr(&auth_request->remote_ip); + if (penalty->disabled || ident == NULL) { + callback(0, auth_request); + return; + } + + request = i_new(struct auth_penalty_request, 1); + request->auth_request = auth_request; + request->callback = callback; + + T_BEGIN { + anvil_client_query(penalty->client, + t_strdup_printf("PENALTY-GET\t%s", ident), + auth_penalty_anvil_callback, request); + } T_END; +} + +void auth_penalty_update(struct auth_penalty *penalty, + struct auth_request *auth_request, unsigned int value) +{ + const char *ident; + + ident = net_ip2addr(&auth_request->remote_ip); + if (penalty->disabled || ident == NULL) + return; + + if (value > AUTH_PENALTY_MAX_PENALTY) { + /* even if the actual value doesn't change, the last_change + timestamp does. */ + value = AUTH_PENALTY_MAX_PENALTY; + } + T_BEGIN { + const char *cmd = + t_strdup_printf("PENALTY-SET\t%s\t%u", ident, value); + anvil_client_cmd(penalty->client, cmd); + } T_END; +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/auth/auth-penalty.h Tue Nov 10 15:08:24 2009 -0500 @@ -0,0 +1,28 @@ +#ifndef AUTH_PENALTY_H +#define AUTH_PENALTY_H + +struct auth_request; + +#define AUTH_PENALTY_INIT_SECS 2 +#define AUTH_PENALTY_MAX_SECS 15 +/* timeout specifies how long it takes for penalty to be irrelevant. */ +#define AUTH_PENALTY_TIMEOUT \ + (AUTH_PENALTY_INIT_SECS + 4 + 8 + AUTH_PENALTY_MAX_SECS) +#define AUTH_PENALTY_MAX_PENALTY 4 + +/* If lookup failed, penalty and last_update are both zero */ +typedef void auth_penalty_callback_t(unsigned int penalty, + struct auth_request *request); + +struct auth_penalty *auth_penalty_init(const char *path); +void auth_penalty_deinit(struct auth_penalty **penalty); + +unsigned int auth_penalty_to_secs(unsigned int penalty); + +void auth_penalty_lookup(struct auth_penalty *penalty, + struct auth_request *auth_request, + auth_penalty_callback_t *callback); +void auth_penalty_update(struct auth_penalty *penalty, + struct auth_request *auth_request, unsigned int value); + +#endif
--- a/src/auth/auth-request-handler.c Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/auth-request-handler.c Tue Nov 10 15:08:24 2009 -0500 @@ -8,6 +8,7 @@ #include "hash.h" #include "str.h" #include "str-sanitize.h" +#include "auth-penalty.h" #include "auth-request.h" #include "auth-master-connection.h" #include "auth-request-handler.h" @@ -15,11 +16,13 @@ #include <stdlib.h> #define AUTH_FAILURE_DELAY_CHECK_MSECS 500 +#define AUTH_PENALTY_ANVIL_PATH "anvil-auth-penalty" struct auth_request_handler { int refcount; pool_t pool; struct hash_table *requests; + struct auth_penalty *penalty; struct auth *auth; unsigned int connect_uid, client_pid; @@ -55,6 +58,7 @@ handler->callback = callback; handler->context = context; handler->master_callback = master_callback; + handler->penalty = auth_penalty_init(AUTH_PENALTY_ANVIL_PATH); return handler; } @@ -80,6 +84,7 @@ /* notify parent that we're done with all requests */ handler->callback(NULL, handler->context); + auth_penalty_deinit(&handler->penalty); hash_table_destroy(&handler->requests); pool_unref(&handler->pool); } @@ -188,6 +193,9 @@ request->delayed_failure = TRUE; handler->refcount++; + auth_penalty_update(handler->penalty, request, + request->last_penalty + 1); + request->last_access = ioloop_time; aqueue_append(auth_failures, &request); if (to_auth_failures == NULL) { @@ -221,6 +229,11 @@ case AUTH_CLIENT_RESULT_SUCCESS: auth_request_proxy_finish(request, TRUE); + if (request->last_penalty != 0) { + /* reset penalty */ + auth_penalty_update(handler->penalty, request, 0); + } + auth_stream_reply_add(reply, "OK", NULL); auth_stream_reply_add(reply, NULL, dec2str(request->id)); auth_stream_reply_add(reply, "user", request->user); @@ -281,14 +294,36 @@ auth_request_handler_remove(handler, request); } +static void auth_request_penalty_finish(struct auth_request *request) +{ + timeout_remove(&request->to_penalty); + auth_request_initial(request); +} + +static void +auth_penalty_callback(unsigned int penalty, struct auth_request *request) +{ + unsigned int secs; + + request->last_penalty = penalty; + + if (penalty == 0) + auth_request_initial(request); + else { + secs = auth_penalty_to_secs(penalty); + request->to_penalty = timeout_add(secs * 1000, + auth_request_penalty_finish, + request); + } +} + bool auth_request_handler_auth_begin(struct auth_request_handler *handler, const char *args) { const struct mech_module *mech; struct auth_request *request; const char *const *list, *name, *arg, *initial_resp; - const void *initial_resp_data; - size_t initial_resp_len; + void *initial_resp_data; unsigned int id; buffer_t *buf; @@ -365,11 +400,9 @@ /* Empty initial response is a "=" base64 string. Completely empty string shouldn't really be sent, but at least Exim does it, so just allow it for backwards compatibility.. */ - if (initial_resp == NULL || *initial_resp == '\0') { - initial_resp_data = NULL; - initial_resp_len = 0; - } else { + if (initial_resp != NULL && *initial_resp != '\0') { size_t len = strlen(initial_resp); + buf = buffer_create_dynamic(pool_datastack_create(), MAX_BASE64_DECODED_SIZE(len)); if (base64_decode(initial_resp, len, NULL, buf) < 0) { @@ -377,13 +410,18 @@ "Invalid base64 data in initial response"); return TRUE; } - initial_resp_data = buf->data; - initial_resp_len = buf->used; + initial_resp_data = + p_malloc(request->pool, I_MAX(buf->used, 1)); + memcpy(initial_resp_data, buf->data, buf->used); + request->initial_response = initial_resp_data; + request->initial_response_len = buf->used; } /* handler is referenced until auth_callback is called. */ handler->refcount++; - auth_request_initial(request, initial_resp_data, initial_resp_len); + + /* before we start authenticating, see if we need to wait first */ + auth_penalty_lookup(handler->penalty, request, auth_penalty_callback); return TRUE; }
--- a/src/auth/auth-request.c Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/auth-request.c Tue Nov 10 15:08:24 2009 -0500 @@ -110,6 +110,9 @@ if (--request->refcount > 0) return; + if (request->to_penalty != NULL) + timeout_remove(&request->to_penalty); + if (request->mech != NULL) request->mech->auth_free(request); else @@ -198,13 +201,13 @@ return TRUE; } -void auth_request_initial(struct auth_request *request, - const unsigned char *data, size_t data_size) +void auth_request_initial(struct auth_request *request) { i_assert(request->state == AUTH_REQUEST_STATE_NEW); request->state = AUTH_REQUEST_STATE_MECH_CONTINUE; - request->mech->auth_initial(request, data, data_size); + request->mech->auth_initial(request, request->initial_response, + request->initial_response_len); } void auth_request_continue(struct auth_request *request,
--- a/src/auth/auth-request.h Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/auth-request.h Tue Nov 10 15:08:24 2009 -0500 @@ -68,6 +68,11 @@ struct ip_addr local_ip, remote_ip; unsigned int local_port, remote_port; + struct timeout *to_penalty; + unsigned int last_penalty; + unsigned int initial_response_len; + const unsigned char *initial_response; + union { verify_plain_callback_t *verify_plain; lookup_credentials_callback_t *lookup_credentials; @@ -121,8 +126,7 @@ bool auth_request_import(struct auth_request *request, const char *key, const char *value); -void auth_request_initial(struct auth_request *request, - const unsigned char *data, size_t data_size); +void auth_request_initial(struct auth_request *request); void auth_request_continue(struct auth_request *request, const unsigned char *data, size_t data_size);
--- a/src/auth/mech.h Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/mech.h Tue Nov 10 15:08:24 2009 -0500 @@ -9,6 +9,7 @@ AUTH_CLIENT_RESULT_FAILURE }; +struct auth_settings; struct auth_request; typedef void mech_callback_t(struct auth_request *request,
--- a/src/auth/passdb.h Tue Nov 10 15:07:49 2009 -0500 +++ b/src/auth/passdb.h Tue Nov 10 15:08:24 2009 -0500 @@ -5,6 +5,8 @@ ((pass)[0] != '\0' && (pass)[0] != '*' && (pass)[0] != '!') struct auth_request; +struct auth_passdb; +struct auth_passdb_settings; enum passdb_result { PASSDB_RESULT_INTERNAL_FAILURE = -1,