dovecot-2.0: auth/anvil: Penalty is no longer increased if the s...

dovecot at dovecot.org dovecot at dovecot.org
Sat Feb 20 08:49:01 EET 2010


details:   http://hg.dovecot.org/dovecot-2.0/rev/4cdb58bb0360
changeset: 10773:4cdb58bb0360
user:      Timo Sirainen <tss at iki.fi>
date:      Sat Feb 20 08:48:54 2010 +0200
description:
auth/anvil: Penalty is no longer increased if the same user+pass combination was recently used.
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.

diffstat:

 src/anvil/Makefile.am        |   20 +++++
 src/anvil/anvil-connection.c |   17 +++-
 src/anvil/penalty.c          |  161 ++++++++++++++++++++++++++++++++++++----
 src/anvil/penalty.h          |   13 ++-
 src/anvil/test-penalty.c     |   64 ++++++++++++++++
 src/auth/auth-penalty.c      |   27 +++++-
 6 files changed, 270 insertions(+), 32 deletions(-)

diffs (truncated from 464 to 300 lines):

diff -r 5380ee17392f -r 4cdb58bb0360 src/anvil/Makefile.am
--- a/src/anvil/Makefile.am	Sat Feb 20 05:54:46 2010 +0200
+++ b/src/anvil/Makefile.am	Sat Feb 20 08:48:54 2010 +0200
@@ -4,6 +4,7 @@
 
 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 @@
 	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
diff -r 5380ee17392f -r 4cdb58bb0360 src/anvil/anvil-connection.c
--- a/src/anvil/anvil-connection.c	Sat Feb 20 05:54:46 2010 +0200
+++ b/src/anvil/anvil-connection.c	Sat Feb 20 08:48:54 2010 +0200
@@ -47,7 +47,7 @@
 			 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 @@
 		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;
 		}
-		penalty_set(penalty, args[0], strtoul(args[1], NULL, 10));
+		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_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: "
diff -r 5380ee17392f -r 4cdb58bb0360 src/anvil/penalty.c
--- a/src/anvil/penalty.c	Sat Feb 20 05:54:46 2010 +0200
+++ b/src/anvil/penalty.c	Sat Feb 20 08:48:54 2010 +0200
@@ -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 @@
 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 @@
 	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 @@
 	} 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 @@
 					  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;
+}
diff -r 5380ee17392f -r 4cdb58bb0360 src/anvil/penalty.h
--- a/src/anvil/penalty.h	Sat Feb 20 05:54:46 2010 +0200
+++ b/src/anvil/penalty.h	Sat Feb 20 08:48:54 2010 +0200


More information about the dovecot-cvs mailing list