xref: /src/contrib/blocklist/bin/blocklistd.c (revision 56c94c76432b1aafcc9b099398e674f0ae87623c)
1 /*	$NetBSD: blocklistd.c,v 1.15 2026/02/07 14:32:04 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.15 2026/02/07 14:32:04 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 		goto out;
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\" "
200 		    "uid=%lu gid=%lu",
201 		    bi->bi_type, bi->bi_fd, rbuf,
202 		    bi->bi_msg, (unsigned long)bi->bi_uid,
203 		    (unsigned long)bi->bi_gid);
204 	}
205 
206 	if (conf_find(bi->bi_fd, bi->bi_uid, &rss, &c) == NULL) {
207 		(*lfun)(LOG_DEBUG, "no rule matched");
208 		goto out;
209 	}
210 
211 
212 	if (state_get(state, &c, &dbi) == -1)
213 		goto out;
214 
215 	if (debug) {
216 		char b1[128], b2[128];
217 		(*lfun)(LOG_DEBUG, "%s: initial db state for %s: count=%d/%d "
218 		    "last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,
219 		    fmttime(b1, sizeof(b1), dbi.last),
220 		    fmttime(b2, sizeof(b2), ts.tv_sec));
221 	}
222 
223 	switch (bi->bi_type) {
224 	case BL_ABUSE:
225 		/*
226 		 * If the application has signaled abusive behavior,
227 		 * set the number of fails to be one less than the
228 		 * configured limit.  Fallthrough to the normal BL_ADD
229 		 * processing, which will increment the failure count
230 		 * to the threshold, and block the abusive address.
231 		 */
232 		if (c.c_nfail != -1)
233 			dbi.count = c.c_nfail - 1;
234 		/*FALLTHROUGH*/
235 	case BL_ADD:
236 		dbi.count++;
237 		dbi.last = ts.tv_sec;
238 		if (c.c_nfail != -1 && dbi.count >= c.c_nfail) {
239 			/*
240 			 * No point in re-adding the rule.
241 			 * It might exist already due to latency in processing
242 			 * and removing the rule is the wrong thing to do as
243 			 * it allows a window to attack again.
244 			 */
245 			if (dbi.id[0] == '\0') {
246 				int res = run_change("add", &c,
247 				    dbi.id, sizeof(dbi.id));
248 				if (res == -1)
249 					goto out;
250 			}
251 			sockaddr_snprintf(rbuf, sizeof(rbuf), "%a",
252 			    (void *)&rss);
253 			(*lfun)(LOG_INFO,
254 			    "blocked %s/%d:%d for %d seconds",
255 			    rbuf, c.c_lmask, c.c_port, c.c_duration);
256 		}
257 		break;
258 	case BL_DELETE:
259 		if (dbi.last == 0)
260 			goto out;
261 		dbi.count = 0;
262 		dbi.last = 0;
263 		break;
264 	case BL_BADUSER:
265 		/* ignore for now */
266 		break;
267 	default:
268 		(*lfun)(LOG_ERR, "unknown message %d", bi->bi_type);
269 	}
270 	state_put(state, &c, &dbi);
271 
272 out:
273 	close(bi->bi_fd);
274 
275 	if (debug) {
276 		char b1[128], b2[128];
277 		(*lfun)(LOG_DEBUG, "%s: final db state for %s: count=%d/%d "
278 		    "last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,
279 		    fmttime(b1, sizeof(b1), dbi.last),
280 		    fmttime(b2, sizeof(b2), ts.tv_sec));
281 	}
282 }
283 
284 static void
update_interfaces(void)285 update_interfaces(void)
286 {
287 	struct ifaddrs *oifas, *nifas;
288 
289 	if (getifaddrs(&nifas) == -1)
290 		return;
291 
292 	oifas = ifas;
293 	ifas = nifas;
294 
295 	if (oifas)
296 		freeifaddrs(oifas);
297 }
298 
299 static void
update(void)300 update(void)
301 {
302 	struct timespec ts;
303 	struct conf c;
304 	struct dbinfo dbi;
305 	unsigned int f, n;
306 	char buf[128];
307 	void *ss = &c.c_ss;
308 
309 	if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {
310 		(*lfun)(LOG_ERR, "clock_gettime failed (%m)");
311 		return;
312 	}
313 
314 again:
315 	for (n = 0, f = 1; state_iterate(state, &c, &dbi, f) == 1;
316 	    f = 0, n++)
317 	{
318 		time_t when = c.c_duration + dbi.last;
319 		if (debug > 1) {
320 			char b1[64], b2[64];
321 			sockaddr_snprintf(buf, sizeof(buf), "%a:%p", ss);
322 			(*lfun)(LOG_DEBUG, "%s:[%u] %s count=%d duration=%d "
323 			    "last=%s " "now=%s", __func__, n, buf, dbi.count,
324 			    c.c_duration, fmttime(b1, sizeof(b1), dbi.last),
325 			    fmttime(b2, sizeof(b2), ts.tv_sec));
326 		}
327 		if (c.c_duration == -1 || when >= ts.tv_sec)
328 			continue;
329 		if (dbi.id[0]) {
330 			run_change("rem", &c, dbi.id, 0);
331 			sockaddr_snprintf(buf, sizeof(buf), "%a", ss);
332 			(*lfun)(LOG_INFO, "released %s/%d:%d after %d seconds",
333 			    buf, c.c_lmask, c.c_port, c.c_duration);
334 		}
335 		if (state_del(state, &c) == 0)
336 			goto again;
337 	}
338 }
339 
340 static void
addfd(struct pollfd ** pfdp,bl_t ** blp,size_t * nfd,size_t * maxfd,const char * path)341 addfd(struct pollfd **pfdp, bl_t **blp, size_t *nfd, size_t *maxfd,
342     const char *path)
343 {
344 	bl_t bl = bl_create(true, path, vflag ? vdlog : vsyslog_r);
345 	if (bl == NULL || !bl_isconnected(bl))
346 		exit(EXIT_FAILURE);
347 	if (*nfd >= *maxfd) {
348 		*maxfd += 10;
349 		*blp = reallocarray(*blp, *maxfd, sizeof(**blp));
350 		if (*blp == NULL)
351 			err(EXIT_FAILURE, "malloc");
352 		*pfdp = reallocarray(*pfdp, *maxfd, sizeof(**pfdp));
353 		if (*pfdp == NULL)
354 			err(EXIT_FAILURE, "malloc");
355 	}
356 
357 	(*pfdp)[*nfd].fd = bl_getfd(bl);
358 	(*pfdp)[*nfd].events = POLLIN;
359 	(*blp)[*nfd] = bl;
360 	*nfd += 1;
361 }
362 
363 static void
uniqueadd(struct conf *** listp,size_t * nlist,size_t * mlist,struct conf * c)364 uniqueadd(struct conf ***listp, size_t *nlist, size_t *mlist, struct conf *c)
365 {
366 	struct conf **list = *listp;
367 
368 	if (c->c_name[0] == '\0')
369 		return;
370 	for (size_t i = 0; i < *nlist; i++) {
371 		if (strcmp(list[i]->c_name, c->c_name) == 0)
372 			return;
373 	}
374 	if (*nlist == *mlist) {
375 		*mlist += 10;
376 		void *p = reallocarray(*listp, *mlist, sizeof(*list));
377 		if (p == NULL)
378 			err(EXIT_FAILURE, "Can't allocate for rule list");
379 		list = *listp = p;
380 	}
381 	list[(*nlist)++] = c;
382 }
383 
384 static void
rules_flush(void)385 rules_flush(void)
386 {
387 	struct conf **list;
388 	size_t nlist, mlist;
389 
390 	list = NULL;
391 	mlist = nlist = 0;
392 	for (size_t i = 0; i < rconf.cs_n; i++)
393 		uniqueadd(&list, &nlist, &mlist, &rconf.cs_c[i]);
394 	for (size_t i = 0; i < lconf.cs_n; i++)
395 		uniqueadd(&list, &nlist, &mlist, &lconf.cs_c[i]);
396 
397 	for (size_t i = 0; i < nlist; i++)
398 		run_flush(list[i]);
399 	free(list);
400 }
401 
402 static void
rules_restore(void)403 rules_restore(void)
404 {
405 	DB *db;
406 	struct conf c;
407 	struct dbinfo dbi;
408 	unsigned int f;
409 
410 	db = state_open(dbfile, O_RDONLY, 0);
411 	if (db == NULL) {
412 		(*lfun)(LOG_ERR, "Can't open `%s' to restore state (%m)",
413 		    dbfile);
414 		return;
415 	}
416 	for (f = 1; state_iterate(db, &c, &dbi, f) == 1; f = 0) {
417 		if (dbi.id[0] == '\0')
418 			continue;
419 		(void)run_change("add", &c, dbi.id, sizeof(dbi.id));
420 		state_put(state, &c, &dbi);
421 	}
422 	state_close(db);
423 	state_sync(state);
424 }
425 
426 int
main(int argc,char * argv[])427 main(int argc, char *argv[])
428 {
429 	int c, tout, flags, flush, restore, ret;
430 	const char *spath, **blsock;
431 	size_t nblsock, maxblsock;
432 
433 	setprogname(argv[0]);
434 
435 	spath = NULL;
436 	blsock = NULL;
437 	maxblsock = nblsock = 0;
438 	flush = 0;
439 	restore = 0;
440 	tout = 0;
441 	flags = O_RDWR|O_EXCL|O_CLOEXEC;
442 	while ((c = getopt(argc, argv, "C:c:D:dfP:rR:s:t:v")) != -1) {
443 		switch (c) {
444 		case 'C':
445 			controlprog = optarg;
446 			break;
447 		case 'c':
448 			configfile = optarg;
449 			break;
450 		case 'D':
451 			dbfile = optarg;
452 			break;
453 		case 'd':
454 			debug++;
455 			break;
456 		case 'f':
457 			flush++;
458 			break;
459 		case 'P':
460 			spath = optarg;
461 			break;
462 		case 'R':
463 			rulename = optarg;
464 			break;
465 		case 'r':
466 			restore++;
467 			break;
468 		case 's':
469 			if (nblsock >= maxblsock) {
470 				maxblsock += 10;
471 				void *p = reallocarray(blsock, maxblsock,
472 				    sizeof(*blsock));
473 				if (p == NULL)
474 					err(EXIT_FAILURE, "Can't allocate "
475 					    "memory for %zu sockets",
476 					    maxblsock);
477 				blsock = p;
478 			}
479 			blsock[nblsock++] = optarg;
480 			break;
481 		case 't':
482 			tout = atoi(optarg) * 1000;
483 			break;
484 		case 'v':
485 			vflag++;
486 			break;
487 		default:
488 			usage(c);
489 		}
490 	}
491 
492 	argc -= optind;
493 	if (argc)
494 		usage('?');
495 
496 	signal(SIGHUP, sighup);
497 	signal(SIGINT, sigdone);
498 	signal(SIGQUIT, sigdone);
499 	signal(SIGTERM, sigdone);
500 	signal(SIGUSR1, sigusr1);
501 	signal(SIGUSR2, sigusr2);
502 
503 	openlog(getprogname(), LOG_PID, LOG_DAEMON);
504 
505 	if (debug) {
506 		lfun = dlog;
507 		if (tout == 0)
508 			tout = 5000;
509 	} else {
510 		if (tout == 0)
511 			tout = 15000;
512 	}
513 
514 	update_interfaces();
515 	conf_parse(configfile);
516 	if (flush) {
517 		rules_flush();
518 		if (!restore)
519 			flags |= O_TRUNC;
520 	}
521 
522 	struct pollfd *pfd = NULL;
523 	bl_t *bl = NULL;
524 	size_t nfd = 0;
525 	size_t maxfd = 0;
526 
527 	for (size_t i = 0; i < nblsock; i++)
528 		addfd(&pfd, &bl, &nfd, &maxfd, blsock[i]);
529 	free(blsock);
530 
531 	if (spath) {
532 		FILE *fp = fopen(spath, "r");
533 		char *line;
534 		if (fp == NULL)
535 			err(EXIT_FAILURE, "Can't open `%s'", spath);
536 		for (; (line = fparseln(fp, NULL, NULL, NULL, 0)) != NULL;
537 		    free(line))
538 			addfd(&pfd, &bl, &nfd, &maxfd, line);
539 		fclose(fp);
540 	}
541 	if (nfd == 0)
542 		addfd(&pfd, &bl, &nfd, &maxfd, _PATH_BLSOCK);
543 
544 	state = state_open(dbfile, flags, 0600);
545 	if (state == NULL)
546 		state = state_open(dbfile,  flags | O_CREAT, 0600);
547 	else {
548 		if (restore) {
549 			if (!flush)
550 				rules_flush();
551 			rules_restore();
552 		}
553 	}
554 	if (state == NULL)
555 		exit(EXIT_FAILURE);
556 
557 	if (!debug) {
558 		if (daemon(0, 0) == -1)
559 			err(EXIT_FAILURE, "daemon failed");
560 		if (pidfile(NULL) == -1)
561 			err(EXIT_FAILURE, "Can't create pidfile");
562 	}
563 
564 	for (size_t t = 0; !done; t++) {
565 		if (readconf) {
566 			readconf = 0;
567 			conf_parse(configfile);
568 		}
569 		ret = poll(pfd, (nfds_t)nfd, tout);
570 		if (debug)
571 			(*lfun)(LOG_DEBUG, "received %d from poll()", ret);
572 		switch (ret) {
573 		case -1:
574 			if (errno == EINTR)
575 				continue;
576 			(*lfun)(LOG_ERR, "poll (%m)");
577 			exit(EXIT_FAILURE);
578 		case 0:
579 			state_sync(state);
580 			break;
581 		default:
582 			for (size_t i = 0; i < nfd; i++)
583 				if (pfd[i].revents & POLLIN)
584 					process(bl[i]);
585 		}
586 		if (t % 100 == 0)
587 			state_sync(state);
588 		if (t % 10000 == 0)
589 			update_interfaces();
590 		update();
591 	}
592 	state_close(state);
593 	exit(EXIT_SUCCESS);
594 }
595