2 * Bouncer -- A Daemon for Turning Away Mischievous Guests
4 * (c) 2016 Martin Mares <mj@ucw.cz>
10 #include <ucw/clists.h>
13 #include <ucw/mainloop.h>
15 #include <ucw/string.h>
18 #include <arpa/inet.h>
21 #include <sys/types.h>
22 #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 char *listen_on = "/var/run/bouncer.sock";
90 static uns max_failures = ~0U;
91 static uns suspect_time = 86400;
92 static uns banned_time = 86400;
93 static uns max_probation = ~0U;
94 static double banned_again_coeff = 2;
95 static uns max_banned_time = 86400;
96 static uns probation_time = 86400;
97 static uns max_culprits = ~0U;
98 static char *config_log_stream;
99 static char *ipv4_set;
100 static char *ipv6_set;
102 static struct cf_section bouncer_cf = {
104 CF_STRING("ListenOn", &listen_on),
105 CF_UNS("MaxFailures", &max_failures),
106 CF_UNS("SuspectTime", &suspect_time),
107 CF_UNS("BannedTime", &banned_time),
108 CF_UNS("MaxProbation", &max_probation),
109 CF_DOUBLE("BannedAgainCoeff", &banned_again_coeff),
110 CF_UNS("MaxBannedTime", &max_banned_time),
111 CF_UNS("ProbationTime", &probation_time),
112 CF_UNS("MaxCulprits", &max_culprits),
113 CF_STRING("LogStream", &config_log_stream),
114 CF_STRING("IPv4Set", &ipv4_set),
115 CF_STRING("IPv6Set", &ipv6_set),
120 /*** An interface to IP sets ***/
122 static struct ipset_session *is_sess;
124 static const char *trim_eol(const char *msg)
126 int len = strlen(msg);
127 if (!len || msg[len-1] != '\n')
131 char *x = xstrdup(msg);
137 static void is_die(const char *when)
139 const char *warn = ipset_session_warning(is_sess);
141 msg(L_WARN, "%s: %s", when, trim_eol(warn));
143 const char *err = ipset_session_error(is_sess);
144 die("%s: %s", when, err ? trim_eol(err) : "Unknown error");
147 static void is_err(const char *when)
149 const char *warn = ipset_session_warning(is_sess);
151 msg(L_WARN, "%s: %s", when, trim_eol(warn));
153 const char *err = ipset_session_error(is_sess);
154 msg(L_ERROR, "%s: %s", when, err ? trim_eol(err) : "Unknown error");
156 ipset_session_report_reset(is_sess);
159 static void is_init(void)
163 is_sess = ipset_session_init(printf);
165 die("Unable to initialize ipset session");
168 static bool is_setup(char *set)
173 if (ipset_parse_setname(is_sess, IPSET_SETNAME, set) < 0)
174 is_die("ipset_parse_setname");
178 static void is_flush(char *set)
183 if (ipset_cmd(is_sess, IPSET_CMD_FLUSH, 0) < 0)
184 return is_err("IPSET_CMD_FLUSH");
187 static bool is_modify(bool add, struct addr addr)
189 int cmd = add ? IPSET_CMD_ADD : IPSET_CMD_DEL;
190 char *set = addr_is_v4(addr) ? ipv4_set : ipv6_set;
194 if (ipset_envopt_parse(is_sess, IPSET_ENV_EXIST, NULL) < 0)
195 is_die("IPSET_ENV_EXIST");
197 const struct ipset_type *is_type = ipset_type_get(is_sess, cmd);
199 is_die("ipset_type_get");
201 if (is_type->dimension != 1)
202 die("Invalid ipset dimension %d", is_type->dimension);
204 char buf[ADDR_BUFSIZE];
205 addr_format(buf, addr);
206 if (ipset_parse_elem(is_sess, 0, buf) < 0)
208 is_err("ipset_parse_elem");
212 if (ipset_cmd(is_sess, cmd, 0) < 0)
214 is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL");
221 /*** Handling of login failures ***/
230 static const char * const culprit_states[] = {
242 enum culprit_state state;
246 struct main_timer timer;
249 static uns num_culprits;
250 static void culprit_timer(struct main_timer *tm);
252 static void culprit_hash_init_data(struct culprit *c)
254 c->state = STATE_SUSPECT;
256 bzero(&c->timer, sizeof(c->timer));
257 c->timer.handler = culprit_timer;
262 #define HASH_NODE struct culprit
263 #define HASH_PREFIX(x) culprit_hash_##x
264 #define HASH_KEY_MEMORY addr_bytes
265 #define HASH_KEY_SIZE 16
266 #define HASH_GIVE_INIT_DATA
267 #define HASH_WANT_LOOKUP
268 #define HASH_WANT_REMOVE
269 #define HASH_USE_AUTO_ELTPOOL 1000
270 #define HASH_ZERO_FILL
271 #include <ucw/hashtable.h>
273 static void culprit_delete(struct culprit *c)
275 timer_del(&c->timer);
276 culprit_hash_remove(c);
280 static void culprit_set_state(struct culprit *c, enum culprit_state state, uns timeout)
283 timer_add_rel(&c->timer, (timestamp_t)timeout * 1000);
284 msg(L_DEBUG, "Suspect %s: state=%s failures=%u timeout=%u", AFMT(c->addr), culprit_states[state], c->fail_count, timeout);
287 static void handle_failed_login(struct addr addr, int cnt)
289 timestamp_t now = main_get_now();
291 struct culprit *c = culprit_hash_lookup((byte *) &addr);
293 if (num_culprits > max_culprits)
295 // This can happen only when this lookup created a new culprit.
296 static timestamp_t last_overflow_warning;
297 if (last_overflow_warning + 60000 < now)
299 last_overflow_warning = now;
300 msg(L_WARN, "Too many culprits, dropping some. Try increasing MaxCulprits.");
309 c->fail_count += cnt;
310 if (c->fail_count > max_failures)
312 c->ban_time = banned_time;
313 msg(L_INFO, "Banned %s: failures=%u ban_time=%u", AFMT(addr), c->fail_count, c->ban_time);
314 c->sentence_count = 1;
315 culprit_set_state(c, STATE_BANNED, c->ban_time);
316 is_modify(1, c->addr);
319 culprit_set_state(c, STATE_SUSPECT, suspect_time);
323 case STATE_PROBATION:
324 c->fail_count += cnt;
325 if (c->fail_count > max_probation)
328 c->ban_time = MIN((uns)(c->ban_time * banned_again_coeff), max_banned_time);
329 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);
330 culprit_set_state(c, STATE_BANNED, c->ban_time);
331 is_modify(1, c->addr);
334 culprit_set_state(c, STATE_PROBATION, probation_time);
341 static void culprit_timer(struct main_timer *tm)
343 struct culprit *c = tm->data;
348 case STATE_PROBATION:
349 msg(L_DEBUG, "Suspect %s: acquitted", AFMT(c->addr));
353 msg(L_INFO, "Unbanned %s", AFMT(c->addr));
354 culprit_set_state(c, STATE_PROBATION, probation_time);
355 is_modify(0, c->addr);
363 /*** Parsing of log messages ***/
365 static bool check_next(char **pp, char *want)
377 static void parse_failure(char *p, int cnt)
379 DBG("Parse 4: <%s> cnt=%d", p, cnt);
392 while (*p && *p != ' ' && *p != '=')
399 while (*p && *p != ' ')
406 DBG("Parse KV: %s=<%s>", key, val);
407 if (!strcmp(key, "rhost"))
411 // Act on the message
413 if (addr_parse(&addr, rhost))
414 handle_failed_login(addr, cnt);
416 msg(L_WARN, "Unable to parse address %s", rhost);
419 static void process_msg(char *line)
421 DBG("Parse: <%s>", line);
425 // 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
426 // 2016-11-05T12:49:52.418880+01:00 sshd[16271]: PAM 2 more authentication failures; logname= uid=0 euid=0 tty=ssh ruser= rhost=116.31.116.26 user=root
428 // We shall start with 32 non-spaces
429 for (int i=0; i<32; i++)
435 DBG("Parse 1: <%s>", p);
437 // Space, something, colon, space
440 while (*p && *p != ' ' && *p != ':')
442 if (!check_next(&p, ": "))
444 DBG("Parse 2: <%s>", p);
446 // pam_unix(something), colon, space
447 if (check_next(&p, "pam_unix("))
456 if (!check_next(&p, ": "))
458 DBG("Parse 3: <%s>", p);
460 if (!check_next(&p, "authentication failure; "))
466 // "PAM <n> more authentication failures;"
467 if (check_next(&p, "PAM "))
469 if (!(*p >= '0' && *p <= '9'))
472 while (*p >= '0' && *p <= '9')
475 if (!check_next(&p, " more authentication failures; "))
478 parse_failure(p, cnt);
482 /*** Socket for receiving messages from rsyslog ***/
484 struct main_file sk_file;
486 static int sk_read(struct main_file *mf)
489 int len = recv(mf->fd, line, sizeof(line), MSG_TRUNC);
492 if (errno == EINTR || errno == EAGAIN)
497 if (len >= (int) sizeof(line))
499 msg(L_WARN, "Truncated message received (length=%d)", len);
500 len = sizeof(line) - 1;
504 if (len > 0 && line[len-1] == '\n')
506 if (len > 0 && line[len-1] == '\r')
513 static void sk_init(void)
516 mode_t old_umask = umask(0077);
519 if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
520 die("Cannot create PF_UNIX socket: %m");
522 struct sockaddr_un sa = { .sun_family = AF_UNIX };
523 strcpy(sa.sun_path, listen_on);
524 if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
525 die("Cannot bind socket %s: %m", listen_on);
528 sk_file.read_handler = sk_read;
536 static struct opt_section options = {
538 OPT_HELP("Bouncer -- A Daemon for Turning Away Mischievous Guests"),
540 OPT_HELP("Options:"),
547 int main(int argc UNUSED, char **argv)
549 cf_def_file = "/etc/bouncer";
550 cf_declare_section("Bouncer", &bouncer_cf, 0);
551 opt_parse(&options, argv+1);
553 if (config_log_stream)
554 log_configured(config_log_stream);
564 msg(L_INFO, "Starting");