1*9469f4f1Schristos /* $NetBSD: srclimit.c,v 1.5 2024/09/24 21:32:19 christos Exp $ */ 217418e98Schristos 3cffc2a7aSchristos /* 4cffc2a7aSchristos * Copyright (c) 2020 Darren Tucker <dtucker@openbsd.org> 51c7715ddSchristos * Copyright (c) 2024 Damien Miller <djm@mindrot.org> 6cffc2a7aSchristos * 7cffc2a7aSchristos * Permission to use, copy, modify, and distribute this software for any 8cffc2a7aSchristos * purpose with or without fee is hereby granted, provided that the above 9cffc2a7aSchristos * copyright notice and this permission notice appear in all copies. 10cffc2a7aSchristos * 11cffc2a7aSchristos * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 12cffc2a7aSchristos * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 13cffc2a7aSchristos * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 14cffc2a7aSchristos * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 15cffc2a7aSchristos * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 16cffc2a7aSchristos * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 17cffc2a7aSchristos * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 18cffc2a7aSchristos */ 1917418e98Schristos #include "includes.h" 20*9469f4f1Schristos __RCSID("$NetBSD: srclimit.c,v 1.5 2024/09/24 21:32:19 christos Exp $"); 21cffc2a7aSchristos 22cffc2a7aSchristos #include <sys/socket.h> 23cffc2a7aSchristos #include <sys/types.h> 241c7715ddSchristos #include <sys/tree.h> 25cffc2a7aSchristos 26cffc2a7aSchristos #include <limits.h> 27cffc2a7aSchristos #include <netdb.h> 28cffc2a7aSchristos #include <stdio.h> 29cffc2a7aSchristos #include <string.h> 301c7715ddSchristos #include <stdlib.h> 31cffc2a7aSchristos 32cffc2a7aSchristos #include "addr.h" 33cffc2a7aSchristos #include "canohost.h" 34cffc2a7aSchristos #include "log.h" 35cffc2a7aSchristos #include "misc.h" 36cffc2a7aSchristos #include "srclimit.h" 37cffc2a7aSchristos #include "xmalloc.h" 381c7715ddSchristos #include "servconf.h" 391c7715ddSchristos #include "match.h" 40cffc2a7aSchristos 41cffc2a7aSchristos static int max_children, max_persource, ipv4_masklen, ipv6_masklen; 421c7715ddSchristos static struct per_source_penalty penalty_cfg; 431c7715ddSchristos static char *penalty_exempt; 44cffc2a7aSchristos 45cffc2a7aSchristos /* Per connection state, used to enforce unauthenticated connection limit. */ 46cffc2a7aSchristos static struct child_info { 47cffc2a7aSchristos int id; 48cffc2a7aSchristos struct xaddr addr; 491c7715ddSchristos } *children; 501c7715ddSchristos 511c7715ddSchristos /* 521c7715ddSchristos * Penalised addresses, active entries here prohibit connections until expired. 531c7715ddSchristos * Entries become active when more than penalty_min seconds of penalty are 541c7715ddSchristos * outstanding. 551c7715ddSchristos */ 561c7715ddSchristos struct penalty { 571c7715ddSchristos struct xaddr addr; 581c7715ddSchristos time_t expiry; 591c7715ddSchristos int active; 601c7715ddSchristos const char *reason; 611c7715ddSchristos RB_ENTRY(penalty) by_addr; 621c7715ddSchristos RB_ENTRY(penalty) by_expiry; 631c7715ddSchristos }; 641c7715ddSchristos static int penalty_addr_cmp(struct penalty *a, struct penalty *b); 651c7715ddSchristos static int penalty_expiry_cmp(struct penalty *a, struct penalty *b); 661c7715ddSchristos RB_HEAD(penalties_by_addr, penalty) penalties_by_addr4, penalties_by_addr6; 671c7715ddSchristos RB_HEAD(penalties_by_expiry, penalty) penalties_by_expiry4, penalties_by_expiry6; 681c7715ddSchristos RB_GENERATE_STATIC(penalties_by_addr, penalty, by_addr, penalty_addr_cmp) 691c7715ddSchristos RB_GENERATE_STATIC(penalties_by_expiry, penalty, by_expiry, penalty_expiry_cmp) 701c7715ddSchristos static size_t npenalties4, npenalties6; 711c7715ddSchristos 721c7715ddSchristos static int 731c7715ddSchristos srclimit_mask_addr(const struct xaddr *addr, int bits, struct xaddr *masked) 741c7715ddSchristos { 751c7715ddSchristos struct xaddr xmask; 761c7715ddSchristos 771c7715ddSchristos /* Mask address off address to desired size. */ 781c7715ddSchristos if (addr_netmask(addr->af, bits, &xmask) != 0 || 791c7715ddSchristos addr_and(masked, addr, &xmask) != 0) { 801c7715ddSchristos debug3_f("%s: invalid mask %d bits", __func__, bits); 811c7715ddSchristos return -1; 821c7715ddSchristos } 831c7715ddSchristos return 0; 841c7715ddSchristos } 851c7715ddSchristos 861c7715ddSchristos static int 871c7715ddSchristos srclimit_peer_addr(int sock, struct xaddr *addr) 881c7715ddSchristos { 891c7715ddSchristos struct sockaddr_storage storage; 901c7715ddSchristos socklen_t addrlen = sizeof(storage); 911c7715ddSchristos struct sockaddr *sa = (struct sockaddr *)&storage; 921c7715ddSchristos 931c7715ddSchristos if (getpeername(sock, sa, &addrlen) != 0) 941c7715ddSchristos return 1; /* not remote socket? */ 951c7715ddSchristos if (addr_sa_to_xaddr(sa, addrlen, addr) != 0) 961c7715ddSchristos return 1; /* unknown address family? */ 971c7715ddSchristos return 0; 981c7715ddSchristos } 99cffc2a7aSchristos 100cffc2a7aSchristos void 1011c7715ddSchristos srclimit_init(int max, int persource, int ipv4len, int ipv6len, 1021c7715ddSchristos struct per_source_penalty *penalty_conf, const char *penalty_exempt_conf) 103cffc2a7aSchristos { 104cffc2a7aSchristos int i; 105cffc2a7aSchristos 106cffc2a7aSchristos max_children = max; 107cffc2a7aSchristos ipv4_masklen = ipv4len; 108cffc2a7aSchristos ipv6_masklen = ipv6len; 109cffc2a7aSchristos max_persource = persource; 1101c7715ddSchristos penalty_cfg = *penalty_conf; 1111c7715ddSchristos if (penalty_cfg.max_sources4 < 0 || penalty_cfg.max_sources6 < 0) 1121c7715ddSchristos fatal_f("invalid max_sources"); /* shouldn't happen */ 1131c7715ddSchristos penalty_exempt = penalty_exempt_conf == NULL ? 1141c7715ddSchristos NULL : xstrdup(penalty_exempt_conf); 1151c7715ddSchristos RB_INIT(&penalties_by_addr4); 1161c7715ddSchristos RB_INIT(&penalties_by_expiry4); 1171c7715ddSchristos RB_INIT(&penalties_by_addr6); 1181c7715ddSchristos RB_INIT(&penalties_by_expiry6); 119cffc2a7aSchristos if (max_persource == INT_MAX) /* no limit */ 120cffc2a7aSchristos return; 121cffc2a7aSchristos debug("%s: max connections %d, per source %d, masks %d,%d", __func__, 122cffc2a7aSchristos max, persource, ipv4len, ipv6len); 123cffc2a7aSchristos if (max <= 0) 124cffc2a7aSchristos fatal("%s: invalid number of sockets: %d", __func__, max); 1251c7715ddSchristos children = xcalloc(max_children, sizeof(*children)); 126cffc2a7aSchristos for (i = 0; i < max_children; i++) 1271c7715ddSchristos children[i].id = -1; 128cffc2a7aSchristos } 129cffc2a7aSchristos 130cffc2a7aSchristos /* returns 1 if connection allowed, 0 if not allowed. */ 131cffc2a7aSchristos int 132cffc2a7aSchristos srclimit_check_allow(int sock, int id) 133cffc2a7aSchristos { 1341c7715ddSchristos struct xaddr xa, xb; 135cffc2a7aSchristos int i, bits, first_unused, count = 0; 136cffc2a7aSchristos char xas[NI_MAXHOST]; 137cffc2a7aSchristos 138cffc2a7aSchristos if (max_persource == INT_MAX) /* no limit */ 139cffc2a7aSchristos return 1; 140cffc2a7aSchristos 141cffc2a7aSchristos debug("%s: sock %d id %d limit %d", __func__, sock, id, max_persource); 1421c7715ddSchristos if (srclimit_peer_addr(sock, &xa) != 0) 143cffc2a7aSchristos return 1; 1441c7715ddSchristos bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen; 1451c7715ddSchristos if (srclimit_mask_addr(&xa, bits, &xb) != 0) 1461c7715ddSchristos return 1; 147cffc2a7aSchristos 148cffc2a7aSchristos first_unused = max_children; 149cffc2a7aSchristos /* Count matching entries and find first unused one. */ 150cffc2a7aSchristos for (i = 0; i < max_children; i++) { 1511c7715ddSchristos if (children[i].id == -1) { 152cffc2a7aSchristos if (i < first_unused) 153cffc2a7aSchristos first_unused = i; 1541c7715ddSchristos } else if (addr_cmp(&children[i].addr, &xb) == 0) { 155cffc2a7aSchristos count++; 156cffc2a7aSchristos } 157cffc2a7aSchristos } 158cffc2a7aSchristos if (addr_ntop(&xa, xas, sizeof(xas)) != 0) { 159cffc2a7aSchristos debug3("%s: addr ntop failed", __func__); 160cffc2a7aSchristos return 1; 161cffc2a7aSchristos } 162cffc2a7aSchristos debug3("%s: new unauthenticated connection from %s/%d, at %d of %d", 163cffc2a7aSchristos __func__, xas, bits, count, max_persource); 164cffc2a7aSchristos 165cffc2a7aSchristos if (first_unused == max_children) { /* no free slot found */ 166cffc2a7aSchristos debug3("%s: no free slot", __func__); 167cffc2a7aSchristos return 0; 168cffc2a7aSchristos } 169cffc2a7aSchristos if (first_unused < 0 || first_unused >= max_children) 170cffc2a7aSchristos fatal("%s: internal error: first_unused out of range", 171cffc2a7aSchristos __func__); 172cffc2a7aSchristos 173cffc2a7aSchristos if (count >= max_persource) 174cffc2a7aSchristos return 0; 175cffc2a7aSchristos 176cffc2a7aSchristos /* Connection allowed, store masked address. */ 1771c7715ddSchristos children[first_unused].id = id; 1781c7715ddSchristos memcpy(&children[first_unused].addr, &xb, sizeof(xb)); 179cffc2a7aSchristos return 1; 180cffc2a7aSchristos } 181cffc2a7aSchristos 182cffc2a7aSchristos void 183cffc2a7aSchristos srclimit_done(int id) 184cffc2a7aSchristos { 185cffc2a7aSchristos int i; 186cffc2a7aSchristos 187cffc2a7aSchristos if (max_persource == INT_MAX) /* no limit */ 188cffc2a7aSchristos return; 189cffc2a7aSchristos 190cffc2a7aSchristos debug("%s: id %d", __func__, id); 191cffc2a7aSchristos /* Clear corresponding state entry. */ 192cffc2a7aSchristos for (i = 0; i < max_children; i++) { 1931c7715ddSchristos if (children[i].id == id) { 1941c7715ddSchristos children[i].id = -1; 195cffc2a7aSchristos return; 196cffc2a7aSchristos } 197cffc2a7aSchristos } 198cffc2a7aSchristos } 1991c7715ddSchristos 2001c7715ddSchristos static int 2011c7715ddSchristos penalty_addr_cmp(struct penalty *a, struct penalty *b) 2021c7715ddSchristos { 2031c7715ddSchristos return addr_cmp(&a->addr, &b->addr); 2041c7715ddSchristos /* Addresses must be unique in by_addr, so no need to tiebreak */ 2051c7715ddSchristos } 2061c7715ddSchristos 2071c7715ddSchristos static int 2081c7715ddSchristos penalty_expiry_cmp(struct penalty *a, struct penalty *b) 2091c7715ddSchristos { 2101c7715ddSchristos if (a->expiry != b->expiry) 2111c7715ddSchristos return a->expiry < b->expiry ? -1 : 1; 2121c7715ddSchristos /* Tiebreak on addresses */ 2131c7715ddSchristos return addr_cmp(&a->addr, &b->addr); 2141c7715ddSchristos } 2151c7715ddSchristos 2161c7715ddSchristos static void 2171c7715ddSchristos expire_penalties_from_tree(time_t now, const char *t, 2181c7715ddSchristos struct penalties_by_expiry *by_expiry, 2191c7715ddSchristos struct penalties_by_addr *by_addr, size_t *npenaltiesp) 2201c7715ddSchristos { 2211c7715ddSchristos struct penalty *penalty, *tmp; 2221c7715ddSchristos 2231c7715ddSchristos /* XXX avoid full scan of tree, e.g. min-heap */ 2241c7715ddSchristos RB_FOREACH_SAFE(penalty, penalties_by_expiry, by_expiry, tmp) { 2251c7715ddSchristos if (penalty->expiry >= now) 2261c7715ddSchristos break; 2271c7715ddSchristos if (RB_REMOVE(penalties_by_expiry, by_expiry, 2281c7715ddSchristos penalty) != penalty || 2291c7715ddSchristos RB_REMOVE(penalties_by_addr, by_addr, 2301c7715ddSchristos penalty) != penalty) 2311c7715ddSchristos fatal_f("internal error: %s penalty table corrupt", t); 2321c7715ddSchristos free(penalty); 2331c7715ddSchristos if ((*npenaltiesp)-- == 0) 2341c7715ddSchristos fatal_f("internal error: %s npenalties underflow", t); 2351c7715ddSchristos } 2361c7715ddSchristos } 2371c7715ddSchristos 2381c7715ddSchristos static void 2391c7715ddSchristos expire_penalties(time_t now) 2401c7715ddSchristos { 2411c7715ddSchristos expire_penalties_from_tree(now, "ipv4", 2421c7715ddSchristos &penalties_by_expiry4, &penalties_by_addr4, &npenalties4); 2431c7715ddSchristos expire_penalties_from_tree(now, "ipv6", 2441c7715ddSchristos &penalties_by_expiry6, &penalties_by_addr6, &npenalties6); 2451c7715ddSchristos } 2461c7715ddSchristos 2471c7715ddSchristos static void 2481c7715ddSchristos addr_masklen_ntop(struct xaddr *addr, int masklen, char *s, size_t slen) 2491c7715ddSchristos { 2501c7715ddSchristos size_t o; 2511c7715ddSchristos 2521c7715ddSchristos if (addr_ntop(addr, s, slen) != 0) { 2531c7715ddSchristos strlcpy(s, "UNKNOWN", slen); 2541c7715ddSchristos return; 2551c7715ddSchristos } 2561c7715ddSchristos if ((o = strlen(s)) < slen) 2571c7715ddSchristos snprintf(s + o, slen - o, "/%d", masklen); 2581c7715ddSchristos } 2591c7715ddSchristos 2601c7715ddSchristos int 2611c7715ddSchristos srclimit_penalty_check_allow(int sock, const char **reason) 2621c7715ddSchristos { 2631c7715ddSchristos struct xaddr addr; 2641c7715ddSchristos struct penalty find, *penalty; 2651c7715ddSchristos time_t now; 2661c7715ddSchristos int bits, max_sources, overflow_mode; 2671c7715ddSchristos char addr_s[NI_MAXHOST]; 2681c7715ddSchristos struct penalties_by_addr *by_addr; 2691c7715ddSchristos size_t npenalties; 2701c7715ddSchristos 2711c7715ddSchristos if (!penalty_cfg.enabled) 2721c7715ddSchristos return 1; 2731c7715ddSchristos if (srclimit_peer_addr(sock, &addr) != 0) 2741c7715ddSchristos return 1; 2751c7715ddSchristos if (penalty_exempt != NULL) { 2761c7715ddSchristos if (addr_ntop(&addr, addr_s, sizeof(addr_s)) != 0) 2771c7715ddSchristos return 1; /* shouldn't happen */ 2781c7715ddSchristos if (addr_match_list(addr_s, penalty_exempt) == 1) { 2791c7715ddSchristos return 1; 2801c7715ddSchristos } 2811c7715ddSchristos } 2821c7715ddSchristos now = monotime(); 2831c7715ddSchristos expire_penalties(now); 2841c7715ddSchristos by_addr = addr.af == AF_INET ? 2851c7715ddSchristos &penalties_by_addr4 : &penalties_by_addr6; 2861c7715ddSchristos max_sources = addr.af == AF_INET ? 2871c7715ddSchristos penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 2881c7715ddSchristos overflow_mode = addr.af == AF_INET ? 2891c7715ddSchristos penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 2901c7715ddSchristos npenalties = addr.af == AF_INET ? npenalties4 : npenalties6; 2911c7715ddSchristos if (npenalties >= (size_t)max_sources && 2921c7715ddSchristos overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 2931c7715ddSchristos *reason = "too many penalised addresses"; 2941c7715ddSchristos return 0; 2951c7715ddSchristos } 2961c7715ddSchristos bits = addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 2971c7715ddSchristos memset(&find, 0, sizeof(find)); 2981c7715ddSchristos if (srclimit_mask_addr(&addr, bits, &find.addr) != 0) 2991c7715ddSchristos return 1; 3001c7715ddSchristos if ((penalty = RB_FIND(penalties_by_addr, by_addr, &find)) == NULL) 3011c7715ddSchristos return 1; /* no penalty */ 3021c7715ddSchristos if (penalty->expiry < now) { 3031c7715ddSchristos expire_penalties(now); 3041c7715ddSchristos return 1; /* expired penalty */ 3051c7715ddSchristos } 3061c7715ddSchristos if (!penalty->active) 3071c7715ddSchristos return 1; /* Penalty hasn't hit activation threshold yet */ 3081c7715ddSchristos *reason = penalty->reason; 3091c7715ddSchristos return 0; 3101c7715ddSchristos } 3111c7715ddSchristos 3121c7715ddSchristos static void 3131c7715ddSchristos srclimit_early_expire_penalties_from_tree(const char *t, 3141c7715ddSchristos struct penalties_by_expiry *by_expiry, 3151c7715ddSchristos struct penalties_by_addr *by_addr, size_t *npenaltiesp, size_t max_sources) 3161c7715ddSchristos { 3171c7715ddSchristos struct penalty *p = NULL; 3181c7715ddSchristos int bits; 3191c7715ddSchristos char s[NI_MAXHOST + 4]; 3201c7715ddSchristos 3211c7715ddSchristos /* Delete the soonest-to-expire penalties. */ 3221c7715ddSchristos while (*npenaltiesp > max_sources) { 3231c7715ddSchristos if ((p = RB_MIN(penalties_by_expiry, by_expiry)) == NULL) 3241c7715ddSchristos fatal_f("internal error: %s table corrupt (find)", t); 3251c7715ddSchristos bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 3261c7715ddSchristos addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 3271c7715ddSchristos debug3_f("%s overflow, remove %s", t, s); 3281c7715ddSchristos if (RB_REMOVE(penalties_by_expiry, by_expiry, p) != p || 3291c7715ddSchristos RB_REMOVE(penalties_by_addr, by_addr, p) != p) 3301c7715ddSchristos fatal_f("internal error: %s table corrupt (remove)", t); 3311c7715ddSchristos free(p); 3321c7715ddSchristos (*npenaltiesp)--; 3331c7715ddSchristos } 3341c7715ddSchristos } 3351c7715ddSchristos 3361c7715ddSchristos static void 3371c7715ddSchristos srclimit_early_expire_penalties(void) 3381c7715ddSchristos { 3391c7715ddSchristos srclimit_early_expire_penalties_from_tree("ipv4", 3401c7715ddSchristos &penalties_by_expiry4, &penalties_by_addr4, &npenalties4, 3411c7715ddSchristos (size_t)penalty_cfg.max_sources4); 3421c7715ddSchristos srclimit_early_expire_penalties_from_tree("ipv6", 3431c7715ddSchristos &penalties_by_expiry6, &penalties_by_addr6, &npenalties6, 3441c7715ddSchristos (size_t)penalty_cfg.max_sources6); 3451c7715ddSchristos } 3461c7715ddSchristos 3471c7715ddSchristos void 3481c7715ddSchristos srclimit_penalise(struct xaddr *addr, int penalty_type) 3491c7715ddSchristos { 3501c7715ddSchristos struct xaddr masked; 3511c7715ddSchristos struct penalty *penalty = NULL, *existing = NULL; 3521c7715ddSchristos time_t now; 3531c7715ddSchristos int bits, penalty_secs, max_sources = 0, overflow_mode; 3541c7715ddSchristos char addrnetmask[NI_MAXHOST + 4]; 3551c7715ddSchristos const char *reason = NULL, *t; 3561c7715ddSchristos size_t *npenaltiesp = NULL; 3571c7715ddSchristos struct penalties_by_addr *by_addr = NULL; 3581c7715ddSchristos struct penalties_by_expiry *by_expiry = NULL; 3591c7715ddSchristos 3601c7715ddSchristos if (!penalty_cfg.enabled) 3611c7715ddSchristos return; 3621c7715ddSchristos if (penalty_exempt != NULL) { 3631c7715ddSchristos if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0) 3641c7715ddSchristos return; /* shouldn't happen */ 3651c7715ddSchristos if (addr_match_list(addrnetmask, penalty_exempt) == 1) { 3661c7715ddSchristos debug3_f("address %s is exempt", addrnetmask); 3671c7715ddSchristos return; 3681c7715ddSchristos } 3691c7715ddSchristos } 3701c7715ddSchristos 3711c7715ddSchristos switch (penalty_type) { 3721c7715ddSchristos case SRCLIMIT_PENALTY_NONE: 3731c7715ddSchristos return; 3741c7715ddSchristos case SRCLIMIT_PENALTY_CRASH: 3751c7715ddSchristos penalty_secs = penalty_cfg.penalty_crash; 3761c7715ddSchristos reason = "penalty: caused crash"; 3771c7715ddSchristos break; 3781c7715ddSchristos case SRCLIMIT_PENALTY_AUTHFAIL: 3791c7715ddSchristos penalty_secs = penalty_cfg.penalty_authfail; 3801c7715ddSchristos reason = "penalty: failed authentication"; 3811c7715ddSchristos break; 3821c7715ddSchristos case SRCLIMIT_PENALTY_NOAUTH: 3831c7715ddSchristos penalty_secs = penalty_cfg.penalty_noauth; 3841c7715ddSchristos reason = "penalty: connections without attempting authentication"; 3851c7715ddSchristos break; 386*9469f4f1Schristos case SRCLIMIT_PENALTY_REFUSECONNECTION: 387*9469f4f1Schristos penalty_secs = penalty_cfg.penalty_refuseconnection; 388*9469f4f1Schristos reason = "penalty: connection prohibited by RefuseConnection"; 389*9469f4f1Schristos break; 3901c7715ddSchristos case SRCLIMIT_PENALTY_GRACE_EXCEEDED: 3911c7715ddSchristos penalty_secs = penalty_cfg.penalty_crash; 3921c7715ddSchristos reason = "penalty: exceeded LoginGraceTime"; 3931c7715ddSchristos break; 3941c7715ddSchristos default: 3951c7715ddSchristos fatal_f("internal error: unknown penalty %d", penalty_type); 3961c7715ddSchristos } 3971c7715ddSchristos bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen; 3981c7715ddSchristos if (srclimit_mask_addr(addr, bits, &masked) != 0) 3991c7715ddSchristos return; 4001c7715ddSchristos addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask)); 4011c7715ddSchristos 4021c7715ddSchristos now = monotime(); 4031c7715ddSchristos expire_penalties(now); 4041c7715ddSchristos by_expiry = addr->af == AF_INET ? 4051c7715ddSchristos &penalties_by_expiry4 : &penalties_by_expiry6; 4061c7715ddSchristos by_addr = addr->af == AF_INET ? 4071c7715ddSchristos &penalties_by_addr4 : &penalties_by_addr6; 4081c7715ddSchristos max_sources = addr->af == AF_INET ? 4091c7715ddSchristos penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 4101c7715ddSchristos overflow_mode = addr->af == AF_INET ? 4111c7715ddSchristos penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 4121c7715ddSchristos npenaltiesp = addr->af == AF_INET ? &npenalties4 : &npenalties6; 4131c7715ddSchristos t = addr->af == AF_INET ? "ipv4" : "ipv6"; 4141c7715ddSchristos if (*npenaltiesp >= (size_t)max_sources && 4151c7715ddSchristos overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 4161c7715ddSchristos verbose_f("%s penalty table full, cannot penalise %s for %s", t, 4171c7715ddSchristos addrnetmask, reason); 4181c7715ddSchristos return; 4191c7715ddSchristos } 4201c7715ddSchristos 4211c7715ddSchristos penalty = xcalloc(1, sizeof(*penalty)); 4221c7715ddSchristos penalty->addr = masked; 4231c7715ddSchristos penalty->expiry = now + penalty_secs; 4241c7715ddSchristos penalty->reason = reason; 4251c7715ddSchristos if ((existing = RB_INSERT(penalties_by_addr, by_addr, 4261c7715ddSchristos penalty)) == NULL) { 4271c7715ddSchristos /* penalty didn't previously exist */ 4281c7715ddSchristos if (penalty_secs > penalty_cfg.penalty_min) 4291c7715ddSchristos penalty->active = 1; 4301c7715ddSchristos if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL) 4311c7715ddSchristos fatal_f("internal error: %s penalty tables corrupt", t); 4321c7715ddSchristos verbose_f("%s: new %s %s penalty of %d seconds for %s", t, 4331c7715ddSchristos addrnetmask, penalty->active ? "active" : "deferred", 4341c7715ddSchristos penalty_secs, reason); 4351c7715ddSchristos if (++(*npenaltiesp) > (size_t)max_sources) 4361c7715ddSchristos srclimit_early_expire_penalties(); /* permissive */ 4371c7715ddSchristos return; 4381c7715ddSchristos } 4391c7715ddSchristos debug_f("%s penalty for %s %s already exists, %lld seconds remaining", 4401c7715ddSchristos existing->active ? "active" : "inactive", t, 4411c7715ddSchristos addrnetmask, (long long)(existing->expiry - now)); 4421c7715ddSchristos /* Expiry information is about to change, remove from tree */ 4431c7715ddSchristos if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing) 4441c7715ddSchristos fatal_f("internal error: %s penalty table corrupt (remove)", t); 4451c7715ddSchristos /* An entry already existed. Accumulate penalty up to maximum */ 4461c7715ddSchristos existing->expiry += penalty_secs; 4471c7715ddSchristos if (existing->expiry - now > penalty_cfg.penalty_max) 4481c7715ddSchristos existing->expiry = now + penalty_cfg.penalty_max; 4491c7715ddSchristos if (existing->expiry - now > penalty_cfg.penalty_min && 4501c7715ddSchristos !existing->active) { 4511c7715ddSchristos verbose_f("%s: activating %s penalty of %lld seconds for %s", 4521c7715ddSchristos addrnetmask, t, (long long)(existing->expiry - now), 4531c7715ddSchristos reason); 4541c7715ddSchristos existing->active = 1; 4551c7715ddSchristos } 4561c7715ddSchristos existing->reason = penalty->reason; 4571c7715ddSchristos free(penalty); 4581c7715ddSchristos penalty = NULL; 4591c7715ddSchristos /* Re-insert into expiry tree */ 4601c7715ddSchristos if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL) 4611c7715ddSchristos fatal_f("internal error: %s penalty table corrupt (insert)", t); 4621c7715ddSchristos } 4631c7715ddSchristos 4641c7715ddSchristos static void 4651c7715ddSchristos srclimit_penalty_info_for_tree(const char *t, 4661c7715ddSchristos struct penalties_by_expiry *by_expiry, size_t npenalties) 4671c7715ddSchristos { 4681c7715ddSchristos struct penalty *p = NULL; 4691c7715ddSchristos int bits; 4701c7715ddSchristos char s[NI_MAXHOST + 4]; 4711c7715ddSchristos time_t now; 4721c7715ddSchristos 4731c7715ddSchristos now = monotime(); 4741c7715ddSchristos logit("%zu active %s penalties", npenalties, t); 4751c7715ddSchristos RB_FOREACH(p, penalties_by_expiry, by_expiry) { 4761c7715ddSchristos bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 4771c7715ddSchristos addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 4781c7715ddSchristos if (p->expiry < now) 4791c7715ddSchristos logit("client %s %s (expired)", s, p->reason); 4801c7715ddSchristos else { 4811c7715ddSchristos logit("client %s %s (%llu secs left)", s, p->reason, 4821c7715ddSchristos (long long)(p->expiry - now)); 4831c7715ddSchristos } 4841c7715ddSchristos } 4851c7715ddSchristos } 4861c7715ddSchristos 4871c7715ddSchristos void 4881c7715ddSchristos srclimit_penalty_info(void) 4891c7715ddSchristos { 4901c7715ddSchristos srclimit_penalty_info_for_tree("ipv4", 4911c7715ddSchristos &penalties_by_expiry4, npenalties4); 4921c7715ddSchristos srclimit_penalty_info_for_tree("ipv6", 4931c7715ddSchristos &penalties_by_expiry6, npenalties6); 4941c7715ddSchristos } 495