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