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