blocklistd.c revision 1.6 1 /* $NetBSD: blocklistd.c,v 1.6 2025/02/05 20:04:18 christos Exp $ */
2
3 /*-
4 * Copyright (c) 2015 The NetBSD Foundation, Inc.
5 * All rights reserved.
6 *
7 * This code is derived from software contributed to The NetBSD Foundation
8 * by Christos Zoulas.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions
12 * are met:
13 * 1. Redistributions of source code must retain the above copyright
14 * notice, this list of conditions and the following disclaimer.
15 * 2. Redistributions in binary form must reproduce the above copyright
16 * notice, this list of conditions and the following disclaimer in the
17 * documentation and/or other materials provided with the distribution.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 */
31 #ifdef HAVE_CONFIG_H
32 #include "config.h"
33 #endif
34 #include <sys/cdefs.h>
35 __RCSID("$NetBSD: blocklistd.c,v 1.6 2025/02/05 20:04:18 christos Exp $");
36
37 #include <sys/types.h>
38 #include <sys/socket.h>
39 #include <sys/queue.h>
40
41 #ifdef HAVE_LIBUTIL_H
42 #include <libutil.h>
43 #endif
44 #ifdef HAVE_UTIL_H
45 #include <util.h>
46 #endif
47 #include <string.h>
48 #include <signal.h>
49 #include <netdb.h>
50 #include <stdio.h>
51 #include <stdbool.h>
52 #include <string.h>
53 #include <inttypes.h>
54 #include <syslog.h>
55 #include <ctype.h>
56 #include <limits.h>
57 #include <errno.h>
58 #include <poll.h>
59 #include <fcntl.h>
60 #include <err.h>
61 #include <stdlib.h>
62 #include <unistd.h>
63 #include <time.h>
64 #include <ifaddrs.h>
65 #include <netinet/in.h>
66
67 #include "bl.h"
68 #include "internal.h"
69 #include "conf.h"
70 #include "run.h"
71 #include "state.h"
72 #include "support.h"
73
74 static const char *configfile = _PATH_BLCONF;
75 static DB *state;
76 static const char *dbfile = _PATH_BLSTATE;
77 static sig_atomic_t readconf;
78 static sig_atomic_t done;
79 static int vflag;
80
81 static void
82 sigusr1(int n __unused)
83 {
84 debug++;
85 }
86
87 static void
88 sigusr2(int n __unused)
89 {
90 debug--;
91 }
92
93 static void
94 sighup(int n __unused)
95 {
96 readconf++;
97 }
98
99 static void
100 sigdone(int n __unused)
101 {
102 done++;
103 }
104
105 static __dead void
106 usage(int c)
107 {
108 if (c != '?')
109 warnx("Unknown option `%c'", (char)c);
110 fprintf(stderr, "Usage: %s [-vdfr] [-c <config>] [-R <rulename>] "
111 "[-P <sockpathsfile>] [-C <controlprog>] [-D <dbfile>] "
112 "[-s <sockpath>] [-t <timeout>]\n", getprogname());
113 exit(EXIT_FAILURE);
114 }
115
116 static int
117 getremoteaddress(bl_info_t *bi, struct sockaddr_storage *rss, socklen_t *rsl)
118 {
119 *rsl = sizeof(*rss);
120 memset(rss, 0, *rsl);
121
122 if (getpeername(bi->bi_fd, (void *)rss, rsl) != -1)
123 return 0;
124
125 if (errno != ENOTCONN) {
126 (*lfun)(LOG_ERR, "getpeername failed (%m)");
127 return -1;
128 }
129
130 if (bi->bi_slen == 0) {
131 (*lfun)(LOG_ERR, "unconnected socket with no peer in message");
132 return -1;
133 }
134
135 switch (bi->bi_ss.ss_family) {
136 case AF_INET:
137 *rsl = sizeof(struct sockaddr_in);
138 break;
139 case AF_INET6:
140 *rsl = sizeof(struct sockaddr_in6);
141 break;
142 default:
143 (*lfun)(LOG_ERR, "bad client passed socket family %u",
144 (unsigned)bi->bi_ss.ss_family);
145 return -1;
146 }
147
148 if (*rsl != bi->bi_slen) {
149 (*lfun)(LOG_ERR, "bad client passed socket length %u != %u",
150 (unsigned)*rsl, (unsigned)bi->bi_slen);
151 return -1;
152 }
153
154 memcpy(rss, &bi->bi_ss, *rsl);
155
156 #ifdef HAVE_STRUCT_SOCKADDR_SA_LEN
157 if (*rsl != rss->ss_len) {
158 (*lfun)(LOG_ERR,
159 "bad client passed socket internal length %u != %u",
160 (unsigned)*rsl, (unsigned)rss->ss_len);
161 return -1;
162 }
163 #endif
164 return 0;
165 }
166
167 static void
168 process(bl_t bl)
169 {
170 struct sockaddr_storage rss;
171 socklen_t rsl;
172 char rbuf[BUFSIZ];
173 bl_info_t *bi;
174 struct conf c;
175 struct dbinfo dbi;
176 struct timespec ts;
177
178 if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
179 (*lfun)(LOG_ERR, "clock_gettime failed (%m)");
180 return;
181 }
182
183 if ((bi = bl_recv(bl)) == NULL) {
184 (*lfun)(LOG_ERR, "no message (%m)");
185 return;
186 }
187
188 if (getremoteaddress(bi, &rss, &rsl) == -1)
189 goto out;
190
191 if (debug || bi->bi_msg[0]) {
192 sockaddr_snprintf(rbuf, sizeof(rbuf), "%a:%p", (void *)&rss);
193 (*lfun)(bi->bi_msg[0] ? LOG_INFO : LOG_DEBUG,
194 "processing type=%d fd=%d remote=%s msg=%s uid=%lu gid=%lu",
195 bi->bi_type, bi->bi_fd, rbuf,
196 bi->bi_msg, (unsigned long)bi->bi_uid,
197 (unsigned long)bi->bi_gid);
198 }
199
200 if (conf_find(bi->bi_fd, bi->bi_uid, &rss, &c) == NULL) {
201 (*lfun)(LOG_DEBUG, "no rule matched");
202 goto out;
203 }
204
205
206 if (state_get(state, &c, &dbi) == -1)
207 goto out;
208
209 if (debug) {
210 char b1[128], b2[128];
211 (*lfun)(LOG_DEBUG, "%s: initial db state for %s: count=%d/%d "
212 "last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,
213 fmttime(b1, sizeof(b1), dbi.last),
214 fmttime(b2, sizeof(b2), ts.tv_sec));
215 }
216
217 switch (bi->bi_type) {
218 case BL_ABUSE:
219 /*
220 * If the application has signaled abusive behavior,
221 * set the number of fails to be one less than the
222 * configured limit. Fallthrough to the normal BL_ADD
223 * processing, which will increment the failure count
224 * to the threshhold, and block the abusive address.
225 */
226 if (c.c_nfail != -1)
227 dbi.count = c.c_nfail - 1;
228 /*FALLTHROUGH*/
229 case BL_ADD:
230 dbi.count++;
231 dbi.last = ts.tv_sec;
232 if (c.c_nfail != -1 && dbi.count >= c.c_nfail) {
233 /*
234 * No point in re-adding the rule.
235 * It might exist already due to latency in processing
236 * and removing the rule is the wrong thing to do as
237 * it allows a window to attack again.
238 */
239 if (dbi.id[0] == '\0') {
240 int res = run_change("add", &c,
241 dbi.id, sizeof(dbi.id));
242 if (res == -1)
243 goto out;
244 }
245 sockaddr_snprintf(rbuf, sizeof(rbuf), "%a",
246 (void *)&rss);
247 (*lfun)(LOG_INFO,
248 "blocked %s/%d:%d for %d seconds",
249 rbuf, c.c_lmask, c.c_port, c.c_duration);
250 }
251 break;
252 case BL_DELETE:
253 if (dbi.last == 0)
254 goto out;
255 dbi.count = 0;
256 dbi.last = 0;
257 break;
258 case BL_BADUSER:
259 /* ignore for now */
260 break;
261 default:
262 (*lfun)(LOG_ERR, "unknown message %d", bi->bi_type);
263 }
264 state_put(state, &c, &dbi);
265
266 out:
267 close(bi->bi_fd);
268
269 if (debug) {
270 char b1[128], b2[128];
271 (*lfun)(LOG_DEBUG, "%s: final db state for %s: count=%d/%d "
272 "last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,
273 fmttime(b1, sizeof(b1), dbi.last),
274 fmttime(b2, sizeof(b2), ts.tv_sec));
275 }
276 }
277
278 static void
279 update_interfaces(void)
280 {
281 struct ifaddrs *oifas, *nifas;
282
283 if (getifaddrs(&nifas) == -1)
284 return;
285
286 oifas = ifas;
287 ifas = nifas;
288
289 if (oifas)
290 freeifaddrs(oifas);
291 }
292
293 static void
294 update(void)
295 {
296 struct timespec ts;
297 struct conf c;
298 struct dbinfo dbi;
299 unsigned int f, n;
300 char buf[128];
301 void *ss = &c.c_ss;
302
303 if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
304 (*lfun)(LOG_ERR, "clock_gettime failed (%m)");
305 return;
306 }
307
308 again:
309 for (n = 0, f = 1; state_iterate(state, &c, &dbi, f) == 1;
310 f = 0, n++)
311 {
312 time_t when = c.c_duration + dbi.last;
313 if (debug > 1) {
314 char b1[64], b2[64];
315 sockaddr_snprintf(buf, sizeof(buf), "%a:%p", ss);
316 (*lfun)(LOG_DEBUG, "%s:[%u] %s count=%d duration=%d "
317 "last=%s " "now=%s", __func__, n, buf, dbi.count,
318 c.c_duration, fmttime(b1, sizeof(b1), dbi.last),
319 fmttime(b2, sizeof(b2), ts.tv_sec));
320 }
321 if (c.c_duration == -1 || when >= ts.tv_sec)
322 continue;
323 if (dbi.id[0]) {
324 run_change("rem", &c, dbi.id, 0);
325 sockaddr_snprintf(buf, sizeof(buf), "%a", ss);
326 (*lfun)(LOG_INFO, "released %s/%d:%d after %d seconds",
327 buf, c.c_lmask, c.c_port, c.c_duration);
328 }
329 state_del(state, &c);
330 goto again;
331 }
332 }
333
334 static void
335 addfd(struct pollfd **pfdp, bl_t **blp, size_t *nfd, size_t *maxfd,
336 const char *path)
337 {
338 bl_t bl = bl_create(true, path, vflag ? vdlog : vsyslog_r);
339 if (bl == NULL || !bl_isconnected(bl))
340 exit(EXIT_FAILURE);
341 if (*nfd >= *maxfd) {
342 *maxfd += 10;
343 *blp = realloc(*blp, sizeof(**blp) * *maxfd);
344 if (*blp == NULL)
345 err(EXIT_FAILURE, "malloc");
346 *pfdp = realloc(*pfdp, sizeof(**pfdp) * *maxfd);
347 if (*pfdp == NULL)
348 err(EXIT_FAILURE, "malloc");
349 }
350
351 (*pfdp)[*nfd].fd = bl_getfd(bl);
352 (*pfdp)[*nfd].events = POLLIN;
353 (*blp)[*nfd] = bl;
354 *nfd += 1;
355 }
356
357 static void
358 uniqueadd(struct conf ***listp, size_t *nlist, size_t *mlist, struct conf *c)
359 {
360 struct conf **list = *listp;
361
362 if (c->c_name[0] == '\0')
363 return;
364 for (size_t i = 0; i < *nlist; i++) {
365 if (strcmp(list[i]->c_name, c->c_name) == 0)
366 return;
367 }
368 if (*nlist == *mlist) {
369 *mlist += 10;
370 void *p = realloc(*listp, *mlist * sizeof(*list));
371 if (p == NULL)
372 err(EXIT_FAILURE, "Can't allocate for rule list");
373 list = *listp = p;
374 }
375 list[(*nlist)++] = c;
376 }
377
378 static void
379 rules_flush(void)
380 {
381 struct conf **list;
382 size_t nlist, mlist;
383
384 list = NULL;
385 mlist = nlist = 0;
386 for (size_t i = 0; i < rconf.cs_n; i++)
387 uniqueadd(&list, &nlist, &mlist, &rconf.cs_c[i]);
388 for (size_t i = 0; i < lconf.cs_n; i++)
389 uniqueadd(&list, &nlist, &mlist, &lconf.cs_c[i]);
390
391 for (size_t i = 0; i < nlist; i++)
392 run_flush(list[i]);
393 free(list);
394 }
395
396 static void
397 rules_restore(void)
398 {
399 DB *db;
400 struct conf c;
401 struct dbinfo dbi;
402 unsigned int f;
403
404 db = state_open(dbfile, O_RDONLY, 0);
405 if (db == NULL) {
406 (*lfun)(LOG_ERR, "Can't open `%s' to restore state (%m)",
407 dbfile);
408 return;
409 }
410 for (f = 1; state_iterate(db, &c, &dbi, f) == 1; f = 0) {
411 if (dbi.id[0] == '\0')
412 continue;
413 (void)run_change("add", &c, dbi.id, sizeof(dbi.id));
414 state_put(state, &c, &dbi);
415 }
416 state_close(db);
417 state_sync(state);
418 }
419
420 int
421 main(int argc, char *argv[])
422 {
423 int c, tout, flags, flush, restore, ret;
424 const char *spath, **blsock;
425 size_t nblsock, maxblsock;
426
427 setprogname(argv[0]);
428
429 spath = NULL;
430 blsock = NULL;
431 maxblsock = nblsock = 0;
432 flush = 0;
433 restore = 0;
434 tout = 0;
435 flags = O_RDWR|O_EXCL|O_CLOEXEC;
436 while ((c = getopt(argc, argv, "C:c:D:dfP:rR:s:t:v")) != -1) {
437 switch (c) {
438 case 'C':
439 controlprog = optarg;
440 break;
441 case 'c':
442 configfile = optarg;
443 break;
444 case 'D':
445 dbfile = optarg;
446 break;
447 case 'd':
448 debug++;
449 break;
450 case 'f':
451 flush++;
452 break;
453 case 'P':
454 spath = optarg;
455 break;
456 case 'R':
457 rulename = optarg;
458 break;
459 case 'r':
460 restore++;
461 break;
462 case 's':
463 if (nblsock >= maxblsock) {
464 maxblsock += 10;
465 void *p = realloc(blsock,
466 sizeof(*blsock) * maxblsock);
467 if (p == NULL)
468 err(EXIT_FAILURE,
469 "Can't allocate memory for %zu sockets",
470 maxblsock);
471 blsock = p;
472 }
473 blsock[nblsock++] = optarg;
474 break;
475 case 't':
476 tout = atoi(optarg) * 1000;
477 break;
478 case 'v':
479 vflag++;
480 break;
481 default:
482 usage(c);
483 }
484 }
485
486 argc -= optind;
487 if (argc)
488 usage('?');
489
490 signal(SIGHUP, sighup);
491 signal(SIGINT, sigdone);
492 signal(SIGQUIT, sigdone);
493 signal(SIGTERM, sigdone);
494 signal(SIGUSR1, sigusr1);
495 signal(SIGUSR2, sigusr2);
496
497 openlog(getprogname(), LOG_PID, LOG_DAEMON);
498
499 if (debug) {
500 lfun = dlog;
501 if (tout == 0)
502 tout = 5000;
503 } else {
504 if (tout == 0)
505 tout = 15000;
506 }
507
508 update_interfaces();
509 conf_parse(configfile);
510 if (flush) {
511 rules_flush();
512 if (!restore)
513 flags |= O_TRUNC;
514 }
515
516 struct pollfd *pfd = NULL;
517 bl_t *bl = NULL;
518 size_t nfd = 0;
519 size_t maxfd = 0;
520
521 for (size_t i = 0; i < nblsock; i++)
522 addfd(&pfd, &bl, &nfd, &maxfd, blsock[i]);
523 free(blsock);
524
525 if (spath) {
526 FILE *fp = fopen(spath, "r");
527 char *line;
528 if (fp == NULL)
529 err(EXIT_FAILURE, "Can't open `%s'", spath);
530 for (; (line = fparseln(fp, NULL, NULL, NULL, 0)) != NULL;
531 free(line))
532 addfd(&pfd, &bl, &nfd, &maxfd, line);
533 fclose(fp);
534 }
535 if (nfd == 0)
536 addfd(&pfd, &bl, &nfd, &maxfd, _PATH_BLSOCK);
537
538 state = state_open(dbfile, flags, 0600);
539 if (state == NULL)
540 state = state_open(dbfile, flags | O_CREAT, 0600);
541 if (state == NULL)
542 return EXIT_FAILURE;
543
544 if (restore) {
545 if (!flush)
546 rules_flush();
547 rules_restore();
548 }
549
550 if (!debug) {
551 if (daemon(0, 0) == -1)
552 err(EXIT_FAILURE, "daemon failed");
553 if (pidfile(NULL) == -1)
554 err(EXIT_FAILURE, "Can't create pidfile");
555 }
556
557 for (size_t t = 0; !done; t++) {
558 if (readconf) {
559 readconf = 0;
560 conf_parse(configfile);
561 }
562 ret = poll(pfd, (nfds_t)nfd, tout);
563 if (debug)
564 (*lfun)(LOG_DEBUG, "received %d from poll()", ret);
565 switch (ret) {
566 case -1:
567 if (errno == EINTR)
568 continue;
569 (*lfun)(LOG_ERR, "poll (%m)");
570 return EXIT_FAILURE;
571 case 0:
572 state_sync(state);
573 break;
574 default:
575 for (size_t i = 0; i < nfd; i++)
576 if (pfd[i].revents & POLLIN)
577 process(bl[i]);
578 }
579 if (t % 100 == 0)
580 state_sync(state);
581 if (t % 10000 == 0)
582 update_interfaces();
583 update();
584 }
585 state_close(state);
586 return 0;
587 }
588