158dc6e0eSdtucker /* 258dc6e0eSdtucker * Copyright (c) 2020 Darren Tucker <dtucker@openbsd.org> 37965d983Sdjm * Copyright (c) 2024 Damien Miller <djm@mindrot.org> 458dc6e0eSdtucker * 558dc6e0eSdtucker * Permission to use, copy, modify, and distribute this software for any 658dc6e0eSdtucker * purpose with or without fee is hereby granted, provided that the above 758dc6e0eSdtucker * copyright notice and this permission notice appear in all copies. 858dc6e0eSdtucker * 958dc6e0eSdtucker * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 1058dc6e0eSdtucker * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 1158dc6e0eSdtucker * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 1258dc6e0eSdtucker * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 1358dc6e0eSdtucker * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 1458dc6e0eSdtucker * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 1558dc6e0eSdtucker * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 1658dc6e0eSdtucker */ 1758dc6e0eSdtucker 1858dc6e0eSdtucker #include <sys/socket.h> 1958dc6e0eSdtucker #include <sys/types.h> 207965d983Sdjm #include <sys/tree.h> 2158dc6e0eSdtucker 2258dc6e0eSdtucker #include <limits.h> 2358dc6e0eSdtucker #include <netdb.h> 2458dc6e0eSdtucker #include <stdio.h> 2558dc6e0eSdtucker #include <string.h> 267965d983Sdjm #include <stdlib.h> 2758dc6e0eSdtucker 2858dc6e0eSdtucker #include "addr.h" 2958dc6e0eSdtucker #include "canohost.h" 3058dc6e0eSdtucker #include "log.h" 3158dc6e0eSdtucker #include "misc.h" 3258dc6e0eSdtucker #include "srclimit.h" 3358dc6e0eSdtucker #include "xmalloc.h" 347965d983Sdjm #include "servconf.h" 357965d983Sdjm #include "match.h" 3658dc6e0eSdtucker 3758dc6e0eSdtucker static int max_children, max_persource, ipv4_masklen, ipv6_masklen; 387965d983Sdjm static struct per_source_penalty penalty_cfg; 397965d983Sdjm static char *penalty_exempt; 4058dc6e0eSdtucker 4158dc6e0eSdtucker /* Per connection state, used to enforce unauthenticated connection limit. */ 4258dc6e0eSdtucker static struct child_info { 4358dc6e0eSdtucker int id; 4458dc6e0eSdtucker struct xaddr addr; 45b1d7ea00Sderaadt } *children; 4658dc6e0eSdtucker 477965d983Sdjm /* 487965d983Sdjm * Penalised addresses, active entries here prohibit connections until expired. 497965d983Sdjm * Entries become active when more than penalty_min seconds of penalty are 507965d983Sdjm * outstanding. 517965d983Sdjm */ 527965d983Sdjm struct penalty { 537965d983Sdjm struct xaddr addr; 547965d983Sdjm time_t expiry; 557965d983Sdjm int active; 567965d983Sdjm const char *reason; 577965d983Sdjm RB_ENTRY(penalty) by_addr; 587965d983Sdjm RB_ENTRY(penalty) by_expiry; 597965d983Sdjm }; 607965d983Sdjm static int penalty_addr_cmp(struct penalty *a, struct penalty *b); 617965d983Sdjm static int penalty_expiry_cmp(struct penalty *a, struct penalty *b); 62cd187d0bSdjm RB_HEAD(penalties_by_addr, penalty) penalties_by_addr4, penalties_by_addr6; 63cd187d0bSdjm RB_HEAD(penalties_by_expiry, penalty) penalties_by_expiry4, penalties_by_expiry6; 647965d983Sdjm RB_GENERATE_STATIC(penalties_by_addr, penalty, by_addr, penalty_addr_cmp) 657965d983Sdjm RB_GENERATE_STATIC(penalties_by_expiry, penalty, by_expiry, penalty_expiry_cmp) 66cd187d0bSdjm static size_t npenalties4, npenalties6; 677965d983Sdjm 687965d983Sdjm static int 697965d983Sdjm srclimit_mask_addr(const struct xaddr *addr, int bits, struct xaddr *masked) 707965d983Sdjm { 717965d983Sdjm struct xaddr xmask; 727965d983Sdjm 737965d983Sdjm /* Mask address off address to desired size. */ 747965d983Sdjm if (addr_netmask(addr->af, bits, &xmask) != 0 || 757965d983Sdjm addr_and(masked, addr, &xmask) != 0) { 767965d983Sdjm debug3_f("%s: invalid mask %d bits", __func__, bits); 777965d983Sdjm return -1; 787965d983Sdjm } 797965d983Sdjm return 0; 807965d983Sdjm } 817965d983Sdjm 827965d983Sdjm static int 837965d983Sdjm srclimit_peer_addr(int sock, struct xaddr *addr) 847965d983Sdjm { 857965d983Sdjm struct sockaddr_storage storage; 867965d983Sdjm socklen_t addrlen = sizeof(storage); 877965d983Sdjm struct sockaddr *sa = (struct sockaddr *)&storage; 887965d983Sdjm 897965d983Sdjm if (getpeername(sock, sa, &addrlen) != 0) 907965d983Sdjm return 1; /* not remote socket? */ 917965d983Sdjm if (addr_sa_to_xaddr(sa, addrlen, addr) != 0) 927965d983Sdjm return 1; /* unknown address family? */ 937965d983Sdjm return 0; 947965d983Sdjm } 957965d983Sdjm 9658dc6e0eSdtucker void 977965d983Sdjm srclimit_init(int max, int persource, int ipv4len, int ipv6len, 987965d983Sdjm struct per_source_penalty *penalty_conf, const char *penalty_exempt_conf) 9958dc6e0eSdtucker { 10058dc6e0eSdtucker int i; 10158dc6e0eSdtucker 10258dc6e0eSdtucker max_children = max; 10358dc6e0eSdtucker ipv4_masklen = ipv4len; 10458dc6e0eSdtucker ipv6_masklen = ipv6len; 10558dc6e0eSdtucker max_persource = persource; 1067965d983Sdjm penalty_cfg = *penalty_conf; 107cd187d0bSdjm if (penalty_cfg.max_sources4 < 0 || penalty_cfg.max_sources6 < 0) 108cd187d0bSdjm fatal_f("invalid max_sources"); /* shouldn't happen */ 1097965d983Sdjm penalty_exempt = penalty_exempt_conf == NULL ? 1107965d983Sdjm NULL : xstrdup(penalty_exempt_conf); 111cd187d0bSdjm RB_INIT(&penalties_by_addr4); 112cd187d0bSdjm RB_INIT(&penalties_by_expiry4); 113cd187d0bSdjm RB_INIT(&penalties_by_addr6); 114cd187d0bSdjm RB_INIT(&penalties_by_expiry6); 11558dc6e0eSdtucker if (max_persource == INT_MAX) /* no limit */ 11658dc6e0eSdtucker return; 11758dc6e0eSdtucker debug("%s: max connections %d, per source %d, masks %d,%d", __func__, 11858dc6e0eSdtucker max, persource, ipv4len, ipv6len); 11958dc6e0eSdtucker if (max <= 0) 12058dc6e0eSdtucker fatal("%s: invalid number of sockets: %d", __func__, max); 121b1d7ea00Sderaadt children = xcalloc(max_children, sizeof(*children)); 12258dc6e0eSdtucker for (i = 0; i < max_children; i++) 123b1d7ea00Sderaadt children[i].id = -1; 12458dc6e0eSdtucker } 12558dc6e0eSdtucker 12658dc6e0eSdtucker /* returns 1 if connection allowed, 0 if not allowed. */ 12758dc6e0eSdtucker int 12858dc6e0eSdtucker srclimit_check_allow(int sock, int id) 12958dc6e0eSdtucker { 1307965d983Sdjm struct xaddr xa, xb; 13158dc6e0eSdtucker int i, bits, first_unused, count = 0; 13258dc6e0eSdtucker char xas[NI_MAXHOST]; 13358dc6e0eSdtucker 13458dc6e0eSdtucker if (max_persource == INT_MAX) /* no limit */ 13558dc6e0eSdtucker return 1; 13658dc6e0eSdtucker 13758dc6e0eSdtucker debug("%s: sock %d id %d limit %d", __func__, sock, id, max_persource); 1387965d983Sdjm if (srclimit_peer_addr(sock, &xa) != 0) 13958dc6e0eSdtucker return 1; 1407965d983Sdjm bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen; 1417965d983Sdjm if (srclimit_mask_addr(&xa, bits, &xb) != 0) 1427965d983Sdjm return 1; 14358dc6e0eSdtucker 14458dc6e0eSdtucker first_unused = max_children; 14558dc6e0eSdtucker /* Count matching entries and find first unused one. */ 14658dc6e0eSdtucker for (i = 0; i < max_children; i++) { 147b1d7ea00Sderaadt if (children[i].id == -1) { 14858dc6e0eSdtucker if (i < first_unused) 14958dc6e0eSdtucker first_unused = i; 150b1d7ea00Sderaadt } else if (addr_cmp(&children[i].addr, &xb) == 0) { 15158dc6e0eSdtucker count++; 15258dc6e0eSdtucker } 15358dc6e0eSdtucker } 15458dc6e0eSdtucker if (addr_ntop(&xa, xas, sizeof(xas)) != 0) { 15558dc6e0eSdtucker debug3("%s: addr ntop failed", __func__); 15658dc6e0eSdtucker return 1; 15758dc6e0eSdtucker } 15858dc6e0eSdtucker debug3("%s: new unauthenticated connection from %s/%d, at %d of %d", 15958dc6e0eSdtucker __func__, xas, bits, count, max_persource); 16058dc6e0eSdtucker 16158dc6e0eSdtucker if (first_unused == max_children) { /* no free slot found */ 16258dc6e0eSdtucker debug3("%s: no free slot", __func__); 16358dc6e0eSdtucker return 0; 16458dc6e0eSdtucker } 16558dc6e0eSdtucker if (first_unused < 0 || first_unused >= max_children) 16658dc6e0eSdtucker fatal("%s: internal error: first_unused out of range", 16758dc6e0eSdtucker __func__); 16858dc6e0eSdtucker 16958dc6e0eSdtucker if (count >= max_persource) 17058dc6e0eSdtucker return 0; 17158dc6e0eSdtucker 17258dc6e0eSdtucker /* Connection allowed, store masked address. */ 173b1d7ea00Sderaadt children[first_unused].id = id; 174b1d7ea00Sderaadt memcpy(&children[first_unused].addr, &xb, sizeof(xb)); 17558dc6e0eSdtucker return 1; 17658dc6e0eSdtucker } 17758dc6e0eSdtucker 17858dc6e0eSdtucker void 17958dc6e0eSdtucker srclimit_done(int id) 18058dc6e0eSdtucker { 18158dc6e0eSdtucker int i; 18258dc6e0eSdtucker 18358dc6e0eSdtucker if (max_persource == INT_MAX) /* no limit */ 18458dc6e0eSdtucker return; 18558dc6e0eSdtucker 18658dc6e0eSdtucker debug("%s: id %d", __func__, id); 18758dc6e0eSdtucker /* Clear corresponding state entry. */ 18858dc6e0eSdtucker for (i = 0; i < max_children; i++) { 189b1d7ea00Sderaadt if (children[i].id == id) { 190b1d7ea00Sderaadt children[i].id = -1; 19158dc6e0eSdtucker return; 19258dc6e0eSdtucker } 19358dc6e0eSdtucker } 19458dc6e0eSdtucker } 1957965d983Sdjm 1967965d983Sdjm static int 1977965d983Sdjm penalty_addr_cmp(struct penalty *a, struct penalty *b) 1987965d983Sdjm { 1997965d983Sdjm return addr_cmp(&a->addr, &b->addr); 2007965d983Sdjm /* Addresses must be unique in by_addr, so no need to tiebreak */ 2017965d983Sdjm } 2027965d983Sdjm 2037965d983Sdjm static int 2047965d983Sdjm penalty_expiry_cmp(struct penalty *a, struct penalty *b) 2057965d983Sdjm { 2067965d983Sdjm if (a->expiry != b->expiry) 2077965d983Sdjm return a->expiry < b->expiry ? -1 : 1; 2087965d983Sdjm /* Tiebreak on addresses */ 2097965d983Sdjm return addr_cmp(&a->addr, &b->addr); 2107965d983Sdjm } 2117965d983Sdjm 2127965d983Sdjm static void 213cd187d0bSdjm expire_penalties_from_tree(time_t now, const char *t, 214cd187d0bSdjm struct penalties_by_expiry *by_expiry, 215cd187d0bSdjm struct penalties_by_addr *by_addr, size_t *npenaltiesp) 2167965d983Sdjm { 2177965d983Sdjm struct penalty *penalty, *tmp; 2187965d983Sdjm 2197965d983Sdjm /* XXX avoid full scan of tree, e.g. min-heap */ 220cd187d0bSdjm RB_FOREACH_SAFE(penalty, penalties_by_expiry, by_expiry, tmp) { 2217965d983Sdjm if (penalty->expiry >= now) 2227965d983Sdjm break; 223cd187d0bSdjm if (RB_REMOVE(penalties_by_expiry, by_expiry, 2247965d983Sdjm penalty) != penalty || 225cd187d0bSdjm RB_REMOVE(penalties_by_addr, by_addr, 2267965d983Sdjm penalty) != penalty) 227cd187d0bSdjm fatal_f("internal error: %s penalty table corrupt", t); 2287965d983Sdjm free(penalty); 229cd187d0bSdjm if ((*npenaltiesp)-- == 0) 230cd187d0bSdjm fatal_f("internal error: %s npenalties underflow", t); 2317965d983Sdjm } 2327965d983Sdjm } 2337965d983Sdjm 2347965d983Sdjm static void 235cd187d0bSdjm expire_penalties(time_t now) 236cd187d0bSdjm { 237cd187d0bSdjm expire_penalties_from_tree(now, "ipv4", 238cd187d0bSdjm &penalties_by_expiry4, &penalties_by_addr4, &npenalties4); 239cd187d0bSdjm expire_penalties_from_tree(now, "ipv6", 240cd187d0bSdjm &penalties_by_expiry6, &penalties_by_addr6, &npenalties6); 241cd187d0bSdjm } 242cd187d0bSdjm 243cd187d0bSdjm static void 2447965d983Sdjm addr_masklen_ntop(struct xaddr *addr, int masklen, char *s, size_t slen) 2457965d983Sdjm { 2467965d983Sdjm size_t o; 2477965d983Sdjm 2487965d983Sdjm if (addr_ntop(addr, s, slen) != 0) { 2497965d983Sdjm strlcpy(s, "UNKNOWN", slen); 2507965d983Sdjm return; 2517965d983Sdjm } 2527965d983Sdjm if ((o = strlen(s)) < slen) 2537965d983Sdjm snprintf(s + o, slen - o, "/%d", masklen); 2547965d983Sdjm } 2557965d983Sdjm 2567965d983Sdjm int 2577965d983Sdjm srclimit_penalty_check_allow(int sock, const char **reason) 2587965d983Sdjm { 2597965d983Sdjm struct xaddr addr; 2607965d983Sdjm struct penalty find, *penalty; 2617965d983Sdjm time_t now; 262cd187d0bSdjm int bits, max_sources, overflow_mode; 2637965d983Sdjm char addr_s[NI_MAXHOST]; 264cd187d0bSdjm struct penalties_by_addr *by_addr; 265cd187d0bSdjm size_t npenalties; 2667965d983Sdjm 2677965d983Sdjm if (!penalty_cfg.enabled) 2687965d983Sdjm return 1; 2697965d983Sdjm if (srclimit_peer_addr(sock, &addr) != 0) 2707965d983Sdjm return 1; 2717965d983Sdjm if (penalty_exempt != NULL) { 2727965d983Sdjm if (addr_ntop(&addr, addr_s, sizeof(addr_s)) != 0) 2737965d983Sdjm return 1; /* shouldn't happen */ 2747965d983Sdjm if (addr_match_list(addr_s, penalty_exempt) == 1) { 2757965d983Sdjm return 1; 2767965d983Sdjm } 2777965d983Sdjm } 278cd187d0bSdjm now = monotime(); 279cd187d0bSdjm expire_penalties(now); 280cd187d0bSdjm by_addr = addr.af == AF_INET ? 281cd187d0bSdjm &penalties_by_addr4 : &penalties_by_addr6; 282cd187d0bSdjm max_sources = addr.af == AF_INET ? 283cd187d0bSdjm penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 284cd187d0bSdjm overflow_mode = addr.af == AF_INET ? 285cd187d0bSdjm penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 286cd187d0bSdjm npenalties = addr.af == AF_INET ? npenalties4 : npenalties6; 287cd187d0bSdjm if (npenalties >= (size_t)max_sources && 288cd187d0bSdjm overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 2897965d983Sdjm *reason = "too many penalised addresses"; 2907965d983Sdjm return 0; 2917965d983Sdjm } 2927965d983Sdjm bits = addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 2937965d983Sdjm memset(&find, 0, sizeof(find)); 2947965d983Sdjm if (srclimit_mask_addr(&addr, bits, &find.addr) != 0) 2957965d983Sdjm return 1; 296cd187d0bSdjm if ((penalty = RB_FIND(penalties_by_addr, by_addr, &find)) == NULL) 2977965d983Sdjm return 1; /* no penalty */ 2987965d983Sdjm if (penalty->expiry < now) { 2997965d983Sdjm expire_penalties(now); 3007965d983Sdjm return 1; /* expired penalty */ 3017965d983Sdjm } 3027965d983Sdjm if (!penalty->active) 3037965d983Sdjm return 1; /* Penalty hasn't hit activation threshold yet */ 3047965d983Sdjm *reason = penalty->reason; 3057965d983Sdjm return 0; 3067965d983Sdjm } 3077965d983Sdjm 3087965d983Sdjm static void 309cd187d0bSdjm srclimit_early_expire_penalties_from_tree(const char *t, 310cd187d0bSdjm struct penalties_by_expiry *by_expiry, 311cd187d0bSdjm struct penalties_by_addr *by_addr, size_t *npenaltiesp, size_t max_sources) 3127965d983Sdjm { 3137965d983Sdjm struct penalty *p = NULL; 3147965d983Sdjm int bits; 3157965d983Sdjm char s[NI_MAXHOST + 4]; 3167965d983Sdjm 3177965d983Sdjm /* Delete the soonest-to-expire penalties. */ 318cd187d0bSdjm while (*npenaltiesp > max_sources) { 319cd187d0bSdjm if ((p = RB_MIN(penalties_by_expiry, by_expiry)) == NULL) 320cd187d0bSdjm fatal_f("internal error: %s table corrupt (find)", t); 3217965d983Sdjm bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 3227965d983Sdjm addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 323cd187d0bSdjm debug3_f("%s overflow, remove %s", t, s); 324cd187d0bSdjm if (RB_REMOVE(penalties_by_expiry, by_expiry, p) != p || 325cd187d0bSdjm RB_REMOVE(penalties_by_addr, by_addr, p) != p) 326cd187d0bSdjm fatal_f("internal error: %s table corrupt (remove)", t); 3277965d983Sdjm free(p); 328cd187d0bSdjm (*npenaltiesp)--; 3297965d983Sdjm } 3307965d983Sdjm } 3317965d983Sdjm 332cd187d0bSdjm static void 333cd187d0bSdjm srclimit_early_expire_penalties(void) 334cd187d0bSdjm { 335cd187d0bSdjm srclimit_early_expire_penalties_from_tree("ipv4", 336cd187d0bSdjm &penalties_by_expiry4, &penalties_by_addr4, &npenalties4, 337cd187d0bSdjm (size_t)penalty_cfg.max_sources4); 338cd187d0bSdjm srclimit_early_expire_penalties_from_tree("ipv6", 339cd187d0bSdjm &penalties_by_expiry6, &penalties_by_addr6, &npenalties6, 340cd187d0bSdjm (size_t)penalty_cfg.max_sources6); 341cd187d0bSdjm } 342cd187d0bSdjm 3437965d983Sdjm void 3447965d983Sdjm srclimit_penalise(struct xaddr *addr, int penalty_type) 3457965d983Sdjm { 3467965d983Sdjm struct xaddr masked; 347cd187d0bSdjm struct penalty *penalty = NULL, *existing = NULL; 3487965d983Sdjm time_t now; 349cd187d0bSdjm int bits, penalty_secs, max_sources = 0, overflow_mode; 3507965d983Sdjm char addrnetmask[NI_MAXHOST + 4]; 351cd187d0bSdjm const char *reason = NULL, *t; 352cd187d0bSdjm size_t *npenaltiesp = NULL; 353cd187d0bSdjm struct penalties_by_addr *by_addr = NULL; 354cd187d0bSdjm struct penalties_by_expiry *by_expiry = NULL; 3557965d983Sdjm 3567965d983Sdjm if (!penalty_cfg.enabled) 3577965d983Sdjm return; 3587965d983Sdjm if (penalty_exempt != NULL) { 3597965d983Sdjm if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0) 3607965d983Sdjm return; /* shouldn't happen */ 3617965d983Sdjm if (addr_match_list(addrnetmask, penalty_exempt) == 1) { 3627965d983Sdjm debug3_f("address %s is exempt", addrnetmask); 3637965d983Sdjm return; 3647965d983Sdjm } 3657965d983Sdjm } 3667965d983Sdjm 3677965d983Sdjm switch (penalty_type) { 3687965d983Sdjm case SRCLIMIT_PENALTY_NONE: 3697965d983Sdjm return; 3707965d983Sdjm case SRCLIMIT_PENALTY_CRASH: 3717965d983Sdjm penalty_secs = penalty_cfg.penalty_crash; 3727965d983Sdjm reason = "penalty: caused crash"; 3737965d983Sdjm break; 3747965d983Sdjm case SRCLIMIT_PENALTY_AUTHFAIL: 3757965d983Sdjm penalty_secs = penalty_cfg.penalty_authfail; 3767965d983Sdjm reason = "penalty: failed authentication"; 3777965d983Sdjm break; 3787965d983Sdjm case SRCLIMIT_PENALTY_NOAUTH: 3797965d983Sdjm penalty_secs = penalty_cfg.penalty_noauth; 3807965d983Sdjm reason = "penalty: connections without attempting authentication"; 3817965d983Sdjm break; 382*4ad4d979Sdjm case SRCLIMIT_PENALTY_REFUSECONNECTION: 383*4ad4d979Sdjm penalty_secs = penalty_cfg.penalty_refuseconnection; 384*4ad4d979Sdjm reason = "penalty: connection prohibited by RefuseConnection"; 385*4ad4d979Sdjm break; 3867965d983Sdjm case SRCLIMIT_PENALTY_GRACE_EXCEEDED: 3877965d983Sdjm penalty_secs = penalty_cfg.penalty_crash; 3887965d983Sdjm reason = "penalty: exceeded LoginGraceTime"; 3897965d983Sdjm break; 3907965d983Sdjm default: 3917965d983Sdjm fatal_f("internal error: unknown penalty %d", penalty_type); 3927965d983Sdjm } 3937965d983Sdjm bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen; 3947965d983Sdjm if (srclimit_mask_addr(addr, bits, &masked) != 0) 3957965d983Sdjm return; 3967965d983Sdjm addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask)); 3977965d983Sdjm 3987965d983Sdjm now = monotime(); 3997965d983Sdjm expire_penalties(now); 400cd187d0bSdjm by_expiry = addr->af == AF_INET ? 401cd187d0bSdjm &penalties_by_expiry4 : &penalties_by_expiry6; 402cd187d0bSdjm by_addr = addr->af == AF_INET ? 403cd187d0bSdjm &penalties_by_addr4 : &penalties_by_addr6; 404cd187d0bSdjm max_sources = addr->af == AF_INET ? 405cd187d0bSdjm penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 406cd187d0bSdjm overflow_mode = addr->af == AF_INET ? 407cd187d0bSdjm penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 408cd187d0bSdjm npenaltiesp = addr->af == AF_INET ? &npenalties4 : &npenalties6; 409cd187d0bSdjm t = addr->af == AF_INET ? "ipv4" : "ipv6"; 410bd1f7b4dSdjm if (*npenaltiesp >= (size_t)max_sources && 411cd187d0bSdjm overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 412cd187d0bSdjm verbose_f("%s penalty table full, cannot penalise %s for %s", t, 4137965d983Sdjm addrnetmask, reason); 4147965d983Sdjm return; 4157965d983Sdjm } 4167965d983Sdjm 4177965d983Sdjm penalty = xcalloc(1, sizeof(*penalty)); 4187965d983Sdjm penalty->addr = masked; 4197965d983Sdjm penalty->expiry = now + penalty_secs; 4207965d983Sdjm penalty->reason = reason; 421cd187d0bSdjm if ((existing = RB_INSERT(penalties_by_addr, by_addr, 4227965d983Sdjm penalty)) == NULL) { 4237965d983Sdjm /* penalty didn't previously exist */ 4247965d983Sdjm if (penalty_secs > penalty_cfg.penalty_min) 4257965d983Sdjm penalty->active = 1; 426cd187d0bSdjm if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL) 427cd187d0bSdjm fatal_f("internal error: %s penalty tables corrupt", t); 428cd187d0bSdjm verbose_f("%s: new %s %s penalty of %d seconds for %s", t, 4297965d983Sdjm addrnetmask, penalty->active ? "active" : "deferred", 4307965d983Sdjm penalty_secs, reason); 431cd187d0bSdjm if (++(*npenaltiesp) > (size_t)max_sources) 432cd187d0bSdjm srclimit_early_expire_penalties(); /* permissive */ 4337965d983Sdjm return; 4347965d983Sdjm } 435cd187d0bSdjm debug_f("%s penalty for %s %s already exists, %lld seconds remaining", 436cd187d0bSdjm existing->active ? "active" : "inactive", t, 4377965d983Sdjm addrnetmask, (long long)(existing->expiry - now)); 4387965d983Sdjm /* Expiry information is about to change, remove from tree */ 439cd187d0bSdjm if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing) 440cd187d0bSdjm fatal_f("internal error: %s penalty table corrupt (remove)", t); 4417965d983Sdjm /* An entry already existed. Accumulate penalty up to maximum */ 4427965d983Sdjm existing->expiry += penalty_secs; 4437965d983Sdjm if (existing->expiry - now > penalty_cfg.penalty_max) 4447965d983Sdjm existing->expiry = now + penalty_cfg.penalty_max; 4457965d983Sdjm if (existing->expiry - now > penalty_cfg.penalty_min && 4467965d983Sdjm !existing->active) { 447cd187d0bSdjm verbose_f("%s: activating %s penalty of %lld seconds for %s", 448cd187d0bSdjm addrnetmask, t, (long long)(existing->expiry - now), 449cd187d0bSdjm reason); 4507965d983Sdjm existing->active = 1; 4517965d983Sdjm } 4527965d983Sdjm existing->reason = penalty->reason; 4537965d983Sdjm free(penalty); 454cd187d0bSdjm penalty = NULL; 4557965d983Sdjm /* Re-insert into expiry tree */ 456cd187d0bSdjm if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL) 457cd187d0bSdjm fatal_f("internal error: %s penalty table corrupt (insert)", t); 4587965d983Sdjm } 4597965d983Sdjm 460cd187d0bSdjm static void 461cd187d0bSdjm srclimit_penalty_info_for_tree(const char *t, 462cd187d0bSdjm struct penalties_by_expiry *by_expiry, size_t npenalties) 4637965d983Sdjm { 4647965d983Sdjm struct penalty *p = NULL; 4657965d983Sdjm int bits; 4667965d983Sdjm char s[NI_MAXHOST + 4]; 4677965d983Sdjm time_t now; 4687965d983Sdjm 4697965d983Sdjm now = monotime(); 470cd187d0bSdjm logit("%zu active %s penalties", npenalties, t); 471cd187d0bSdjm RB_FOREACH(p, penalties_by_expiry, by_expiry) { 4727965d983Sdjm bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 4737965d983Sdjm addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 4747965d983Sdjm if (p->expiry < now) 4757965d983Sdjm logit("client %s %s (expired)", s, p->reason); 4767965d983Sdjm else { 4777965d983Sdjm logit("client %s %s (%llu secs left)", s, p->reason, 4787965d983Sdjm (long long)(p->expiry - now)); 4797965d983Sdjm } 4807965d983Sdjm } 4817965d983Sdjm } 482cd187d0bSdjm 483cd187d0bSdjm void 484cd187d0bSdjm srclimit_penalty_info(void) 485cd187d0bSdjm { 486cd187d0bSdjm srclimit_penalty_info_for_tree("ipv4", 487cd187d0bSdjm &penalties_by_expiry4, npenalties4); 488cd187d0bSdjm srclimit_penalty_info_for_tree("ipv6", 489cd187d0bSdjm &penalties_by_expiry6, npenalties6); 490cd187d0bSdjm } 491