Home | History | Annotate | Line # | Download | only in bin
      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
     85 sigusr1(int n __unused)
     86 {
     87 	debug++;
     88 }
     89 
     90 static void
     91 sigusr2(int n __unused)
     92 {
     93 	debug--;
     94 }
     95 
     96 static void
     97 sighup(int n __unused)
     98 {
     99 	readconf++;
    100 }
    101 
    102 static void
    103 sigdone(int n __unused)
    104 {
    105 	done++;
    106 }
    107 
    108 static __dead void
    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
    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
    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, set the
    227 		 * number of fails to be two less than the configured limit.
    228 		 * Fall through to the normal BL_ADD and BL_BADUSER processing,
    229 		 * which will increment the failure count to the threshold, and
    230 		 * block the abusive address.
    231 		 */
    232 		if (c.c_nfail != -1)
    233 			dbi.count = c.c_nfail - 2;
    234 		/*FALLTHROUGH*/
    235 	case BL_ADD:
    236 		dbi.count++;		/* will become += 2 */
    237 		/*FALLTHROUGH*/
    238 	case BL_BADUSER:
    239 		dbi.count++;
    240 		dbi.last = ts.tv_sec;
    241 		if (c.c_nfail != -1 && dbi.count >= c.c_nfail) {
    242 			/*
    243 			 * No point in re-adding the rule.
    244 			 * It might exist already due to latency in processing
    245 			 * and removing the rule is the wrong thing to do as
    246 			 * it allows a window to attack again.
    247 			 */
    248 			if (dbi.id[0] == '\0') {
    249 				int res = run_change("add", &c,
    250 				    dbi.id, sizeof(dbi.id));
    251 				if (res == -1)
    252 					goto out;
    253 			}
    254 			sockaddr_snprintf(rbuf, sizeof(rbuf), "%a",
    255 			    (void *)&rss);
    256 			(*lfun)(LOG_INFO,
    257 			    "blocked %s/%d:%d for %d seconds",
    258 			    rbuf, c.c_lmask, c.c_port, c.c_duration);
    259 		}
    260 		break;
    261 	case BL_DELETE:
    262 		if (dbi.last == 0)
    263 			goto out;
    264 		dbi.count = 0;
    265 		dbi.last = 0;
    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
    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
    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
    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
    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
    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
    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
    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