Home | History | Annotate | Line # | Download | only in dist
      1 /*	$NetBSD: srclimit.c,v 1.7 2025/10/11 15:45:07 christos Exp $	*/
      2 
      3 /*
      4  * Copyright (c) 2020 Darren Tucker <dtucker (at) openbsd.org>
      5  * Copyright (c) 2024 Damien Miller <djm (at) mindrot.org>
      6  *
      7  * Permission to use, copy, modify, and distribute this software for any
      8  * purpose with or without fee is hereby granted, provided that the above
      9  * copyright notice and this permission notice appear in all copies.
     10  *
     11  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     12  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
     13  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
     14  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
     15  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
     16  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
     17  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
     18  */
     19 #include "includes.h"
     20 __RCSID("$NetBSD: srclimit.c,v 1.7 2025/10/11 15:45:07 christos Exp $");
     21 
     22 #include <sys/socket.h>
     23 #include <sys/types.h>
     24 #include <sys/tree.h>
     25 
     26 #include <limits.h>
     27 #include <netdb.h>
     28 #include <stdio.h>
     29 #include <string.h>
     30 #include <stdlib.h>
     31 
     32 #include "addr.h"
     33 #include "canohost.h"
     34 #include "log.h"
     35 #include "misc.h"
     36 #include "srclimit.h"
     37 #include "xmalloc.h"
     38 #include "servconf.h"
     39 #include "match.h"
     40 
     41 static int max_children, max_persource, ipv4_masklen, ipv6_masklen;
     42 static struct per_source_penalty penalty_cfg;
     43 static char *penalty_exempt;
     44 
     45 /* Per connection state, used to enforce unauthenticated connection limit. */
     46 static struct child_info {
     47 	int id;
     48 	struct xaddr addr;
     49 } *children;
     50 
     51 /*
     52  * Penalised addresses, active entries here prohibit connections until expired.
     53  * Entries become active when more than penalty_min seconds of penalty are
     54  * outstanding.
     55  */
     56 struct penalty {
     57 	struct xaddr addr;
     58 	time_t expiry;
     59 	int active;
     60 	const char *reason;
     61 	RB_ENTRY(penalty) by_addr;
     62 	RB_ENTRY(penalty) by_expiry;
     63 };
     64 static int penalty_addr_cmp(struct penalty *a, struct penalty *b);
     65 static int penalty_expiry_cmp(struct penalty *a, struct penalty *b);
     66 RB_HEAD(penalties_by_addr, penalty) penalties_by_addr4, penalties_by_addr6;
     67 RB_HEAD(penalties_by_expiry, penalty) penalties_by_expiry4, penalties_by_expiry6;
     68 RB_GENERATE_STATIC(penalties_by_addr, penalty, by_addr, penalty_addr_cmp)
     69 RB_GENERATE_STATIC(penalties_by_expiry, penalty, by_expiry, penalty_expiry_cmp)
     70 static size_t npenalties4, npenalties6;
     71 
     72 static int
     73 srclimit_mask_addr(const struct xaddr *addr, int bits, struct xaddr *masked)
     74 {
     75 	struct xaddr xmask;
     76 
     77 	/* Mask address off address to desired size. */
     78 	if (addr_netmask(addr->af, bits, &xmask) != 0 ||
     79 	    addr_and(masked, addr, &xmask) != 0) {
     80 		debug3_f("%s: invalid mask %d bits", __func__, bits);
     81 		return -1;
     82 	}
     83 	return 0;
     84 }
     85 
     86 static int
     87 srclimit_peer_addr(int sock, struct xaddr *addr)
     88 {
     89 	struct sockaddr_storage storage;
     90 	socklen_t addrlen = sizeof(storage);
     91 	struct sockaddr *sa = (struct sockaddr *)&storage;
     92 
     93 	if (getpeername(sock, sa, &addrlen) != 0)
     94 		return 1;	/* not remote socket? */
     95 	if (addr_sa_to_xaddr(sa, addrlen, addr) != 0)
     96 		return 1;	/* unknown address family? */
     97 	return 0;
     98 }
     99 
    100 void
    101 srclimit_init(int max, int persource, int ipv4len, int ipv6len,
    102     struct per_source_penalty *penalty_conf, const char *penalty_exempt_conf)
    103 {
    104 	int i;
    105 
    106 	max_children = max;
    107 	ipv4_masklen = ipv4len;
    108 	ipv6_masklen = ipv6len;
    109 	max_persource = persource;
    110 	penalty_cfg = *penalty_conf;
    111 	if (penalty_cfg.max_sources4 < 0 || penalty_cfg.max_sources6 < 0)
    112 		fatal_f("invalid max_sources"); /* shouldn't happen */
    113 	penalty_exempt = penalty_exempt_conf == NULL ?
    114 	    NULL : xstrdup(penalty_exempt_conf);
    115 	RB_INIT(&penalties_by_addr4);
    116 	RB_INIT(&penalties_by_expiry4);
    117 	RB_INIT(&penalties_by_addr6);
    118 	RB_INIT(&penalties_by_expiry6);
    119 	if (max_persource == INT_MAX)	/* no limit */
    120 		return;
    121 	debug("%s: max connections %d, per source %d, masks %d,%d", __func__,
    122 	    max, persource, ipv4len, ipv6len);
    123 	if (max <= 0)
    124 		fatal_f("invalid number of sockets: %d", max);
    125 	children = xcalloc(max_children, sizeof(*children));
    126 	for (i = 0; i < max_children; i++)
    127 		children[i].id = -1;
    128 }
    129 
    130 /* returns 1 if connection allowed, 0 if not allowed. */
    131 int
    132 srclimit_check_allow(int sock, int id)
    133 {
    134 	struct xaddr xa, xb;
    135 	int i, bits, first_unused, count = 0;
    136 	char xas[NI_MAXHOST];
    137 
    138 	if (max_persource == INT_MAX)	/* no limit */
    139 		return 1;
    140 
    141 	debug_f("sock %d id %d limit %d", sock, id, max_persource);
    142 	if (srclimit_peer_addr(sock, &xa) != 0)
    143 		return 1;
    144 	bits = xa.af == AF_INET ? ipv4_masklen : ipv6_masklen;
    145 	if (srclimit_mask_addr(&xa, bits, &xb) != 0)
    146 		return 1;
    147 
    148 	first_unused = max_children;
    149 	/* Count matching entries and find first unused one. */
    150 	for (i = 0; i < max_children; i++) {
    151 		if (children[i].id == -1) {
    152 			if (i < first_unused)
    153 				first_unused = i;
    154 		} else if (addr_cmp(&children[i].addr, &xb) == 0) {
    155 			count++;
    156 		}
    157 	}
    158 	if (addr_ntop(&xa, xas, sizeof(xas)) != 0) {
    159 		debug3_f("addr ntop failed");
    160 		return 1;
    161 	}
    162 	debug3("%s: new unauthenticated connection from %s/%d, at %d of %d",
    163 	    __func__, xas, bits, count, max_persource);
    164 
    165 	if (first_unused == max_children) { /* no free slot found */
    166 		debug3_f("no free slot");
    167 		return 0;
    168 	}
    169 	if (first_unused < 0 || first_unused >= max_children)
    170 		fatal("%s: internal error: first_unused out of range",
    171 		    __func__);
    172 
    173 	if (count >= max_persource)
    174 		return 0;
    175 
    176 	/* Connection allowed, store masked address. */
    177 	children[first_unused].id = id;
    178 	memcpy(&children[first_unused].addr, &xb, sizeof(xb));
    179 	return 1;
    180 }
    181 
    182 void
    183 srclimit_done(int id)
    184 {
    185 	int i;
    186 
    187 	if (max_persource == INT_MAX)	/* no limit */
    188 		return;
    189 
    190 	debug_f("id %d", id);
    191 	/* Clear corresponding state entry. */
    192 	for (i = 0; i < max_children; i++) {
    193 		if (children[i].id == id) {
    194 			children[i].id = -1;
    195 			return;
    196 		}
    197 	}
    198 }
    199 
    200 static int
    201 penalty_addr_cmp(struct penalty *a, struct penalty *b)
    202 {
    203 	return addr_cmp(&a->addr, &b->addr);
    204 	/* Addresses must be unique in by_addr, so no need to tiebreak */
    205 }
    206 
    207 static int
    208 penalty_expiry_cmp(struct penalty *a, struct penalty *b)
    209 {
    210 	if (a->expiry != b->expiry)
    211 		return a->expiry < b->expiry ? -1 : 1;
    212 	/* Tiebreak on addresses */
    213 	return addr_cmp(&a->addr, &b->addr);
    214 }
    215 
    216 static void
    217 expire_penalties_from_tree(time_t now, const char *t,
    218     struct penalties_by_expiry *by_expiry,
    219     struct penalties_by_addr *by_addr, size_t *npenaltiesp)
    220 {
    221 	struct penalty *penalty, *tmp;
    222 
    223 	/* XXX avoid full scan of tree, e.g. min-heap */
    224 	RB_FOREACH_SAFE(penalty, penalties_by_expiry, by_expiry, tmp) {
    225 		if (penalty->expiry >= now)
    226 			break;
    227 		if (RB_REMOVE(penalties_by_expiry, by_expiry,
    228 		    penalty) != penalty ||
    229 		    RB_REMOVE(penalties_by_addr, by_addr,
    230 		    penalty) != penalty)
    231 			fatal_f("internal error: %s penalty table corrupt", t);
    232 		free(penalty);
    233 		if ((*npenaltiesp)-- == 0)
    234 			fatal_f("internal error: %s npenalties underflow", t);
    235 	}
    236 }
    237 
    238 static void
    239 expire_penalties(time_t now)
    240 {
    241 	expire_penalties_from_tree(now, "ipv4",
    242 	    &penalties_by_expiry4, &penalties_by_addr4, &npenalties4);
    243 	expire_penalties_from_tree(now, "ipv6",
    244 	    &penalties_by_expiry6, &penalties_by_addr6, &npenalties6);
    245 }
    246 
    247 static void
    248 addr_masklen_ntop(struct xaddr *addr, int masklen, char *s, size_t slen)
    249 {
    250 	size_t o;
    251 
    252 	if (addr_ntop(addr, s, slen) != 0) {
    253 		strlcpy(s, "UNKNOWN", slen);
    254 		return;
    255 	}
    256 	if ((o = strlen(s)) < slen)
    257 		snprintf(s + o, slen - o, "/%d", masklen);
    258 }
    259 
    260 int
    261 srclimit_penalty_check_allow(int sock, const char **reason)
    262 {
    263 	struct xaddr addr;
    264 	struct penalty find, *penalty;
    265 	time_t now;
    266 	int bits, max_sources, overflow_mode;
    267 	char addr_s[NI_MAXHOST];
    268 	struct penalties_by_addr *by_addr;
    269 	size_t npenalties;
    270 
    271 	if (!penalty_cfg.enabled)
    272 		return 1;
    273 	if (srclimit_peer_addr(sock, &addr) != 0)
    274 		return 1;
    275 	if (penalty_exempt != NULL) {
    276 		if (addr_ntop(&addr, addr_s, sizeof(addr_s)) != 0)
    277 			return 1; /* shouldn't happen */
    278 		if (addr_match_list(addr_s, penalty_exempt) == 1) {
    279 			return 1;
    280 		}
    281 	}
    282 	now = monotime();
    283 	expire_penalties(now);
    284 	by_addr = addr.af == AF_INET ?
    285 	    &penalties_by_addr4 : &penalties_by_addr6;
    286 	max_sources = addr.af == AF_INET ?
    287 	    penalty_cfg.max_sources4 : penalty_cfg.max_sources6;
    288 	overflow_mode = addr.af == AF_INET ?
    289 	    penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6;
    290 	npenalties = addr.af == AF_INET ?  npenalties4 : npenalties6;
    291 	if (npenalties >= (size_t)max_sources &&
    292 	    overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) {
    293 		*reason = "too many penalised addresses";
    294 		return 0;
    295 	}
    296 	bits = addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
    297 	memset(&find, 0, sizeof(find));
    298 	if (srclimit_mask_addr(&addr, bits, &find.addr) != 0)
    299 		return 1;
    300 	if ((penalty = RB_FIND(penalties_by_addr, by_addr, &find)) == NULL)
    301 		return 1; /* no penalty */
    302 	if (penalty->expiry < now) {
    303 		expire_penalties(now);
    304 		return 1; /* expired penalty */
    305 	}
    306 	if (!penalty->active)
    307 		return 1; /* Penalty hasn't hit activation threshold yet */
    308 	*reason = penalty->reason;
    309 	return 0;
    310 }
    311 
    312 static void
    313 srclimit_early_expire_penalties_from_tree(const char *t,
    314     struct penalties_by_expiry *by_expiry,
    315     struct penalties_by_addr *by_addr, size_t *npenaltiesp, size_t max_sources)
    316 {
    317 	struct penalty *p = NULL;
    318 	int bits;
    319 	char s[NI_MAXHOST + 4];
    320 
    321 	/* Delete the soonest-to-expire penalties. */
    322 	while (*npenaltiesp > max_sources) {
    323 		if ((p = RB_MIN(penalties_by_expiry, by_expiry)) == NULL)
    324 			fatal_f("internal error: %s table corrupt (find)", t);
    325 		bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
    326 		addr_masklen_ntop(&p->addr, bits, s, sizeof(s));
    327 		debug3_f("%s overflow, remove %s", t, s);
    328 		if (RB_REMOVE(penalties_by_expiry, by_expiry, p) != p ||
    329 		    RB_REMOVE(penalties_by_addr, by_addr, p) != p)
    330 			fatal_f("internal error: %s table corrupt (remove)", t);
    331 		free(p);
    332 		(*npenaltiesp)--;
    333 	}
    334 }
    335 
    336 static void
    337 srclimit_early_expire_penalties(void)
    338 {
    339 	srclimit_early_expire_penalties_from_tree("ipv4",
    340 	    &penalties_by_expiry4, &penalties_by_addr4, &npenalties4,
    341 	    (size_t)penalty_cfg.max_sources4);
    342 	srclimit_early_expire_penalties_from_tree("ipv6",
    343 	    &penalties_by_expiry6, &penalties_by_addr6, &npenalties6,
    344 	    (size_t)penalty_cfg.max_sources6);
    345 }
    346 
    347 void
    348 srclimit_penalise(struct xaddr *addr, int penalty_type)
    349 {
    350 	struct xaddr masked;
    351 	struct penalty *penalty = NULL, *existing = NULL;
    352 	time_t now;
    353 	int bits, penalty_secs, max_sources = 0, overflow_mode;
    354 	char addrnetmask[NI_MAXHOST + 4];
    355 	const char *reason = NULL, *t;
    356 	size_t *npenaltiesp = NULL;
    357 	struct penalties_by_addr *by_addr = NULL;
    358 	struct penalties_by_expiry *by_expiry = NULL;
    359 
    360 	if (!penalty_cfg.enabled)
    361 		return;
    362 	if (penalty_exempt != NULL) {
    363 		if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0)
    364 			return; /* shouldn't happen */
    365 		if (addr_match_list(addrnetmask, penalty_exempt) == 1) {
    366 			debug3_f("address %s is exempt", addrnetmask);
    367 			return;
    368 		}
    369 	}
    370 
    371 	switch (penalty_type) {
    372 	case SRCLIMIT_PENALTY_NONE:
    373 		return;
    374 	case SRCLIMIT_PENALTY_CRASH:
    375 		penalty_secs = penalty_cfg.penalty_crash;
    376 		reason = "penalty: caused crash";
    377 		break;
    378 	case SRCLIMIT_PENALTY_AUTHFAIL:
    379 		penalty_secs = penalty_cfg.penalty_authfail;
    380 		reason = "penalty: failed authentication";
    381 		break;
    382 	case SRCLIMIT_PENALTY_NOAUTH:
    383 		penalty_secs = penalty_cfg.penalty_noauth;
    384 		reason = "penalty: connections without attempting authentication";
    385 		break;
    386 	case SRCLIMIT_PENALTY_REFUSECONNECTION:
    387 		penalty_secs = penalty_cfg.penalty_refuseconnection;
    388 		reason = "penalty: connection prohibited by RefuseConnection";
    389 		break;
    390 	case SRCLIMIT_PENALTY_GRACE_EXCEEDED:
    391 		penalty_secs = penalty_cfg.penalty_grace;
    392 		reason = "penalty: exceeded LoginGraceTime";
    393 		break;
    394 	default:
    395 		fatal_f("internal error: unknown penalty %d", penalty_type);
    396 	}
    397 	bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen;
    398 	if (srclimit_mask_addr(addr, bits, &masked) != 0)
    399 		return;
    400 	addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask));
    401 
    402 	now = monotime();
    403 	expire_penalties(now);
    404 	by_expiry = addr->af == AF_INET ?
    405 	    &penalties_by_expiry4 : &penalties_by_expiry6;
    406 	by_addr = addr->af == AF_INET ?
    407 	    &penalties_by_addr4 : &penalties_by_addr6;
    408 	max_sources = addr->af == AF_INET ?
    409 	    penalty_cfg.max_sources4 : penalty_cfg.max_sources6;
    410 	overflow_mode = addr->af == AF_INET ?
    411 	    penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6;
    412 	npenaltiesp = addr->af == AF_INET ?  &npenalties4 : &npenalties6;
    413 	t = addr->af == AF_INET ? "ipv4" : "ipv6";
    414 	if (*npenaltiesp >= (size_t)max_sources &&
    415 	    overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) {
    416 		verbose_f("%s penalty table full, cannot penalise %s for %s", t,
    417 		    addrnetmask, reason);
    418 		return;
    419 	}
    420 
    421 	penalty = xcalloc(1, sizeof(*penalty));
    422 	penalty->addr = masked;
    423 	penalty->expiry = now + penalty_secs;
    424 	penalty->reason = reason;
    425 	if ((existing = RB_INSERT(penalties_by_addr, by_addr,
    426 	    penalty)) == NULL) {
    427 		/* penalty didn't previously exist */
    428 		if (penalty_secs > penalty_cfg.penalty_min)
    429 			penalty->active = 1;
    430 		if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL)
    431 			fatal_f("internal error: %s penalty tables corrupt", t);
    432 		do_log2_f(penalty->active ?
    433 		    SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE,
    434 		    "%s: new %s %s penalty of %d seconds for %s", t,
    435 		    addrnetmask, penalty->active ? "active" : "deferred",
    436 		    penalty_secs, reason);
    437 		if (++(*npenaltiesp) > (size_t)max_sources)
    438 			srclimit_early_expire_penalties(); /* permissive */
    439 		return;
    440 	}
    441 	debug_f("%s penalty for %s %s already exists, %lld seconds remaining",
    442 	    existing->active ? "active" : "inactive", t,
    443 	    addrnetmask, (long long)(existing->expiry - now));
    444 	/* Expiry information is about to change, remove from tree */
    445 	if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing)
    446 		fatal_f("internal error: %s penalty table corrupt (remove)", t);
    447 	/* An entry already existed. Accumulate penalty up to maximum */
    448 	existing->expiry += penalty_secs;
    449 	if (existing->expiry - now > penalty_cfg.penalty_max)
    450 		existing->expiry = now + penalty_cfg.penalty_max;
    451 	if (existing->expiry - now > penalty_cfg.penalty_min &&
    452 	    !existing->active) {
    453 		logit_f("%s: activating %s penalty of %lld seconds for %s",
    454 		    addrnetmask, t, (long long)(existing->expiry - now),
    455 		    reason);
    456 		existing->active = 1;
    457 	}
    458 	existing->reason = penalty->reason;
    459 	free(penalty);
    460 	penalty = NULL;
    461 	/* Re-insert into expiry tree */
    462 	if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL)
    463 		fatal_f("internal error: %s penalty table corrupt (insert)", t);
    464 }
    465 
    466 static void
    467 srclimit_penalty_info_for_tree(const char *t,
    468     struct penalties_by_expiry *by_expiry, size_t npenalties)
    469 {
    470 	struct penalty *p = NULL;
    471 	int bits;
    472 	char s[NI_MAXHOST + 4];
    473 	time_t now;
    474 
    475 	now = monotime();
    476 	logit("%zu active %s penalties", npenalties, t);
    477 	RB_FOREACH(p, penalties_by_expiry, by_expiry) {
    478 		bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
    479 		addr_masklen_ntop(&p->addr, bits, s, sizeof(s));
    480 		if (p->expiry < now)
    481 			logit("client %s %s (expired)", s, p->reason);
    482 		else {
    483 			logit("client %s %s (%llu secs left)", s, p->reason,
    484 			   (long long)(p->expiry - now));
    485 		}
    486 	}
    487 }
    488 
    489 void
    490 srclimit_penalty_info(void)
    491 {
    492 	srclimit_penalty_info_for_tree("ipv4",
    493 	    &penalties_by_expiry4, npenalties4);
    494 	srclimit_penalty_info_for_tree("ipv6",
    495 	    &penalties_by_expiry6, npenalties6);
    496 }
    497