Path: blob/main/contrib/blocklist/bin/blacklistd.c
39478 views
/* $NetBSD: blacklistd.c,v 1.38 2019/02/27 02:20:18 christos Exp $ */12/*-3* Copyright (c) 2015 The NetBSD Foundation, Inc.4* All rights reserved.5*6* This code is derived from software contributed to The NetBSD Foundation7* by Christos Zoulas.8*9* Redistribution and use in source and binary forms, with or without10* modification, are permitted provided that the following conditions11* are met:12* 1. Redistributions of source code must retain the above copyright13* notice, this list of conditions and the following disclaimer.14* 2. Redistributions in binary form must reproduce the above copyright15* notice, this list of conditions and the following disclaimer in the16* documentation and/or other materials provided with the distribution.17*18* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS19* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED20* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR21* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS22* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR23* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF24* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS25* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN26* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)27* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE28* POSSIBILITY OF SUCH DAMAGE.29*/30#ifdef HAVE_CONFIG_H31#include "config.h"32#endif33#include <sys/cdefs.h>34__RCSID("$NetBSD: blacklistd.c,v 1.38 2019/02/27 02:20:18 christos Exp $");3536#include <sys/types.h>37#include <sys/socket.h>38#include <sys/queue.h>3940#ifdef HAVE_LIBUTIL_H41#include <libutil.h>42#endif43#ifdef HAVE_UTIL_H44#include <util.h>45#endif46#include <string.h>47#include <signal.h>48#include <netdb.h>49#include <stdio.h>50#include <stdbool.h>51#include <string.h>52#include <inttypes.h>53#include <syslog.h>54#include <ctype.h>55#include <limits.h>56#include <errno.h>57#include <poll.h>58#include <fcntl.h>59#include <err.h>60#include <stdlib.h>61#include <unistd.h>62#include <time.h>63#include <ifaddrs.h>64#include <netinet/in.h>6566#include "bl.h"67#include "internal.h"68#include "conf.h"69#include "run.h"70#include "state.h"71#include "support.h"7273static const char *configfile = _PATH_BLCONF;74static DB *state;75static const char *dbfile = _PATH_BLSTATE;76static sig_atomic_t readconf;77static sig_atomic_t done;78static int vflag;7980static void81sigusr1(int n __unused)82{83debug++;84}8586static void87sigusr2(int n __unused)88{89debug--;90}9192static void93sighup(int n __unused)94{95readconf++;96}9798static void99sigdone(int n __unused)100{101done++;102}103104static __dead void105usage(int c)106{107if (c != '?')108warnx("Unknown option `%c'", (char)c);109fprintf(stderr, "Usage: %s [-vdfr] [-c <config>] [-R <rulename>] "110"[-P <sockpathsfile>] [-C <controlprog>] [-D <dbfile>] "111"[-s <sockpath>] [-t <timeout>]\n", getprogname());112exit(EXIT_FAILURE);113}114115static int116getremoteaddress(bl_info_t *bi, struct sockaddr_storage *rss, socklen_t *rsl)117{118*rsl = sizeof(*rss);119memset(rss, 0, *rsl);120121if (getpeername(bi->bi_fd, (void *)rss, rsl) != -1)122return 0;123124if (errno != ENOTCONN) {125(*lfun)(LOG_ERR, "getpeername failed (%m)");126return -1;127}128129if (bi->bi_slen == 0) {130(*lfun)(LOG_ERR, "unconnected socket with no peer in message");131return -1;132}133134switch (bi->bi_ss.ss_family) {135case AF_INET:136*rsl = sizeof(struct sockaddr_in);137break;138case AF_INET6:139*rsl = sizeof(struct sockaddr_in6);140break;141default:142(*lfun)(LOG_ERR, "bad client passed socket family %u",143(unsigned)bi->bi_ss.ss_family);144return -1;145}146147if (*rsl != bi->bi_slen) {148(*lfun)(LOG_ERR, "bad client passed socket length %u != %u",149(unsigned)*rsl, (unsigned)bi->bi_slen);150return -1;151}152153memcpy(rss, &bi->bi_ss, *rsl);154155#ifdef HAVE_STRUCT_SOCKADDR_SA_LEN156if (*rsl != rss->ss_len) {157(*lfun)(LOG_ERR,158"bad client passed socket internal length %u != %u",159(unsigned)*rsl, (unsigned)rss->ss_len);160return -1;161}162#endif163return 0;164}165166static void167process(bl_t bl)168{169struct sockaddr_storage rss;170socklen_t rsl;171char rbuf[BUFSIZ];172bl_info_t *bi;173struct conf c;174struct dbinfo dbi;175struct timespec ts;176177if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {178(*lfun)(LOG_ERR, "clock_gettime failed (%m)");179return;180}181182if ((bi = bl_recv(bl)) == NULL) {183(*lfun)(LOG_ERR, "no message (%m)");184return;185}186187if (getremoteaddress(bi, &rss, &rsl) == -1)188goto out;189190if (debug) {191sockaddr_snprintf(rbuf, sizeof(rbuf), "%a:%p", (void *)&rss);192(*lfun)(LOG_DEBUG, "processing type=%d fd=%d remote=%s msg=%s"193" uid=%lu gid=%lu", bi->bi_type, bi->bi_fd, rbuf,194bi->bi_msg, (unsigned long)bi->bi_uid,195(unsigned long)bi->bi_gid);196}197198if (conf_find(bi->bi_fd, bi->bi_uid, &rss, &c) == NULL) {199(*lfun)(LOG_DEBUG, "no rule matched");200goto out;201}202203204if (state_get(state, &c, &dbi) == -1)205goto out;206207if (debug) {208char b1[128], b2[128];209(*lfun)(LOG_DEBUG, "%s: initial db state for %s: count=%d/%d "210"last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,211fmttime(b1, sizeof(b1), dbi.last),212fmttime(b2, sizeof(b2), ts.tv_sec));213}214215switch (bi->bi_type) {216case BL_ABUSE:217/*218* If the application has signaled abusive behavior,219* set the number of fails to be one less than the220* configured limit. Fallthrough to the normal BL_ADD221* processing, which will increment the failure count222* to the threshhold, and block the abusive address.223*/224if (c.c_nfail != -1)225dbi.count = c.c_nfail - 1;226/*FALLTHROUGH*/227case BL_ADD:228dbi.count++;229dbi.last = ts.tv_sec;230if (c.c_nfail != -1 && dbi.count >= c.c_nfail) {231/*232* No point in re-adding the rule.233* It might exist already due to latency in processing234* and removing the rule is the wrong thing to do as235* it allows a window to attack again.236*/237if (dbi.id[0] == '\0') {238int res = run_change("add", &c,239dbi.id, sizeof(dbi.id));240if (res == -1)241goto out;242}243sockaddr_snprintf(rbuf, sizeof(rbuf), "%a",244(void *)&rss);245(*lfun)(LOG_INFO,246"blocked %s/%d:%d for %d seconds",247rbuf, c.c_lmask, c.c_port, c.c_duration);248}249break;250case BL_DELETE:251if (dbi.last == 0)252goto out;253dbi.count = 0;254dbi.last = 0;255break;256case BL_BADUSER:257/* ignore for now */258break;259default:260(*lfun)(LOG_ERR, "unknown message %d", bi->bi_type);261}262state_put(state, &c, &dbi);263264out:265close(bi->bi_fd);266267if (debug) {268char b1[128], b2[128];269(*lfun)(LOG_DEBUG, "%s: final db state for %s: count=%d/%d "270"last=%s now=%s", __func__, rbuf, dbi.count, c.c_nfail,271fmttime(b1, sizeof(b1), dbi.last),272fmttime(b2, sizeof(b2), ts.tv_sec));273}274}275276static void277update_interfaces(void)278{279struct ifaddrs *oifas, *nifas;280281if (getifaddrs(&nifas) == -1)282return;283284oifas = ifas;285ifas = nifas;286287if (oifas)288freeifaddrs(oifas);289}290291static void292update(void)293{294struct timespec ts;295struct conf c;296struct dbinfo dbi;297unsigned int f, n;298char buf[128];299void *ss = &c.c_ss;300301if (clock_gettime(CLOCK_REALTIME, &ts) == -1) {302(*lfun)(LOG_ERR, "clock_gettime failed (%m)");303return;304}305306again:307for (n = 0, f = 1; state_iterate(state, &c, &dbi, f) == 1;308f = 0, n++)309{310time_t when = c.c_duration + dbi.last;311if (debug > 1) {312char b1[64], b2[64];313sockaddr_snprintf(buf, sizeof(buf), "%a:%p", ss);314(*lfun)(LOG_DEBUG, "%s:[%u] %s count=%d duration=%d "315"last=%s " "now=%s", __func__, n, buf, dbi.count,316c.c_duration, fmttime(b1, sizeof(b1), dbi.last),317fmttime(b2, sizeof(b2), ts.tv_sec));318}319if (c.c_duration == -1 || when >= ts.tv_sec)320continue;321if (dbi.id[0]) {322run_change("rem", &c, dbi.id, 0);323sockaddr_snprintf(buf, sizeof(buf), "%a", ss);324(*lfun)(LOG_INFO, "released %s/%d:%d after %d seconds",325buf, c.c_lmask, c.c_port, c.c_duration);326}327state_del(state, &c);328goto again;329}330}331332static void333addfd(struct pollfd **pfdp, bl_t **blp, size_t *nfd, size_t *maxfd,334const char *path)335{336bl_t bl = bl_create(true, path, vflag ? vdlog : vsyslog);337if (bl == NULL || !bl_isconnected(bl))338exit(EXIT_FAILURE);339if (*nfd >= *maxfd) {340*maxfd += 10;341*blp = realloc(*blp, sizeof(**blp) * *maxfd);342if (*blp == NULL)343err(EXIT_FAILURE, "malloc");344*pfdp = realloc(*pfdp, sizeof(**pfdp) * *maxfd);345if (*pfdp == NULL)346err(EXIT_FAILURE, "malloc");347}348349(*pfdp)[*nfd].fd = bl_getfd(bl);350(*pfdp)[*nfd].events = POLLIN;351(*blp)[*nfd] = bl;352*nfd += 1;353}354355static void356uniqueadd(struct conf ***listp, size_t *nlist, size_t *mlist, struct conf *c)357{358struct conf **list = *listp;359360if (c->c_name[0] == '\0')361return;362for (size_t i = 0; i < *nlist; i++) {363if (strcmp(list[i]->c_name, c->c_name) == 0)364return;365}366if (*nlist == *mlist) {367*mlist += 10;368void *p = realloc(*listp, *mlist * sizeof(*list));369if (p == NULL)370err(EXIT_FAILURE, "Can't allocate for rule list");371list = *listp = p;372}373list[(*nlist)++] = c;374}375376static void377rules_flush(void)378{379struct conf **list;380size_t nlist, mlist;381382list = NULL;383mlist = nlist = 0;384for (size_t i = 0; i < rconf.cs_n; i++)385uniqueadd(&list, &nlist, &mlist, &rconf.cs_c[i]);386for (size_t i = 0; i < lconf.cs_n; i++)387uniqueadd(&list, &nlist, &mlist, &lconf.cs_c[i]);388389for (size_t i = 0; i < nlist; i++)390run_flush(list[i]);391free(list);392}393394static void395rules_restore(void)396{397struct conf c;398struct dbinfo dbi;399unsigned int f;400401for (f = 1; state_iterate(state, &c, &dbi, f) == 1; f = 0) {402if (dbi.id[0] == '\0')403continue;404(void)run_change("add", &c, dbi.id, sizeof(dbi.id));405}406}407408int409main(int argc, char *argv[])410{411int c, tout, flags, flush, restore, ret;412const char *spath, **blsock;413size_t nblsock, maxblsock;414415setprogname(argv[0]);416417spath = NULL;418blsock = NULL;419maxblsock = nblsock = 0;420flush = 0;421restore = 0;422tout = 0;423flags = O_RDWR|O_EXCL|O_CLOEXEC;424while ((c = getopt(argc, argv, "C:c:D:dfP:rR:s:t:v")) != -1) {425switch (c) {426case 'C':427controlprog = optarg;428break;429case 'c':430configfile = optarg;431break;432case 'D':433dbfile = optarg;434break;435case 'd':436debug++;437break;438case 'f':439flush++;440break;441case 'P':442spath = optarg;443break;444case 'R':445rulename = optarg;446break;447case 'r':448restore++;449break;450case 's':451if (nblsock >= maxblsock) {452maxblsock += 10;453void *p = realloc(blsock,454sizeof(*blsock) * maxblsock);455if (p == NULL)456err(EXIT_FAILURE,457"Can't allocate memory for %zu sockets",458maxblsock);459blsock = p;460}461blsock[nblsock++] = optarg;462break;463case 't':464tout = atoi(optarg) * 1000;465break;466case 'v':467vflag++;468break;469default:470usage(c);471}472}473474argc -= optind;475if (argc)476usage('?');477478signal(SIGHUP, sighup);479signal(SIGINT, sigdone);480signal(SIGQUIT, sigdone);481signal(SIGTERM, sigdone);482signal(SIGUSR1, sigusr1);483signal(SIGUSR2, sigusr2);484485openlog(getprogname(), LOG_PID, LOG_DAEMON);486487if (debug) {488lfun = dlog;489if (tout == 0)490tout = 5000;491} else {492if (tout == 0)493tout = 15000;494}495496update_interfaces();497conf_parse(configfile);498if (flush) {499rules_flush();500if (!restore)501flags |= O_TRUNC;502}503504struct pollfd *pfd = NULL;505bl_t *bl = NULL;506size_t nfd = 0;507size_t maxfd = 0;508509for (size_t i = 0; i < nblsock; i++)510addfd(&pfd, &bl, &nfd, &maxfd, blsock[i]);511free(blsock);512513if (spath) {514FILE *fp = fopen(spath, "r");515char *line;516if (fp == NULL)517err(EXIT_FAILURE, "Can't open `%s'", spath);518for (; (line = fparseln(fp, NULL, NULL, NULL, 0)) != NULL;519free(line))520addfd(&pfd, &bl, &nfd, &maxfd, line);521fclose(fp);522}523if (nfd == 0)524addfd(&pfd, &bl, &nfd, &maxfd, _PATH_BLSOCK);525526state = state_open(dbfile, flags, 0600);527if (state == NULL)528state = state_open(dbfile, flags | O_CREAT, 0600);529if (state == NULL)530return EXIT_FAILURE;531532if (restore) {533if (!flush)534rules_flush();535rules_restore();536}537538if (!debug) {539if (daemon(0, 0) == -1)540err(EXIT_FAILURE, "daemon failed");541if (pidfile(NULL) == -1)542err(EXIT_FAILURE, "Can't create pidfile");543}544545for (size_t t = 0; !done; t++) {546if (readconf) {547readconf = 0;548conf_parse(configfile);549}550ret = poll(pfd, (nfds_t)nfd, tout);551if (debug)552(*lfun)(LOG_DEBUG, "received %d from poll()", ret);553switch (ret) {554case -1:555if (errno == EINTR)556continue;557(*lfun)(LOG_ERR, "poll (%m)");558return EXIT_FAILURE;559case 0:560state_sync(state);561break;562default:563for (size_t i = 0; i < nfd; i++)564if (pfd[i].revents & POLLIN)565process(bl[i]);566}567if (t % 100 == 0)568state_sync(state);569if (t % 10000 == 0)570update_interfaces();571update();572}573state_close(state);574return 0;575}576577578