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