xref: /openbsd-src/usr.bin/ssh/srclimit.c (revision 4ad4d979befa76a8400438890fa82e99f8cc01ae)
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