1 /* $NetBSD: srclimit.c,v 1.8 2026/04/08 18:58:41 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.8 2026/04/08 18:58:41 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 double 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(double 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(double 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 double 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_double(); 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 double now; 353 int bits, max_sources = 0, overflow_mode; 354 double penalty_secs; 355 char addrnetmask[NI_MAXHOST + 4]; 356 const char *reason = NULL, *t; 357 size_t *npenaltiesp = NULL; 358 struct penalties_by_addr *by_addr = NULL; 359 struct penalties_by_expiry *by_expiry = NULL; 360 361 if (!penalty_cfg.enabled) 362 return; 363 if (penalty_exempt != NULL) { 364 if (addr_ntop(addr, addrnetmask, sizeof(addrnetmask)) != 0) 365 return; /* shouldn't happen */ 366 if (addr_match_list(addrnetmask, penalty_exempt) == 1) { 367 debug3_f("address %s is exempt", addrnetmask); 368 return; 369 } 370 } 371 372 switch (penalty_type) { 373 case SRCLIMIT_PENALTY_NONE: 374 return; 375 case SRCLIMIT_PENALTY_CRASH: 376 penalty_secs = penalty_cfg.penalty_crash; 377 reason = "penalty: caused crash"; 378 break; 379 case SRCLIMIT_PENALTY_AUTHFAIL: 380 penalty_secs = penalty_cfg.penalty_authfail; 381 reason = "penalty: failed authentication"; 382 break; 383 case SRCLIMIT_PENALTY_NOAUTH: 384 penalty_secs = penalty_cfg.penalty_noauth; 385 reason = "penalty: connections without attempting authentication"; 386 break; 387 case SRCLIMIT_PENALTY_INVALIDUSER: 388 penalty_secs = penalty_cfg.penalty_invaliduser; 389 reason = "penalty: attempted authentication by invalid user"; 390 break; 391 case SRCLIMIT_PENALTY_REFUSECONNECTION: 392 penalty_secs = penalty_cfg.penalty_refuseconnection; 393 reason = "penalty: connection prohibited by RefuseConnection"; 394 break; 395 case SRCLIMIT_PENALTY_GRACE_EXCEEDED: 396 penalty_secs = penalty_cfg.penalty_grace; 397 reason = "penalty: exceeded LoginGraceTime"; 398 break; 399 default: 400 fatal_f("internal error: unknown penalty %d", penalty_type); 401 } 402 403 if (penalty_secs <= 0) 404 return; 405 406 bits = addr->af == AF_INET ? ipv4_masklen : ipv6_masklen; 407 if (srclimit_mask_addr(addr, bits, &masked) != 0) 408 return; 409 addr_masklen_ntop(addr, bits, addrnetmask, sizeof(addrnetmask)); 410 411 now = monotime_double(); 412 expire_penalties(now); 413 by_expiry = addr->af == AF_INET ? 414 &penalties_by_expiry4 : &penalties_by_expiry6; 415 by_addr = addr->af == AF_INET ? 416 &penalties_by_addr4 : &penalties_by_addr6; 417 max_sources = addr->af == AF_INET ? 418 penalty_cfg.max_sources4 : penalty_cfg.max_sources6; 419 overflow_mode = addr->af == AF_INET ? 420 penalty_cfg.overflow_mode : penalty_cfg.overflow_mode6; 421 npenaltiesp = addr->af == AF_INET ? &npenalties4 : &npenalties6; 422 t = addr->af == AF_INET ? "ipv4" : "ipv6"; 423 if (*npenaltiesp >= (size_t)max_sources && 424 overflow_mode == PER_SOURCE_PENALTY_OVERFLOW_DENY_ALL) { 425 verbose_f("%s penalty table full, cannot penalise %s for %s", t, 426 addrnetmask, reason); 427 return; 428 } 429 430 penalty = xcalloc(1, sizeof(*penalty)); 431 penalty->addr = masked; 432 penalty->expiry = now + penalty_secs; 433 penalty->reason = reason; 434 if ((existing = RB_INSERT(penalties_by_addr, by_addr, 435 penalty)) == NULL) { 436 /* penalty didn't previously exist */ 437 if (penalty_secs > penalty_cfg.penalty_min) 438 penalty->active = 1; 439 if (RB_INSERT(penalties_by_expiry, by_expiry, penalty) != NULL) 440 fatal_f("internal error: %s penalty tables corrupt", t); 441 do_log2_f(penalty->active ? 442 SYSLOG_LEVEL_INFO : SYSLOG_LEVEL_VERBOSE, 443 "%s: new %s %s penalty of %.3f seconds for %s", t, 444 addrnetmask, penalty->active ? "active" : "deferred", 445 penalty_secs, reason); 446 if (++(*npenaltiesp) > (size_t)max_sources) 447 srclimit_early_expire_penalties(); /* permissive */ 448 return; 449 } 450 debug_f("%s penalty for %s %s already exists, %lld seconds remaining", 451 existing->active ? "active" : "inactive", t, 452 addrnetmask, (long long)(existing->expiry - now)); 453 /* Expiry information is about to change, remove from tree */ 454 if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing) 455 fatal_f("internal error: %s penalty table corrupt (remove)", t); 456 /* An entry already existed. Accumulate penalty up to maximum */ 457 existing->expiry += penalty_secs; 458 if (existing->expiry - now > penalty_cfg.penalty_max) 459 existing->expiry = now + penalty_cfg.penalty_max; 460 if (existing->expiry - now > penalty_cfg.penalty_min && 461 !existing->active) { 462 logit_f("%s: activating %s penalty of %.3f seconds for %s", 463 addrnetmask, t, existing->expiry - now, reason); 464 existing->active = 1; 465 } 466 existing->reason = penalty->reason; 467 free(penalty); 468 penalty = NULL; 469 /* Re-insert into expiry tree */ 470 if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL) 471 fatal_f("internal error: %s penalty table corrupt (insert)", t); 472 } 473 474 static void 475 srclimit_penalty_info_for_tree(const char *t, 476 struct penalties_by_expiry *by_expiry, size_t npenalties) 477 { 478 struct penalty *p = NULL; 479 int bits; 480 char s[NI_MAXHOST + 4]; 481 double now; 482 483 now = monotime_double(); 484 logit("%zu active %s penalties", npenalties, t); 485 RB_FOREACH(p, penalties_by_expiry, by_expiry) { 486 bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen; 487 addr_masklen_ntop(&p->addr, bits, s, sizeof(s)); 488 if (p->expiry < now) 489 logit("client %s %s (expired)", s, p->reason); 490 else { 491 logit("client %s %s (%.3f secs left)", s, p->reason, 492 p->expiry - now); 493 } 494 } 495 } 496 497 void 498 srclimit_penalty_info(void) 499 { 500 srclimit_penalty_info_for_tree("ipv4", 501 &penalties_by_expiry4, npenalties4); 502 srclimit_penalty_info_for_tree("ipv6", 503 &penalties_by_expiry6, npenalties6); 504 } 505