Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/freebsd-src
Path: blob/main/contrib/blocklist/bin/blocklistd.c
105189 views
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,
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
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
596