*
* (c) 2016 Martin Mares <mj@ucw.cz>
*
- * 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
*/
#include <ucw/lib.h>
#include <ucw/clists.h>
-#include <ucw/stkstring.h>
+#include <ucw/conf.h>
+#include <ucw/mainloop.h>
#include <ucw/string.h>
+#include <alloca.h>
#include <arpa/inet.h>
+#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <libipset/session.h>
#include <libipset/types.h>
-/*** 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, "<error %d>", 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;
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;
};
#include <ucw/hashtable.h>
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);
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)
{
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)
culprit_init();
clist_init(&culprit_lru);
clist_init(&culprit_bans);
+ cleanup_timer.handler = culprit_timer;
}
/*** Parsing of log messages ***/
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;
}
// 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();
is_flush(IS_IPV6);
sk_init();
- sk_loop();
+ main_loop();
return 0;
}