xref: /netbsd-src/crypto/external/bsd/openssh/dist/srclimit.c (revision 9469f4f13c84743995b7d51c506f9c9849ba30de)
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