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