From ac6542babbf34766e3190fb4c6c4965d6149e91e Mon Sep 17 00:00:00 2001 From: Martin Mares Date: Fri, 4 Nov 2016 21:48:11 +0100 Subject: [PATCH] Use libucw mainloop --- bouncer.c | 272 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 174 insertions(+), 98 deletions(-) diff --git a/bouncer.c b/bouncer.c index cedf14c..595be73 100644 --- a/bouncer.c +++ b/bouncer.c @@ -3,8 +3,8 @@ * * (c) 2016 Martin Mares * - * FIXME: ipset create bouncer4 hash:ip family inet timeout 10000 maxelem 100 forceadd - * FIXME: ipset create bouncer6 hash:ip family inet6 timeout 10000 maxelem 100 forceadd + * FIXME: ipset create bouncer4 hash:ip family inet + * FIXME: ipset create bouncer6 hash:ip family inet6 * FIXME: sshd_config: UseDNS no */ @@ -12,10 +12,13 @@ #include #include -#include +#include +#include #include +#include #include +#include #include #include #include @@ -26,7 +29,69 @@ #include #include -/*** IP sets ***/ +/*** Internal representation of IPv4/IPv6 addresses ***/ + +// In network byte order, IPv4 represented as ::ffff:1.2.3.4 +struct addr { + u32 a[4]; +}; + +#define ADDR_BUFSIZE 64 +#define AFMT(_a) ({ char *_buf = alloca(ADDR_BUFSIZE); addr_format(_buf, _a); _buf; }) + +static bool addr_is_v4(struct addr addr) +{ + return !addr.a[0] && !addr.a[1] && addr.a[2] == htonl(0xffff); +} + +static void addr_format(char *buf, struct addr addr) +{ + const char *ok; + if (addr_is_v4(addr)) + { + struct in_addr in4; + in4.s_addr = addr.a[3]; + ok = inet_ntop(AF_INET, &in4, buf, ADDR_BUFSIZE); + } + else + { + struct in6_addr in6; + memcpy(&in6, &addr, sizeof(addr)); + ok = inet_ntop(AF_INET6, &in6, buf, ADDR_BUFSIZE); + } + if (!ok) + snprintf(buf, ADDR_BUFSIZE, "", errno); +} + +static bool addr_parse(struct addr *addr, const char *src) +{ + struct in_addr a4; + struct in6_addr a6; + + if (inet_pton(AF_INET, src, &a4)) + { + addr->a[0] = addr->a[1] = 0; + addr->a[2] = htonl(0xffff); + addr->a[3] = a4.s_addr; + return 1; + } + else if (inet_pton(AF_INET6, src, &a6)) + { + memcpy(&addr, &a6, 16); + return 1; + } + else + return 0; +} + +/*** Configuration ***/ + +static uns max_culprits = 3; // FIXME +static uns max_failures = 3; // FIXME +static uns max_idle_time = 600; // FIXME +static uns max_banned_time = 600; // FIXME + +/*** An interface to IP sets ***/ static struct ipset_session *is_sess; @@ -98,43 +163,39 @@ static void is_flush(int set) return is_err("IPSET_CMD_FLUSH"); } -static void is_add(int set, const char *elt) +static void is_modify(bool add, struct addr addr) { - is_setup(set); + is_setup(addr_is_v4(addr) ? IS_IPV4 : IS_IPV6); + int cmd = add ? IPSET_CMD_ADD : IPSET_CMD_DEL; if (ipset_envopt_parse(is_sess, IPSET_ENV_EXIST, NULL) < 0) is_die("IPSET_ENV_EXIST"); - const struct ipset_type *is_type = ipset_type_get(is_sess, IPSET_CMD_ADD); + const struct ipset_type *is_type = ipset_type_get(is_sess, cmd); if (!is_type) is_die("ipset_type_get"); if (is_type->dimension != 1) die("Invalid ipset dimension %d", is_type->dimension); - if (ipset_parse_elem(is_sess, 0, elt) < 0) + char buf[ADDR_BUFSIZE]; + addr_format(buf, addr); + if (ipset_parse_elem(is_sess, 0, buf) < 0) return is_err("ipset_parse_elem"); - if (ipset_cmd(is_sess, IPSET_CMD_ADD, 0) < 0) - return is_err("IPSET_CMD_ADD"); + if (ipset_cmd(is_sess, cmd, 0) < 0) + return is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL"); } /*** Handling of login failures ***/ -struct addr { - u32 a[4]; -}; - -// FIXME: call ntohl -#define AFMT(_a) stk_printf("%08x:%08x:%08x:%08x", _a.a[0], _a.a[1], _a.a[2], _a.a[3]) - struct culprit_node { cnode n; union { struct addr addr; byte addr_bytes[16]; }; - time_t first_fail; + timestamp_t last_fail; uns fail_count; bool banned; }; @@ -151,21 +212,61 @@ struct culprit_node { #include static uns num_culprits; -static uns max_culprits = 3; // FIXME -static uns max_failures = 3; // FIXME -static uns max_idle_time = 600; // FIXME -static uns max_banned_time = 600; // FIXME static clist culprit_lru, culprit_bans; +static struct main_timer cleanup_timer; + +static void cleanup_list(clist *list, timestamp_t max_time, timestamp_t *next) +{ + timestamp_t now = main_get_now(); + + for (;;) + { + struct culprit_node *c = clist_head(list); + if (!c) + break; + + timestamp_t expire_in = c->last_fail + max_time - now; + if (num_culprits > max_culprits) + expire_in = 0; + + if (expire_in > now) + { + *next = MIN(*next, expire_in); + break; + } + + if (c->banned) + { + DBG("%s: unbanned", AFMT(c->addr)); + is_modify(0, c->addr); + } + else + { + DBG("%s: removed from LRU", AFMT(c->addr)); + } + + clist_remove(&c->n); + culprit_remove(c); + num_culprits--; + } +} + +static void culprit_cleanup(void) +{ + timestamp_t next_cleanup = main_get_now() + (timestamp_t)3600 * 1000; + cleanup_list(&culprit_bans, (timestamp_t)max_banned_time * 1000, &next_cleanup); + cleanup_list(&culprit_lru, (timestamp_t)max_idle_time * 1000, &next_cleanup); + timer_add(&cleanup_timer, next_cleanup); +} static void handle_failed_login(struct addr addr) { int is_new; - time_t now = time(NULL); + timestamp_t now = main_get_now(); struct culprit_node *c = culprit_lookup((byte *) &addr, &is_new); if (is_new) { - c->first_fail = now; c->fail_count = 1; c->banned = 0; clist_add_tail(&culprit_lru, &c->n); @@ -180,6 +281,7 @@ static void handle_failed_login(struct addr addr) clist_add_tail(&culprit_lru, &c->n); DBG("%s: next fail, cnt=%u", AFMT(addr), c->fail_count); } + c->last_fail = now; if (!c->banned && c->fail_count >= max_failures) { @@ -187,30 +289,13 @@ static void handle_failed_login(struct addr addr) c->banned = 1; clist_remove(&c->n); clist_add_tail(&culprit_bans, &c->n); + is_modify(1, c->addr); } +} - // FIXME: This must be called from main loop, not from here - struct culprit_node *d; - while ((d = clist_head(&culprit_bans)) && (now - d->first_fail >= max_banned_time || num_culprits > max_culprits)) - { - DBG("%s: unbanned", AFMT(d->addr)); - clist_remove(&d->n); - culprit_remove(d); - num_culprits--; - } - while ((d = clist_head(&culprit_lru)) && (now - d->first_fail >= max_idle_time || num_culprits > max_culprits)) - { - DBG("%s: removing from LRU", AFMT(d->addr)); - clist_remove(&d->n); - culprit_remove(d); - } - -#if 0 // FIXME - int set = strchr(rhost, ':') ? IS_IPV6 : IS_IPV4; - - msg(L_INFO, "Banning %s", rhost); - is_add(set, rhost); -#endif +static void culprit_timer(struct main_timer *tm UNUSED) +{ + culprit_cleanup(); } static void fail_init(void) @@ -218,6 +303,7 @@ static void fail_init(void) culprit_init(); clist_init(&culprit_lru); clist_init(&culprit_bans); + cleanup_timer.handler = culprit_timer; } /*** Parsing of log messages ***/ @@ -234,31 +320,9 @@ static bool check_next(char **pp, char *want) return 1; } -static void parse_failed_login(char *rhost) -{ - struct in_addr a4; - struct in6_addr a6; - struct addr addr; - - if (inet_pton(AF_INET, rhost, &a4)) - { - addr.a[0] = addr.a[1] = 0; - addr.a[2] = 0xffff; - addr.a[3] = a4.s_addr; - handle_failed_login(addr); - } - else if (inet_pton(AF_INET6, rhost, &a6)) - { - memcpy(&addr, &a6, 16); - handle_failed_login(addr); - } - else - msg(L_WARN, "Unable to parse address %s", rhost); -} - static void process_msg(char *line) { - msg(L_DEBUG, "Received <%s>", line); + DBG("Parse: <%s>", line); char *p = line; int c; @@ -332,58 +396,70 @@ static void process_msg(char *line) } // Act on the message - parse_failed_login(rhost); + struct addr addr; + if (addr_parse(&addr, rhost)) + handle_failed_login(addr); + else + msg(L_WARN, "Unable to parse address %s", rhost); } /*** Socket for receiving messages from rsyslog ***/ static const char sk_name[] = "/var/run/bouncer.sock"; -static int sk_fd; +struct main_file sk_file; + +static int sk_read(struct main_file *mf) +{ + char line[1024]; + int len = recv(mf->fd, line, sizeof(line), MSG_TRUNC); + if (len < 0) + { + if (errno == EINTR || errno == EAGAIN) + return HOOK_IDLE; + die("recv: %m"); + } + + if (len >= (int) sizeof(line)) + { + msg(L_WARN, "Truncated message received (length=%d)", len); + len = sizeof(line) - 1; + } + line[len] = 0; + + if (len > 0 && line[len-1] == '\n') + line[--len] = 0; + if (len > 0 && line[len-1] == '\r') + line[--len] = 0; + + process_msg(line); + return HOOK_RETRY; +} static void sk_init(void) { unlink(sk_name); - if ((sk_fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) + int fd; + if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) die("Cannot create PF_UNIX socket: %m"); struct sockaddr_un sa = { .sun_family = AF_UNIX }; strcpy(sa.sun_path, sk_name); - if (bind(sk_fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) + if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0) die("Cannot bind socket %s: %m", sk_name); // FIXME: Permissions -} - -static void sk_loop(void) -{ - for (;;) - { - char line[1024]; - int len = recv(sk_fd, line, sizeof(line), MSG_TRUNC); - if (len < 0) - die("recv: %m"); - if (len >= (int) sizeof(line)) - { - msg(L_WARN, "Truncated message received (length=%d)", len); - len = sizeof(line) - 1; - } - line[len] = 0; - - if (len > 0 && line[len-1] == '\n') - line[--len] = 0; - if (len > 0 && line[len-1] == '\r') - line[--len] = 0; - - process_msg(line); - } + sk_file.fd = fd; + sk_file.read_handler = sk_read; + file_add(&sk_file); } /*** Main ***/ int main(void) { + main_init(); is_init(); fail_init(); @@ -392,7 +468,7 @@ int main(void) is_flush(IS_IPV6); sk_init(); - sk_loop(); + main_loop(); return 0; } -- 2.39.2