summaryrefslogtreecommitdiffstats
path: root/src/anvil/penalty.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/anvil/penalty.c')
-rw-r--r--src/anvil/penalty.c273
1 files changed, 273 insertions, 0 deletions
diff --git a/src/anvil/penalty.c b/src/anvil/penalty.c
new file mode 100644
index 0000000..2ab6da1
--- /dev/null
+++ b/src/anvil/penalty.c
@@ -0,0 +1,273 @@
+/* Copyright (c) 2009-2018 Dovecot authors, see the included COPYING file */
+
+/* The idea behind checksums is that the same username+password doesn't
+ increase the penalty, because it's most likely a user with a misconfigured
+ account. */
+
+#include "lib.h"
+#include "ioloop.h"
+#include "hash.h"
+#include "str.h"
+#include "strescape.h"
+#include "llist.h"
+#include "ostream.h"
+#include "penalty.h"
+
+#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 last_penalty;
+
+ unsigned int penalty:16;
+ unsigned int last_update:LAST_UPDATE_BITS; /* last_penalty + n */
+ bool 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 {
+ /* ident => penalty_rec */
+ HASH_TABLE(char *, struct penalty_rec *) hash;
+ struct penalty_rec *oldest, *newest;
+
+ unsigned int expire_secs;
+ struct timeout *to;
+};
+
+struct penalty *penalty_init(void)
+{
+ struct penalty *penalty;
+
+ penalty = i_new(struct penalty, 1);
+ hash_table_create(&penalty->hash, default_pool, 0, str_hash, strcmp);
+ penalty->expire_secs = PENALTY_DEFAULT_EXPIRE_SECS;
+ return 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);
+}
+
+void penalty_deinit(struct penalty **_penalty)
+{
+ struct penalty *penalty = *_penalty;
+
+ *_penalty = NULL;
+
+ while (penalty->oldest != NULL)
+ penalty_rec_free(penalty, penalty->oldest);
+ hash_table_destroy(&penalty->hash);
+
+ timeout_remove(&penalty->to);
+ i_free(penalty);
+}
+
+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) {
+ memmove(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;
+ }
+
+ memmove(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_penalty_r)
+{
+ struct penalty_rec *rec;
+
+ rec = hash_table_lookup(penalty->hash, ident);
+ if (rec == NULL) {
+ *last_penalty_r = 0;
+ return 0;
+ }
+
+ *last_penalty_r = rec->last_penalty;
+ return rec->penalty;
+}
+
+static void penalty_timeout(struct penalty *penalty)
+{
+ struct penalty_rec *rec;
+ time_t rec_last_update, expire_time;
+ unsigned int diff;
+
+ timeout_remove(&penalty->to);
+
+ expire_time = ioloop_time - penalty->expire_secs;
+ while (penalty->oldest != NULL) {
+ rec = penalty->oldest;
+
+ rec_last_update = rec->last_penalty + rec->last_update;
+ if (rec_last_update > expire_time) {
+ diff = rec_last_update - expire_time;
+ penalty->to = timeout_add(diff * 1000,
+ penalty_timeout, penalty);
+ break;
+ }
+ hash_table_remove(penalty->hash, rec->ident);
+ penalty_rec_free(penalty, rec);
+ }
+}
+
+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) {
+ rec = i_new(struct penalty_rec, 1);
+ rec->ident = i_strdup(ident);
+ hash_table_insert(penalty->hash, rec->ident, rec);
+ } else {
+ DLLIST2_REMOVE(&penalty->oldest, &penalty->newest, rec);
+ }
+
+ 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;
+ } else {
+ rec->last_update = diff;
+ }
+
+ DLLIST2_APPEND(&penalty->oldest, &penalty->newest, rec);
+
+ if (penalty->to == NULL) {
+ penalty->to = timeout_add(penalty->expire_secs * 1000,
+ 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;
+}
+
+void penalty_dump(struct penalty *penalty, struct ostream *output)
+{
+ const struct penalty_rec *rec;
+ string_t *str = t_str_new(256);
+
+ for (rec = penalty->oldest; rec != NULL; rec = rec->next) {
+ str_truncate(str, 0);
+ str_append_tabescaped(str, rec->ident);
+ str_printfa(str, "\t%u\t%u\t%u\n",
+ rec->penalty, rec->last_penalty,
+ rec->last_penalty + rec->last_update);
+ if (o_stream_send(output, str_data(str), str_len(str)) < 0)
+ break;
+ }
+ o_stream_nsend(output, "\n", 1);
+}