2 * Bouncer -- A Daemon for Turning Away Mischievous Guests
4 * (c) 2016 Martin Mares <mj@ucw.cz>
6 * FIXME: ipset create bouncer4 hash:ip family inet
7 * FIXME: ipset create bouncer6 hash:ip family inet6
8 * FIXME: sshd_config: UseDNS no
14 #include <ucw/clists.h>
16 #include <ucw/mainloop.h>
17 #include <ucw/string.h>
20 #include <arpa/inet.h>
23 #include <sys/socket.h>
28 #include <libipset/data.h>
29 #include <libipset/session.h>
30 #include <libipset/types.h>
32 /*** Internal representation of IPv4/IPv6 addresses ***/
34 // In network byte order, IPv4 represented as ::ffff:1.2.3.4
39 #define ADDR_BUFSIZE 64
40 #define AFMT(_a) ({ char *_buf = alloca(ADDR_BUFSIZE); addr_format(_buf, _a); _buf; })
42 static bool addr_is_v4(struct addr addr)
44 return !addr.a[0] && !addr.a[1] && addr.a[2] == htonl(0xffff);
47 static void addr_format(char *buf, struct addr addr)
53 in4.s_addr = addr.a[3];
54 ok = inet_ntop(AF_INET, &in4, buf, ADDR_BUFSIZE);
59 memcpy(&in6, &addr, sizeof(addr));
60 ok = inet_ntop(AF_INET6, &in6, buf, ADDR_BUFSIZE);
63 snprintf(buf, ADDR_BUFSIZE, "<error %d>", errno);
66 static bool addr_parse(struct addr *addr, const char *src)
71 if (inet_pton(AF_INET, src, &a4))
73 addr->a[0] = addr->a[1] = 0;
74 addr->a[2] = htonl(0xffff);
75 addr->a[3] = a4.s_addr;
78 else if (inet_pton(AF_INET6, src, &a6))
80 memcpy(&addr, &a6, 16);
87 /*** Configuration ***/
89 static uns max_culprits = 3; // FIXME
90 static uns max_failures = 3; // FIXME
91 static uns max_idle_time = 600; // FIXME
92 static uns max_banned_time = 600; // FIXME
94 /*** An interface to IP sets ***/
96 static struct ipset_session *is_sess;
103 static const char * const is_names[] = {
108 static const char *trim_eol(const char *msg)
110 int len = strlen(msg);
111 if (!len || msg[len-1] != '\n')
115 char *x = xstrdup(msg);
121 static void is_die(const char *when)
123 const char *warn = ipset_session_warning(is_sess);
125 msg(L_WARN, "%s: %s", when, trim_eol(warn));
127 const char *err = ipset_session_error(is_sess);
128 die("%s: %s", when, err ? trim_eol(err) : "Unknown error");
131 static void is_err(const char *when)
133 const char *warn = ipset_session_warning(is_sess);
135 msg(L_WARN, "%s: %s", when, trim_eol(warn));
137 const char *err = ipset_session_error(is_sess);
138 msg(L_ERROR, "%s: %s", when, err ? trim_eol(err) : "Unknown error");
140 ipset_session_report_reset(is_sess);
143 static void is_init(void)
147 is_sess = ipset_session_init(printf);
149 die("Unable to initialize ipset session");
152 static void is_setup(int set)
154 if (ipset_parse_setname(is_sess, IPSET_SETNAME, is_names[set]) < 0)
155 is_die("ipset_parse_setname");
158 static void is_flush(int set)
162 if (ipset_cmd(is_sess, IPSET_CMD_FLUSH, 0) < 0)
163 return is_err("IPSET_CMD_FLUSH");
166 static void is_modify(bool add, struct addr addr)
168 is_setup(addr_is_v4(addr) ? IS_IPV4 : IS_IPV6);
169 int cmd = add ? IPSET_CMD_ADD : IPSET_CMD_DEL;
171 if (ipset_envopt_parse(is_sess, IPSET_ENV_EXIST, NULL) < 0)
172 is_die("IPSET_ENV_EXIST");
174 const struct ipset_type *is_type = ipset_type_get(is_sess, cmd);
176 is_die("ipset_type_get");
178 if (is_type->dimension != 1)
179 die("Invalid ipset dimension %d", is_type->dimension);
181 char buf[ADDR_BUFSIZE];
182 addr_format(buf, addr);
183 if (ipset_parse_elem(is_sess, 0, buf) < 0)
184 return is_err("ipset_parse_elem");
186 if (ipset_cmd(is_sess, cmd, 0) < 0)
187 return is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL");
190 /*** Handling of login failures ***/
192 struct culprit_node {
198 timestamp_t last_fail;
203 #define HASH_NODE struct culprit_node
204 #define HASH_PREFIX(x) culprit_##x
205 #define HASH_KEY_MEMORY addr_bytes
206 #define HASH_KEY_SIZE 16
207 #define HASH_WANT_LOOKUP
208 #define HASH_WANT_REMOVE
209 #define HASH_USE_AUTO_ELTPOOL 1000
210 #define HASH_ZERO_FILL
211 #define HASH_LOOKUP_DETECT_NEW
212 #include <ucw/hashtable.h>
214 static uns num_culprits;
215 static clist culprit_lru, culprit_bans;
216 static struct main_timer cleanup_timer;
218 static void cleanup_list(clist *list, timestamp_t max_time, timestamp_t *next)
220 timestamp_t now = main_get_now();
224 struct culprit_node *c = clist_head(list);
228 timestamp_t expire_in = c->last_fail + max_time - now;
229 if (num_culprits > max_culprits)
234 *next = MIN(*next, expire_in);
240 DBG("%s: unbanned", AFMT(c->addr));
241 is_modify(0, c->addr);
245 DBG("%s: removed from LRU", AFMT(c->addr));
254 static void culprit_cleanup(void)
256 timestamp_t next_cleanup = main_get_now() + (timestamp_t)3600 * 1000;
257 cleanup_list(&culprit_bans, (timestamp_t)max_banned_time * 1000, &next_cleanup);
258 cleanup_list(&culprit_lru, (timestamp_t)max_idle_time * 1000, &next_cleanup);
259 timer_add(&cleanup_timer, next_cleanup);
262 static void handle_failed_login(struct addr addr)
265 timestamp_t now = main_get_now();
267 struct culprit_node *c = culprit_lookup((byte *) &addr, &is_new);
272 clist_add_tail(&culprit_lru, &c->n);
274 // FIXME: Warn on overflow, but not too frequently
275 DBG("%s: first fail", AFMT(addr));
281 clist_add_tail(&culprit_lru, &c->n);
282 DBG("%s: next fail, cnt=%u", AFMT(addr), c->fail_count);
286 if (!c->banned && c->fail_count >= max_failures)
288 DBG("%s: banned", AFMT(addr));
291 clist_add_tail(&culprit_bans, &c->n);
292 is_modify(1, c->addr);
296 static void culprit_timer(struct main_timer *tm UNUSED)
301 static void fail_init(void)
304 clist_init(&culprit_lru);
305 clist_init(&culprit_bans);
306 cleanup_timer.handler = culprit_timer;
309 /*** Parsing of log messages ***/
311 static bool check_next(char **pp, char *want)
323 static void process_msg(char *line)
325 DBG("Parse: <%s>", line);
329 // 2016-11-04T17:18:54.825821+01:00 sshd[6733]: pam_unix(sshd:auth): authentication failure; logname= uid=0 euid=0 tty=ssh ruser= rhost=1.2.3.4
331 // We shall start with 32 non-spaces
332 for (int i=0; i<32; i++)
338 DBG("Parse 1: <%s>", p);
340 // Space, something, colon, space
343 while (*p && *p != ' ' && *p != ':')
345 if (!check_next(&p, ": "))
347 DBG("Parse 2: <%s>", p);
349 // pam_unix(something), colon, space (FIXME: make configurable)
350 if (!check_next(&p, "pam_unix("))
359 if (!check_next(&p, ": "))
361 DBG("Parse 3: <%s>", p);
363 // "authentication failure;"
364 if (!check_next(&p, "authentication failure; "))
366 DBG("Parse 4: <%s>", p);
379 while (*p && *p != ' ' && *p != '=')
386 while (*p && *p != ' ')
393 DBG("Parse KV: %s=<%s>", key, val);
394 if (!strcmp(key, "rhost"))
398 // Act on the message
400 if (addr_parse(&addr, rhost))
401 handle_failed_login(addr);
403 msg(L_WARN, "Unable to parse address %s", rhost);
406 /*** Socket for receiving messages from rsyslog ***/
408 static const char sk_name[] = "/var/run/bouncer.sock";
409 struct main_file sk_file;
411 static int sk_read(struct main_file *mf)
414 int len = recv(mf->fd, line, sizeof(line), MSG_TRUNC);
417 if (errno == EINTR || errno == EAGAIN)
422 if (len >= (int) sizeof(line))
424 msg(L_WARN, "Truncated message received (length=%d)", len);
425 len = sizeof(line) - 1;
429 if (len > 0 && line[len-1] == '\n')
431 if (len > 0 && line[len-1] == '\r')
438 static void sk_init(void)
443 if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
444 die("Cannot create PF_UNIX socket: %m");
446 struct sockaddr_un sa = { .sun_family = AF_UNIX };
447 strcpy(sa.sun_path, sk_name);
448 if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
449 die("Cannot bind socket %s: %m", sk_name);
451 // FIXME: Permissions
454 sk_file.read_handler = sk_read;
466 msg(L_INFO, "Clearing previous state");