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,
--- a/src/auth/userdb.h	Tue Nov 10 15:07:49 2009 -0500
+++ b/src/auth/userdb.h	Tue Nov 10 15:08:24 2009 -0500
@@ -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,