]> mj.ucw.cz Git - bouncer.git/blob - bouncer.c
e5edf9e1150fee491e46fc4f1e9a090f13572120
[bouncer.git] / bouncer.c
1 /*
2  *      Bouncer -- A Daemon for Turning Away Mischievous Guests
3  *
4  *      (c) 2016 Martin Mares <mj@ucw.cz>
5  *
6  *      FIXME: ipset create bouncer4 hash:ip family inet
7  *      FIXME: ipset create bouncer6 hash:ip family inet6
8  *      FIXME: sshd_config: UseDNS no
9  *      FIXME: PAM module names should be made configurable
10  */
11
12 #undef LOCAL_DEBUG
13
14 #include <ucw/lib.h>
15 #include <ucw/clists.h>
16 #include <ucw/conf.h>
17 #include <ucw/log.h>
18 #include <ucw/mainloop.h>
19 #include <ucw/opt.h>
20 #include <ucw/string.h>
21
22 #include <alloca.h>
23 #include <arpa/inet.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <sys/types.h>
27 #include <sys/socket.h>
28 #include <sys/stat.h>
29 #include <sys/un.h>
30 #include <time.h>
31 #include <unistd.h>
32
33 #include <libipset/data.h>
34 #include <libipset/session.h>
35 #include <libipset/types.h>
36
37 /*** Internal representation of IPv4/IPv6 addresses ***/
38
39 // In network byte order, IPv4 represented as ::ffff:1.2.3.4
40 struct addr {
41   u32 a[4];
42 };
43
44 #define ADDR_BUFSIZE 64
45 #define AFMT(_a) ({ char *_buf = alloca(ADDR_BUFSIZE); addr_format(_buf, _a); _buf; })
46
47 static bool addr_is_v4(struct addr addr)
48 {
49   return !addr.a[0] && !addr.a[1] && addr.a[2] == htonl(0xffff);
50 }
51
52 static void addr_format(char *buf, struct addr addr)
53 {
54   const char *ok;
55   if (addr_is_v4(addr))
56     {
57       struct in_addr in4;
58       in4.s_addr = addr.a[3];
59       ok = inet_ntop(AF_INET, &in4, buf, ADDR_BUFSIZE);
60     }
61   else
62     {
63       struct in6_addr in6;
64       memcpy(&in6, &addr, sizeof(addr));
65       ok = inet_ntop(AF_INET6, &in6, buf, ADDR_BUFSIZE);
66     }
67   if (!ok)
68     snprintf(buf, ADDR_BUFSIZE, "<error %d>", errno);
69 }
70
71 static bool addr_parse(struct addr *addr, const char *src)
72 {
73   struct in_addr a4;
74   struct in6_addr a6;
75
76   if (inet_pton(AF_INET, src, &a4))
77     {
78       addr->a[0] = addr->a[1] = 0;
79       addr->a[2] = htonl(0xffff);
80       addr->a[3] = a4.s_addr;
81       return 1;
82     }
83   else if (inet_pton(AF_INET6, src, &a6))
84     {
85       memcpy(&addr, &a6, 16);
86       return 1;
87     }
88   else
89     return 0;
90 }
91
92 /*** Configuration ***/
93
94 static char *listen_on = "/var/run/bouncer.sock";
95 static uns max_failures = ~0U;
96 static uns max_suspect_time = 86400;
97 static uns max_banned_time = 86400;
98 static uns max_suspects = ~0U;
99 static uns max_banned = ~0U;
100 static char *ipv4_set;
101 static char *ipv6_set;
102 static char *config_log_stream;
103
104 static struct cf_section bouncer_cf = {
105   CF_ITEMS {
106     CF_STRING("ListenOn", &listen_on),
107     CF_UNS("MaxSuspects", &max_suspects),
108     CF_UNS("MaxBanned", &max_banned),
109     CF_UNS("MaxSuspectTime", &max_suspect_time),
110     CF_UNS("MaxBannedTime", &max_banned_time),
111     CF_UNS("MaxFailures", &max_failures),
112     CF_STRING("IPv4Set", &ipv4_set),
113     CF_STRING("IPv6Set", &ipv6_set),
114     CF_STRING("LogStream", &config_log_stream),
115     CF_END
116   }
117 };
118
119 /*** An interface to IP sets ***/
120
121 static struct ipset_session *is_sess;
122
123 static const char *trim_eol(const char *msg)
124 {
125   int len = strlen(msg);
126   if (!len || msg[len-1] != '\n')
127     return msg;
128   else
129     {
130       char *x = xstrdup(msg);
131       x[len-1] = 0;
132       return x;
133     }
134 }
135
136 static void is_die(const char *when)
137 {
138   const char *warn = ipset_session_warning(is_sess);
139   if (warn)
140     msg(L_WARN, "%s: %s", when, trim_eol(warn));
141
142   const char *err = ipset_session_error(is_sess);
143   die("%s: %s", when, err ? trim_eol(err) : "Unknown error");
144 }
145
146 static void is_err(const char *when)
147 {
148   const char *warn = ipset_session_warning(is_sess);
149   if (warn)
150     msg(L_WARN, "%s: %s", when, trim_eol(warn));
151
152   const char *err = ipset_session_error(is_sess);
153   msg(L_ERROR, "%s: %s", when, err ? trim_eol(err) : "Unknown error");
154
155   ipset_session_report_reset(is_sess);
156 }
157
158 static void is_init(void)
159 {
160   ipset_load_types();
161
162   is_sess = ipset_session_init(printf);
163   if (!is_sess)
164     die("Unable to initialize ipset session");
165 }
166
167 static bool is_setup(char *set)
168 {
169   if (!set)
170     return 0;
171
172   if (ipset_parse_setname(is_sess, IPSET_SETNAME, set) < 0)
173     is_die("ipset_parse_setname");
174   return 1;
175 }
176
177 static void is_flush(char *set)
178 {
179   if (!is_setup(set))
180     return;
181
182   if (ipset_cmd(is_sess, IPSET_CMD_FLUSH, 0) < 0)
183     return is_err("IPSET_CMD_FLUSH");
184 }
185
186 static bool is_modify(bool add, struct addr addr)
187 {
188   int cmd = add ? IPSET_CMD_ADD : IPSET_CMD_DEL;
189   char *set = addr_is_v4(addr) ? ipv4_set : ipv6_set;
190   if (!is_setup(set))
191     return 0;
192
193   if (ipset_envopt_parse(is_sess, IPSET_ENV_EXIST, NULL) < 0)
194     is_die("IPSET_ENV_EXIST");
195
196   const struct ipset_type *is_type = ipset_type_get(is_sess, cmd);
197   if (!is_type)
198     is_die("ipset_type_get");
199
200   if (is_type->dimension != 1)
201     die("Invalid ipset dimension %d", is_type->dimension);
202
203   char buf[ADDR_BUFSIZE];
204   addr_format(buf, addr);
205   if (ipset_parse_elem(is_sess, 0, buf) < 0)
206     {
207       is_err("ipset_parse_elem");
208       return 0;
209     }
210
211   if (ipset_cmd(is_sess, cmd, 0) < 0)
212     {
213       is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL");
214       return 0;
215     }
216
217   return 1;
218 }
219
220 /*** Handling of login failures ***/
221
222 struct culprit_node {
223   cnode n;                      // In either suspect_list or banned_list
224   union {
225     struct addr addr;
226     byte addr_bytes[16];
227   };
228   uns fail_count;
229   bool banned;
230   timestamp_t last_fail;        // Not updated when banned
231 };
232
233 #define HASH_NODE struct culprit_node
234 #define HASH_PREFIX(x) culprit_##x
235 #define HASH_KEY_MEMORY addr_bytes
236 #define HASH_KEY_SIZE 16
237 #define HASH_WANT_LOOKUP
238 #define HASH_WANT_REMOVE
239 #define HASH_USE_AUTO_ELTPOOL 1000
240 #define HASH_ZERO_FILL
241 #define HASH_LOOKUP_DETECT_NEW
242 #include <ucw/hashtable.h>
243
244 static clist suspect_list, banned_list;
245 static uns num_suspects, num_banned;
246 static struct main_timer cleanup_timer;
247
248 static void cleanup_list(clist *list, uns *counter, timestamp_t max_time, uns max_count, timestamp_t *next)
249 {
250   timestamp_t now = main_get_now();
251
252   for (;;)
253     {
254       struct culprit_node *c = clist_head(list);
255       if (!c)
256         break;
257
258       timestamp_t expire_in = c->last_fail + max_time;
259       if (*counter > max_count)
260         {
261           static timestamp_t last_overflow_warning;
262           if (last_overflow_warning + 60000 < now)
263             {
264               last_overflow_warning = now;
265               if (c->banned)
266                 msg(L_WARN, "Too many bans, dropping some. Try increasing MaxBanned.");
267               else
268                 msg(L_WARN, "Too many suspects, dropping some. Try increasing MaxSuspects.");
269             }
270           expire_in = 0;
271         }
272
273       if (expire_in > now)
274         {
275           *next = MIN(*next, expire_in);
276           break;
277         }
278
279       if (c->banned)
280         {
281           msg(L_INFO, "Unbanning %s", AFMT(c->addr));
282           is_modify(0, c->addr);
283         }
284       else
285         msg(L_DEBUG, "Suspect %s: acquitted", AFMT(c->addr));
286
287       clist_remove(&c->n);
288       culprit_remove(c);
289       (*counter)--;
290     }
291 }
292
293 static void culprit_cleanup(void)
294 {
295   timestamp_t next_cleanup = main_get_now() + (timestamp_t)3600 * 1000;
296   cleanup_list(&suspect_list, &num_suspects, (timestamp_t)max_suspect_time * 1000, max_suspects, &next_cleanup);
297   cleanup_list(&banned_list, &num_banned, (timestamp_t)max_banned_time * 1000, max_banned, &next_cleanup);
298   timer_add(&cleanup_timer, next_cleanup);
299 }
300
301 static void handle_failed_login(struct addr addr, int cnt)
302 {
303   int is_new;
304   timestamp_t now = main_get_now();
305
306   struct culprit_node *c = culprit_lookup((byte *) &addr, &is_new);
307   if (is_new)
308     {
309       c->last_fail = now;
310       c->fail_count = cnt;
311       c->banned = 0;
312       clist_add_tail(&suspect_list, &c->n);
313       num_suspects++;
314       msg(L_DEBUG, "Suspect %s: new, failures=%u", AFMT(addr), c->fail_count);
315     }
316   else if (!c->banned)
317     {
318       c->last_fail = now;
319       c->fail_count += cnt;
320       clist_remove(&c->n);
321       clist_add_tail(&suspect_list, &c->n);
322       msg(L_DEBUG, "Suspect %s: failures=%u", AFMT(addr), c->fail_count);
323     }
324
325   if (!c->banned && c->fail_count >= max_failures)
326     {
327       msg(L_INFO, "Banning %s: failures=%u", AFMT(addr), c->fail_count);
328       c->banned = 1;
329       clist_remove(&c->n);
330       num_suspects--;
331       clist_add_tail(&banned_list, &c->n);
332       num_banned++;
333       is_modify(1, c->addr);
334     }
335
336   culprit_cleanup();
337 }
338
339 static void culprit_timer(struct main_timer *tm UNUSED)
340 {
341   culprit_cleanup();
342 }
343
344 static void fail_init(void)
345 {
346   culprit_init();
347   clist_init(&suspect_list);
348   clist_init(&banned_list);
349   cleanup_timer.handler = culprit_timer;
350 }
351
352 /*** Parsing of log messages ***/
353
354 static bool check_next(char **pp, char *want)
355 {
356   char *p = *pp;
357   while (*want)
358     {
359       if (*p++ != *want++)
360         return 0;
361     }
362   *pp = p;
363   return 1;
364 }
365
366 static void process_msg(char *line)
367 {
368   DBG("Parse: <%s>", line);
369
370   char *p = line;
371   int c;
372   // 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
373   // 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
374
375   // We shall start with 32 non-spaces
376   for (int i=0; i<32; i++)
377     {
378       c = *p++;
379       if (!c || c == ' ')
380         return;
381     }
382   DBG("Parse 1: <%s>", p);
383
384   // Space, something, colon, space
385   if (*p++ != ' ')
386     return;
387   while (*p && *p != ' ' && *p != ':')
388     p++;
389   if (!check_next(&p, ": "))
390     return;
391   DBG("Parse 2: <%s>", p);
392
393   // pam_unix(something), colon, space
394   if (!check_next(&p, "pam_unix("))
395     return;
396   do
397     {
398       c = *p++;
399       if (!c || c == ' ')
400         return;
401     }
402   while (c != ')');
403   if (!check_next(&p, ": "))
404     return;
405   DBG("Parse 3: <%s>", p);
406
407   // "authentication failure;"
408   int cnt = 1;
409   if (!check_next(&p, "authentication failure; "))
410     {
411       // "PAM <n> more authentication failures;"
412       if (!check_next(&p, "PAM "))
413         return;
414       if (!(*p >= '0' && *p <= '9'))
415         return;
416       cnt = atoi(p);
417       while (*p >= '0' && *p <= '9')
418         p++;
419       if (!check_next(&p, " more authentication failures; "))
420         return;
421     }
422   DBG("Parse 4: <%s> cnt=%d", p, cnt);
423
424   // Decode attributes
425   bool done = 0;
426   char *rhost = NULL;
427   while (!done)
428     {
429       while (*p == ' ')
430         p++;
431       if (!*p)
432         break;
433
434       char *key = p;
435       while (*p && *p != ' ' && *p != '=')
436         p++;
437       if (*p != '=')
438         continue;
439       *p++ = 0;
440
441       char *val = p;
442       while (*p && *p != ' ')
443         p++;
444       if (*p)
445         *p++ = 0;
446       else
447         done = 1;
448
449       DBG("Parse KV: %s=<%s>", key, val);
450       if (!strcmp(key, "rhost"))
451         rhost = val;
452     }
453
454   // Act on the message
455   struct addr addr;
456   if (addr_parse(&addr, rhost))
457     handle_failed_login(addr, cnt);
458   else
459     msg(L_WARN, "Unable to parse address %s", rhost);
460 }
461
462 /*** Socket for receiving messages from rsyslog ***/
463
464 struct main_file sk_file;
465
466 static int sk_read(struct main_file *mf)
467 {
468   char line[1024];
469   int len = recv(mf->fd, line, sizeof(line), MSG_TRUNC);
470   if (len < 0)
471     {
472       if (errno == EINTR || errno == EAGAIN)
473         return HOOK_IDLE;
474       die("recv: %m");
475     }
476
477   if (len >= (int) sizeof(line))
478     {
479       msg(L_WARN, "Truncated message received (length=%d)", len);
480       len = sizeof(line) - 1;
481     }
482   line[len] = 0;
483
484   if (len > 0 && line[len-1] == '\n')
485     line[--len] = 0;
486   if (len > 0 && line[len-1] == '\r')
487     line[--len] = 0;
488
489   process_msg(line);
490   return HOOK_RETRY;
491 }
492
493 static void sk_init(void)
494 {
495   unlink(listen_on);
496   mode_t old_umask = umask(0077);
497
498   int fd;
499   if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
500     die("Cannot create PF_UNIX socket: %m");
501
502   struct sockaddr_un sa = { .sun_family = AF_UNIX };
503   strcpy(sa.sun_path, listen_on);
504   if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
505     die("Cannot bind socket %s: %m", listen_on);
506
507   sk_file.fd = fd;
508   sk_file.read_handler = sk_read;
509   file_add(&sk_file);
510
511   umask(old_umask);
512 }
513
514 /*** Main ***/
515
516 static struct opt_section options = {
517   OPT_ITEMS {
518     OPT_HELP("Bouncer -- A Daemon for Turning Away Mischievous Guests"),
519     OPT_HELP(""),
520     OPT_HELP("Options:"),
521     OPT_HELP_OPTION,
522     OPT_CONF_OPTIONS,
523     OPT_END
524   }
525 };
526
527 int main(int argc UNUSED, char **argv)
528 {
529   cf_def_file = "config";       // FIXME
530   cf_declare_section("Bouncer", &bouncer_cf, 0);
531   opt_parse(&options, argv+1);
532
533   if (config_log_stream)
534     log_configured(config_log_stream);
535
536   main_init();
537   is_init();
538   fail_init();
539   sk_init();
540
541   is_flush(ipv4_set);
542   is_flush(ipv6_set);
543
544   msg(L_INFO, "Starting");
545   main_loop();
546   return 0;
547 }