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
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
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;
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: "
#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 {
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);
}
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) {
} 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) {
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;
+}
#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
--- /dev/null
+/* 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);
+}
#include "lib.h"
#include "ioloop.h"
#include "network.h"
+#include "crc32.h"
#include "anvil-client.h"
#include "auth-request.h"
#include "auth-penalty.h"
{
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--;
} 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)
{
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;
}