static char *listen_on = "/var/run/bouncer.sock";
static uns max_failures = ~0U;
-static uns max_suspect_time = 86400;
+static uns suspect_time = 86400;
+static uns banned_time = 86400;
+static uns max_probation = ~0U;
+static double banned_again_coeff = 2;
static uns max_banned_time = 86400;
-static uns max_suspects = ~0U;
-static uns max_banned = ~0U;
-static uns probation;
+static uns probation_time = 86400;
+static uns max_culprits = ~0U;
+static char *config_log_stream;
static char *ipv4_set;
static char *ipv6_set;
-static char *config_log_stream;
static struct cf_section bouncer_cf = {
CF_ITEMS {
CF_STRING("ListenOn", &listen_on),
- CF_UNS("MaxSuspects", &max_suspects),
- CF_UNS("MaxBanned", &max_banned),
- CF_UNS("MaxSuspectTime", &max_suspect_time),
- CF_UNS("MaxBannedTime", &max_banned_time),
CF_UNS("MaxFailures", &max_failures),
- CF_UNS("Probation", &probation),
+ CF_UNS("SuspectTime", &suspect_time),
+ CF_UNS("BannedTime", &banned_time),
+ CF_UNS("MaxProbation", &max_probation),
+ CF_DOUBLE("BannedAgainCoeff", &banned_again_coeff),
+ CF_UNS("MaxBannedTime", &max_banned_time),
+ CF_UNS("ProbationTime", &probation_time),
+ CF_UNS("MaxCulprits", &max_culprits),
+ CF_STRING("LogStream", &config_log_stream),
CF_STRING("IPv4Set", &ipv4_set),
CF_STRING("IPv6Set", &ipv6_set),
- CF_STRING("LogStream", &config_log_stream),
CF_END
}
};
/*** Handling of login failures ***/
-struct culprit_node {
- cnode n; // In either suspect_list or banned_list
+enum culprit_state {
+ STATE_INVALID,
+ STATE_SUSPECT,
+ STATE_BANNED,
+ STATE_PROBATION,
+};
+
+static const char * const culprit_states[] = {
+ "invalid",
+ "suspect",
+ "banned",
+ "probation",
+};
+
+struct culprit {
union {
struct addr addr;
byte addr_bytes[16];
};
+ enum culprit_state state;
uns fail_count;
- bool banned;
- timestamp_t last_fail; // Not updated when banned
+ uns sentence_count;
+ uns ban_time;
+ struct main_timer timer;
};
-#define HASH_NODE struct culprit_node
-#define HASH_PREFIX(x) culprit_##x
+static uns num_culprits;
+static void culprit_timer(struct main_timer *tm);
+
+static void culprit_hash_init_data(struct culprit *c)
+{
+ c->state = STATE_SUSPECT;
+ c->fail_count = 0;
+ bzero(&c->timer, sizeof(c->timer));
+ c->timer.handler = culprit_timer;
+ c->timer.data = c;
+ num_culprits++;
+}
+
+#define HASH_NODE struct culprit
+#define HASH_PREFIX(x) culprit_hash_##x
#define HASH_KEY_MEMORY addr_bytes
#define HASH_KEY_SIZE 16
+#define HASH_GIVE_INIT_DATA
#define HASH_WANT_LOOKUP
#define HASH_WANT_REMOVE
#define HASH_USE_AUTO_ELTPOOL 1000
#define HASH_ZERO_FILL
-#define HASH_LOOKUP_DETECT_NEW
#include <ucw/hashtable.h>
-static clist suspect_list, banned_list;
-static uns num_suspects, num_banned;
-static struct main_timer cleanup_timer;
+static void culprit_delete(struct culprit *c)
+{
+ timer_del(&c->timer);
+ culprit_hash_remove(c);
+ num_culprits--;
+}
-static void cleanup_list(clist *list, uns *counter, timestamp_t max_time, uns max_count, timestamp_t *next)
+static void culprit_set_state(struct culprit *c, enum culprit_state state, uns timeout)
{
- timestamp_t now = main_get_now();
+ c->state = state;
+ timer_add_rel(&c->timer, (timestamp_t)timeout * 1000);
+ msg(L_DEBUG, "Suspect %s: state=%s failures=%u timeout=%u", AFMT(c->addr), culprit_states[state], c->fail_count, timeout);
+}
- for (;;)
- {
- struct culprit_node *c = clist_head(list);
- if (!c)
- break;
+static void handle_failed_login(struct addr addr, int cnt)
+{
+ timestamp_t now = main_get_now();
- timestamp_t expire_in = c->last_fail + max_time;
- if (*counter > max_count)
- {
- static timestamp_t last_overflow_warning;
- if (last_overflow_warning + 60000 < now)
- {
- last_overflow_warning = now;
- if (c->banned)
- msg(L_WARN, "Too many bans, dropping some. Try increasing MaxBanned.");
- else
- msg(L_WARN, "Too many suspects, dropping some. Try increasing MaxSuspects.");
- }
- expire_in = 0;
- }
+ struct culprit *c = culprit_hash_lookup((byte *) &addr);
- if (expire_in > now)
+ if (num_culprits > max_culprits)
+ {
+ // This can happen only when this lookup created a new culprit.
+ static timestamp_t last_overflow_warning;
+ if (last_overflow_warning + 60000 < now)
{
- *next = MIN(*next, expire_in);
- break;
+ last_overflow_warning = now;
+ msg(L_WARN, "Too many culprits, dropping some. Try increasing MaxCulprits.");
+ culprit_delete(c);
+ return;
}
+ }
- clist_remove(&c->n);
- (*counter)--;
-
- if (c->banned)
+ switch (c->state)
+ {
+ case STATE_SUSPECT:
+ c->fail_count += cnt;
+ if (c->fail_count > max_failures)
{
- msg(L_INFO, "Unbanning %s", AFMT(c->addr));
- is_modify(0, c->addr);
- if (probation)
- {
- c->banned = 0;
- c->last_fail = now;
- c->fail_count = max_failures - probation;
- clist_add_tail(&suspect_list, &c->n);
- num_suspects++;
- msg(L_DEBUG, "Suspect %s: probation, failures=%u", AFMT(c->addr), c->fail_count);
- }
- else
- culprit_remove(c);
+ c->ban_time = banned_time;
+ msg(L_INFO, "Banned %s: failures=%u ban_time=%u", AFMT(addr), c->fail_count, c->ban_time);
+ c->sentence_count = 1;
+ culprit_set_state(c, STATE_BANNED, c->ban_time);
+ is_modify(1, c->addr);
}
else
+ culprit_set_state(c, STATE_SUSPECT, suspect_time);
+ break;
+ case STATE_BANNED:
+ break;
+ case STATE_PROBATION:
+ c->fail_count += cnt;
+ if (c->fail_count > max_probation)
{
- msg(L_DEBUG, "Suspect %s: acquitted", AFMT(c->addr));
- culprit_remove(c);
+ c->sentence_count++;
+ c->ban_time = MIN((uns)(c->ban_time * banned_again_coeff), max_banned_time);
+ msg(L_INFO, "Re-banned %s: failures=%u sentences=%u ban_time=%u", AFMT(c->addr), c->fail_count, c->sentence_count, c->ban_time);
+ culprit_set_state(c, STATE_BANNED, c->ban_time);
+ is_modify(1, c->addr);
}
+ else
+ culprit_set_state(c, STATE_PROBATION, probation_time);
+ break;
+ default:
+ ASSERT(0);
}
}
-static void culprit_cleanup(void)
+static void culprit_timer(struct main_timer *tm)
{
- timestamp_t next_cleanup = main_get_now() + (timestamp_t)3600 * 1000;
- cleanup_list(&banned_list, &num_banned, (timestamp_t)max_banned_time * 1000, max_banned, &next_cleanup);
- cleanup_list(&suspect_list, &num_suspects, (timestamp_t)max_suspect_time * 1000, max_suspects, &next_cleanup);
- timer_add(&cleanup_timer, next_cleanup);
-}
-
-static void handle_failed_login(struct addr addr, int cnt)
-{
- int is_new;
- timestamp_t now = main_get_now();
+ struct culprit *c = tm->data;
- struct culprit_node *c = culprit_lookup((byte *) &addr, &is_new);
- if (is_new)
+ switch (c->state)
{
- c->last_fail = now;
- c->fail_count = cnt;
- c->banned = 0;
- clist_add_tail(&suspect_list, &c->n);
- num_suspects++;
- msg(L_DEBUG, "Suspect %s: new, failures=%u", AFMT(addr), c->fail_count);
+ case STATE_SUSPECT:
+ case STATE_PROBATION:
+ msg(L_DEBUG, "Suspect %s: acquitted", AFMT(c->addr));
+ culprit_delete(c);
+ break;
+ case STATE_BANNED:
+ msg(L_INFO, "Unbanned %s", AFMT(c->addr));
+ culprit_set_state(c, STATE_PROBATION, probation_time);
+ is_modify(0, c->addr);
+ break;
+ default:
+ ASSERT(0);
}
- else if (!c->banned)
- {
- c->last_fail = now;
- c->fail_count += cnt;
- clist_remove(&c->n);
- clist_add_tail(&suspect_list, &c->n);
- msg(L_DEBUG, "Suspect %s: failures=%u", AFMT(addr), c->fail_count);
- }
-
- if (!c->banned && c->fail_count >= max_failures)
- {
- msg(L_INFO, "Banning %s: failures=%u", AFMT(addr), c->fail_count);
- c->banned = 1;
- clist_remove(&c->n);
- num_suspects--;
- clist_add_tail(&banned_list, &c->n);
- num_banned++;
- is_modify(1, c->addr);
- }
-
- culprit_cleanup();
-}
-
-static void culprit_timer(struct main_timer *tm UNUSED)
-{
- culprit_cleanup();
-}
-
-static void fail_init(void)
-{
- culprit_init();
- clist_init(&suspect_list);
- clist_init(&banned_list);
- cleanup_timer.handler = culprit_timer;
}
/*** Parsing of log messages ***/
main_init();
is_init();
- fail_init();
+ culprit_hash_init();
sk_init();
is_flush(ipv4_set);