]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
auth/anvil: Penalty is no longer increased if the same user+pass combination was...
authorTimo Sirainen <tss@iki.fi>
Sat, 20 Feb 2010 06:48:54 +0000 (08:48 +0200)
committerTimo Sirainen <tss@iki.fi>
Sat, 20 Feb 2010 06:48:54 +0000 (08:48 +0200)
This should avoid penalty increasing for IPs where a user's misconfigured
client tries to keep authenticating with wrong user/pass. This check works
only for plaintext authentication.

Currently the code that keeps track of what user/passwords have been tried
is pretty simple, and hardcoded to remember max. 10 of them.

--HG--
branch : HEAD

src/anvil/Makefile.am
src/anvil/anvil-connection.c
src/anvil/penalty.c
src/anvil/penalty.h
src/anvil/test-penalty.c [new file with mode: 0644]
src/auth/auth-penalty.c

index 6857abe4c0fc8a699df20b2780ffe64aebc85954..85fa434c796debfe7cc7aedca26e170c67cd26ec 100644 (file)
@@ -4,6 +4,7 @@ pkglibexec_PROGRAMS = anvil
 
 AM_CPPFLAGS = \
        -I$(top_srcdir)/src/lib \
+       -I$(top_srcdir)/src/lib-test \
        -I$(top_srcdir)/src/lib-settings \
        -I$(top_srcdir)/src/lib-master
 
@@ -25,3 +26,22 @@ noinst_HEADERS = \
        common.h \
        connect-limit.h \
        penalty.h
