]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth: Added auth failure penalty tracking based on remote IP address.
authorTimo Sirainen <tss@iki.fi>
Tue, 10 Nov 2009 20:08:24 +0000 (15:08 -0500)
committerTimo Sirainen <tss@iki.fi>
Tue, 10 Nov 2009 20:08:24 +0000 (15:08 -0500)
--HG--
branch : HEAD

src/auth/Makefile.am
src/auth/auth-penalty.c [new file with mode: 0644]
src/auth/auth-penalty.h [new file with mode: 0644]
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

index afa3c27c7a9ceb3cede3b201e68def56833270bb..ea303dcf52c3ce40b776d94069266c03f723225a 100644 (file)
@@ -56,6 +56,7 @@ auth_SOURCES = \
        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 @@ headers = \
        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 \
diff --git a/src/auth/auth-penalty.c b/src/auth/auth-penalty.c
new file mode 100644 (file)
index 0000000..faf6e0d
--- /dev/null
@@ -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;
+}
diff --git a/src/auth/auth-penalty.h b/src/auth/auth-penalty.h
new file mode 100644 (file)
index 0000000..96783e4
--- /dev/null
@@ -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
index 20956f5ceadc72ddb87bf37e889d31a059f59450..8917e918747a1d7851e74632a2d87ffbaaa7c52e 100644 (file)
@@ -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"
 #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 @@ auth_request_handler_create(struct auth *auth,
        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 @@ void auth_request_handler_unref(struct auth_request_handler **_handler)
        /* 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 @@ auth_request_handle_failure(struct auth_request *request,
        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 @@ static void auth_callback(struct auth_request *request,
        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 @@ static void auth_request_handler_auth_fail(struct auth_request_handler *handler,
        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 @@ bool auth_request_handler_auth_begin(struct auth_request_handler *handler,
        /* 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 @@ bool auth_request_handler_auth_begin(struct auth_request_handler *handler,
                                "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;
 }
 
index 3343d7939a7eebd3a6c75a581ae33e763b122c89..464a49f43b71ee8fab5b14367f04081f7a03e780 100644 (file)
@@ -110,6 +110,9 @@ void auth_request_unref(struct auth_request **_request)
        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 @@ bool auth_request_import(struct auth_request *request,
        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,
index b02ff15a2ffa241efdd60d7f8ea79db2ee61fec9..9821759e76f4a2b47d1b026af75363aaec475481 100644 (file)
@@ -68,6 +68,11 @@ struct auth_request {
        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 @@ void auth_request_export(struct auth_request *request,
 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);
 
index 46d8a07ce34d3aaf7a428e3fe49ad796828477a3..b86b6f3036d8a6cbf21e0d1082474d0572d1d62e 100644 (file)
@@ -9,6 +9,7 @@ enum auth_client_result {
        AUTH_CLIENT_RESULT_FAILURE
 };
 
+struct auth_settings;
 struct auth_request;
 
 typedef void mech_callback_t(struct auth_request *request,
index 7b8b3f46fa19974a4b773bb6da8980f44898a635..21b5ab8d60eaf9d7d22ad99c5c4542124ce89b67 100644 (file)
@@ -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,
index 91fa6f61354a7e5924fb4987aa8ef8286675d8d8..b6ebf4a3064f43c447dc61663281a28cc2d1975a 100644 (file)
@@ -3,7 +3,10 @@
 
 #include "auth-stream.h"
 
+struct auth;
 struct auth_request;
+struct auth_userdb;
+struct auth_userdb_settings;
 
 enum userdb_result {
        USERDB_RESULT_INTERNAL_FAILURE = -1,