+
+test_programs = \
+       test-penalty
+
+noinst_PROGRAMS = $(test_programs)
+
+test_libs = \
+       ../lib-test/libtest.la \
+       ../lib/liblib.la
+
+test_penalty_SOURCES = test-penalty.c
+test_penalty_LDADD = penalty.lo $(test_libs)
+test_penalty_DEPENDENCIES = penalty.lo $(test_libs)
+
+check: check-am check-test
+check-test: all-am
+       for bin in $(test_programs); do \
+         if ! ./$$bin; then exit 1; fi; \
+       done
index 5c5c8b1d035ef4211210318590328d7e69762a2b..620adc88c487589fe456cd55439676df3628058c 100644 (file)
@@ -47,7 +47,7 @@ anvil_connection_request(struct anvil_connection *conn,
                         const char *const *args, const char **error_r)
 {
        const char *cmd = args[0];
-       unsigned int value;
+       unsigned int value, checksum;
        time_t stamp;
        pid_t pid;
 
@@ -99,12 +99,19 @@ anvil_connection_request(struct anvil_connection *conn,
                value = penalty_get(penalty, args[0], &stamp);
                (void)o_stream_send_str(conn->output,
                        t_strdup_printf("%u %s\n", value, dec2str(stamp)));
-       } else if (strcmp(cmd, "PENALTY-SET") == 0) {
-               if (args[0] == NULL || args[1] == NULL) {
-                       *error_r = "PENALTY-SET: Not enough parameters";
+       } else if (strcmp(cmd, "PENALTY-INC") == 0) {
+               if (args[0] == NULL || args[1] == NULL || args[2] == NULL) {
+                       *error_r = "PENALTY-INC: Not enough parameters";
+                       return -1;
+               }
+               checksum = strtoul(args[1], NULL, 10);
+               value = strtoul(args[2], NULL, 10);
+               if (value > PENALTY_MAX_VALUE ||
+                   (value == 0 && checksum != 0)) {
+                       *error_r = "PENALTY-INC: Invalid parameters";
                        return -1;
                }
-               penalty_set(penalty, args[0], strtoul(args[1], NULL, 10));
+               penalty_inc(penalty, args[0], checksum, value);
        } else if (strcmp(cmd, "PENALTY-SET-EXPIRE-SECS") == 0) {
                if (args[0] == NULL) {
                        *error_r = "PENALTY-SET-EXPIRE-SECS: "
index df33feb34ea273a08f96aaa192f7b1de9c0a91c5..572d9a7720041bfe28fc881af7e30ebc9d8bc15d 100644 (file)
@@ -9,14 +9,28 @@
 #include <time.h>
 
 #define PENALTY_DEFAULT_EXPIRE_SECS (60*60)
+#define PENALTY_CHECKSUM_SAVE_COUNT
+#define CHECKSUM_VALUE_COUNT 2
+#define CHECKSUM_VALUE_PTR_COUNT 10
+
+#define LAST_UPDATE_BITS 15
 
 struct penalty_rec {
        /* ordered by last_update */
        struct penalty_rec *prev, *next;
 
        char *ident;
-       unsigned int penalty;
-       time_t last_update;
+       unsigned int last_penalty;
+
+       unsigned int penalty:16;
+       unsigned int last_update:LAST_UPDATE_BITS; /* last_penalty + n */
+       unsigned int checksum_is_pointer:1;
+       /* we use value up to two different checksums.
+          after that switch to pointer. */
+       union {
+               unsigned int value[CHECKSUM_VALUE_COUNT];
+               unsigned int *value_ptr;
+       } checksum;
 };
 
 struct penalty {
@@ -43,6 +57,8 @@ struct penalty *penalty_init(void)
 static void penalty_rec_free(struct penalty *penalty, struct penalty_rec *rec)
 {
        DLLIST2_REMOVE(&penalty->oldest, &penalty->newest, rec);
+       if (rec->checksum_is_pointer)
+               i_free(rec->checksum.value_ptr);
        i_free(rec->ident);
        i_free(rec);
 }
@@ -67,44 +83,111 @@ void penalty_set_expire_secs(struct penalty *penalty, unsigned int expire_secs)
        penalty->expire_secs = expire_secs;
 }
 
+static bool
+penalty_bump_checksum(struct penalty_rec *rec, unsigned int checksum)
+{
+       unsigned int *checksums;
+       unsigned int i, count;
+
+       if (!rec->checksum_is_pointer) {
+               checksums = rec->checksum.value;
+               count = CHECKSUM_VALUE_COUNT;
+       } else {
+               checksums = rec->checksum.value_ptr;
+               count = CHECKSUM_VALUE_PTR_COUNT;
+       }
+
+       for (i = 0; i < count; i++) {
+               if (checksums[i] == checksum) {
+                       if (i > 0) {
+                               memcpy(checksums + 1, checksums,
+                                      sizeof(checksums[0]) * i);
+                               checksums[0] = checksum;
+                       }
+                       return TRUE;
+               }
+       }
+       return FALSE;
+}
+
+static void penalty_add_checksum(struct penalty_rec *rec, unsigned int checksum)
+{
+       unsigned int *checksums;
+
+       i_assert(checksum != 0);
+
+       if (!rec->checksum_is_pointer) {
+               if (rec->checksum.value[CHECKSUM_VALUE_COUNT-1] == 0) {
+                       memcpy(rec->checksum.value + 1, rec->checksum.value,
+                              sizeof(rec->checksum.value[0]) *
+                              (CHECKSUM_VALUE_COUNT-1));
+                       rec->checksum.value[0] = checksum;
+                       return;
+               }
+
+               /* switch to using a pointer */
+               checksums = i_new(unsigned int, CHECKSUM_VALUE_PTR_COUNT);
+               memcpy(checksums, rec->checksum.value,
+                      sizeof(checksums[0]) * CHECKSUM_VALUE_COUNT);
+               rec->checksum.value_ptr = checksums;
+               rec->checksum_is_pointer = TRUE;
+       }
+
+       memcpy(rec->checksum.value_ptr + 1, rec->checksum.value_ptr,
+              sizeof(rec->checksum.value_ptr[0]) *
+              (CHECKSUM_VALUE_PTR_COUNT-1));
+       rec->checksum.value_ptr[0] = checksum;
+}
+
 unsigned int penalty_get(struct penalty *penalty, const char *ident,
-                        time_t *last_update_r)
+                        time_t *last_penalty_r)
 {
        struct penalty_rec *rec;
 
        rec = hash_table_lookup(penalty->hash, ident);
        if (rec == NULL) {
-               *last_update_r = 0;
+               *last_penalty_r = 0;
                return 0;
-       } else {
-               *last_update_r = rec->last_update;
-               return rec->penalty;
        }
+
+       *last_penalty_r = rec->last_penalty;
+       return rec->penalty;
 }
 
 static void penalty_timeout(struct penalty *penalty)
 {
+       struct penalty_rec *rec;
        time_t expire_time;
 
        expire_time = ioloop_time - penalty->expire_secs;
-       while (penalty->oldest != NULL &&
-              penalty->oldest->last_update <= expire_time) {
-               hash_table_remove(penalty->hash, penalty->oldest->ident);
-               penalty_rec_free(penalty, penalty->oldest);
+       while (penalty->oldest != NULL) {
+               rec = penalty->oldest;
+
+               if (rec->last_penalty + rec->last_update > expire_time)
+                       break;
+               hash_table_remove(penalty->hash, rec->ident);
+               penalty_rec_free(penalty, rec);
        }
 
        timeout_remove(&penalty->to);
-       if (penalty->oldest != NULL) {
-               unsigned int diff = penalty->oldest->last_update - expire_time;
+       rec = penalty->oldest;
+       if (rec != NULL) {
+               unsigned int diff;
+
+               diff = rec->last_penalty + rec->last_update - expire_time;
                penalty->to = timeout_add(diff * 1000,
                                          penalty_timeout, penalty);
        }
 }
 
-void penalty_set(struct penalty *penalty, const char *ident,
-                unsigned int value)
+void penalty_inc(struct penalty *penalty, const char *ident,
+                unsigned int checksum, unsigned int value)
 {
        struct penalty_rec *rec;
+       time_t diff;
+
+       i_assert(value > 0 || checksum == 0);
+       i_assert(value <= INT_MAX);
 
        rec = hash_table_lookup(penalty->hash, ident);
        if (rec == NULL) {
@@ -114,8 +197,26 @@ void penalty_set(struct penalty *penalty, const char *ident,
        } else {
                DLLIST2_REMOVE(&penalty->oldest, &penalty->newest, rec);
        }
-       rec->penalty = value;
-       rec->last_update = time(NULL);
+
+       if (checksum == 0) {
+               rec->penalty = value;
+               rec->last_penalty = ioloop_time;
+       } else {
+               if (penalty_bump_checksum(rec, checksum))
+                       rec->penalty = value - 1;
+               else {
+                       penalty_add_checksum(rec, checksum);
+                       rec->penalty = value;
+                       rec->last_penalty = ioloop_time;
+               }
+       }
+
+       diff = ioloop_time - rec->last_penalty;
+       if (diff >= (1 << LAST_UPDATE_BITS)) {
+               rec->last_update = (1 << LAST_UPDATE_BITS) - 1;
+               rec->last_penalty = ioloop_time - rec->last_update;
+       }
+
        DLLIST2_APPEND(&penalty->oldest, &penalty->newest, rec);
 
        if (penalty->to == NULL) {
@@ -123,3 +224,29 @@ void penalty_set(struct penalty *penalty, const char *ident,
                                          penalty_timeout, penalty);
        }
 }
+
+bool penalty_has_checksum(struct penalty *penalty, const char *ident,
+                         unsigned int checksum)
+{
+       struct penalty_rec *rec;
+       const unsigned int *checksums;
+       unsigned int i, count;
+
+       rec = hash_table_lookup(penalty->hash, ident);
+       if (rec == NULL)
+               return FALSE;
+
+       if (!rec->checksum_is_pointer) {
+               checksums = rec->checksum.value;
+               count = CHECKSUM_VALUE_COUNT;
+       } else {
+               checksums = rec->checksum.value_ptr;
+               count = CHECKSUM_VALUE_PTR_COUNT;
+       }
+
+       for (i = 0; i < count; i++) {
+               if (checksums[i] == checksum)
+                       return TRUE;
+       }
+       return FALSE;
+}
index 7c5ac38755cb3fbbd275eb76ef0dfba8b8657d1d..b765907c7231dfe4516caf17d09b8a46af95907d 100644 (file)
@@ -1,14 +1,21 @@
 #ifndef PENALTY_H
 #define PENALTY_H
 
+#define PENALTY_MAX_VALUE ((1 << 16)-1)
+
 struct penalty *penalty_init(void);
 void penalty_deinit(struct penalty **penalty);
 
 void penalty_set_expire_secs(struct penalty *penalty, unsigned int expire_secs);
 
 unsigned int penalty_get(struct penalty *penalty, const char *ident,
-                        time_t *last_update_r);
-void penalty_set(struct penalty *penalty, const char *ident,
-                unsigned int value);
+                        time_t *last_penalty_r);
+/* if checksum is non-zero and it already exists for ident, the value
+   is set to "value-1", otherwise it's set to "value". */
+void penalty_inc(struct penalty *penalty, const char *ident,
+                unsigned int checksum, unsigned int value);
+
+bool penalty_has_checksum(struct penalty *penalty, const char *ident,
+                         unsigned int checksum);
 
 #endif
diff --git a/src/anvil/test-penalty.c b/src/anvil/test-penalty.c
new file mode 100644 (file)
index 0000000..46c7e93
--- /dev/null
@@ -0,0 +1,64 @@
+/* Copyright (c) 2010 Dovecot authors, see the included COPYING file */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "penalty.h"
+#include "test-common.h"
+
+static void test_penalty_checksum(void)
+{
+       struct penalty *penalty;
+       struct ioloop *ioloop;
+       time_t t;
+       unsigned int i, j;
+
+       test_begin("penalty");
+
+       ioloop = io_loop_create();
+       penalty = penalty_init();
+
+       test_assert(penalty_get(penalty, "foo", &t) == 0);
+       for (i = 1; i <= 10; i++) {
+               ioloop_time = 12345678 + i;
+               penalty_inc(penalty, "foo", i, 5+i);
+
+               for (j = I_MIN(1, i-1); j <= i; j++) {
+                       test_assert(penalty_get(penalty, "foo", &t) == 5+i);
+                       test_assert(t == 12345678 + i);
+                       test_assert(penalty_has_checksum(penalty, "foo", i));
+               }
+               test_assert(penalty_get(penalty, "foo", &t) == 5+i);
+               test_assert(t == 12345678 + i);
+               test_assert(!penalty_has_checksum(penalty, "foo", j));
+       }
+       test_assert(penalty_get(penalty, "foo2", &t) == 0);
+
+       /* overflows checksum array */
+       ioloop_time = 12345678 + i;
+       penalty_inc(penalty, "foo", i, 5 + i);
+       penalty_inc(penalty, "foo", i, 5 + i);
+       penalty_inc(penalty, "foo", 0, 5 + i);
+
+       test_assert(penalty_get(penalty, "foo", &t) == 5+i);
+       test_assert(t == 12345678 + i);
+       test_assert(!penalty_has_checksum(penalty, "foo", 1));
+
+       for (j = 2; j <= i; j++) {
+               test_assert(penalty_get(penalty, "foo", &t) == 5+i);
+               test_assert(t == 12345678 + i);
+               test_assert(penalty_has_checksum(penalty, "foo", i));
+       }
+
+       penalty_deinit(&penalty);
+       io_loop_destroy(&ioloop);
+       test_end();
+}
+
+int main(void)
+{
+       static void (*test_functions[])(void) = {
+               test_penalty_checksum,
+               NULL
+       };
+       return test_run(test_functions);
+}
index 49796c127a04732eedc4e07df98b6647d41e61f3..e42083eb22342983cd5d23afcd719b7adad3de61 100644 (file)
@@ -3,6 +3,7 @@
 #include "lib.h"
 #include "ioloop.h"
 #include "network.h"
+#include "crc32.h"
 #include "anvil-client.h"
 #include "auth-request.h"
 #include "auth-penalty.h"
@@ -58,24 +59,24 @@ 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 long last_penalty = 0;
        unsigned int secs, drop_penalty;
 
        if (reply == NULL) {
                /* internal failure */
-       } else if (sscanf(reply, "%u %lu", &penalty, &last_update) != 2) {
+       } else if (sscanf(reply, "%u %lu", &penalty, &last_penalty) != 2) {
                i_error("Invalid PENALTY-GET reply: %s", reply);
        } else {
-               if ((time_t)last_update > ioloop_time) {
+               if ((time_t)last_penalty > ioloop_time) {
                        /* time moved backwards? */
-                       last_update = ioloop_time;
+                       last_penalty = 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)
+                       if (ioloop_time - last_penalty < secs)
                                break;
                        drop_penalty--;
                        penalty--;
@@ -109,6 +110,14 @@ void auth_penalty_lookup(struct auth_penalty *penalty,
        } T_END;
 }
 
+static unsigned int
+get_userpass_checksum(struct auth_request *auth_request)
+{
+       return auth_request->mech_password == NULL ? 0 :
+               crc32_str_more(crc32_str(auth_request->mech_password),
+                              auth_request->user);
+}
+
 void auth_penalty_update(struct auth_penalty *penalty,
                         struct auth_request *auth_request, unsigned int value)
 {
@@ -124,8 +133,12 @@ void auth_penalty_update(struct auth_penalty *penalty,
                value = AUTH_PENALTY_MAX_PENALTY;
        }
        T_BEGIN {
-               const char *cmd =
-                       t_strdup_printf("PENALTY-SET\t%s\t%u", ident, value);
+               const char *cmd;
+               unsigned int checksum;
+
+               checksum = value == 0 ? 0 : get_userpass_checksum(auth_request);
+               cmd = t_strdup_printf("PENALTY-INC\t%s\t%u\t%u",
+                                     ident, checksum, value);
                anvil_client_cmd(penalty->client, cmd);
        } T_END;
 }