1 /*        $NetBSD: srclimit.c,v 1.6 2025/04/09 15:49:32 christos Exp $          */
2 
3 /*
4  * Copyright (c) 2020 Darren Tucker <dtucker@openbsd.org>
5  * Copyright (c) 2024 Damien Miller <djm@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.6 2025/04/09 15:49:32 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
srclimit_mask_addr(const struct xaddr * addr,int bits,struct xaddr * masked)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
srclimit_peer_addr(int sock,struct xaddr * addr)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
srclimit_init(int max,int persource,int ipv4len,int ipv6len,struct per_source_penalty * penalty_conf,const char * penalty_exempt_conf)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("%s: invalid number of sockets: %d", __func__, 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
srclimit_check_allow(int sock,int id)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("%s: sock %d id %d limit %d", __func__, 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("%s: addr ntop failed", __func__);
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("%s: no free slot", __func__);
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
srclimit_done(int id)183 srclimit_done(int id)
184 {
185           int i;
186 
187           if (max_persource == INT_MAX) /* no limit */
188                     return;
189 
190           debug("%s: id %d", __func__, 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
penalty_addr_cmp(struct penalty * a,struct penalty * b)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
penalty_expiry_cmp(struct penalty * a,struct penalty * b)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
expire_penalties_from_tree(time_t now,const char * t,struct penalties_by_expiry * by_expiry,struct penalties_by_addr * by_addr,size_t * npenaltiesp)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
expire_penalties(time_t now)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
addr_masklen_ntop(struct xaddr * addr,int masklen,char * s,size_t slen)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
srclimit_penalty_check_allow(int sock,const char ** reason)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
srclimit_early_expire_penalties_from_tree(const char * t,struct penalties_by_expiry * by_expiry,struct penalties_by_addr * by_addr,size_t * npenaltiesp,size_t max_sources)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
srclimit_early_expire_penalties(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
srclimit_penalise(struct xaddr * addr,int penalty_type)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                     verbose_f("%s: new %s %s penalty of %d seconds for %s", t,
433                         addrnetmask, penalty->active ? "active" : "deferred",
434                         penalty_secs, reason);
435                     if (++(*npenaltiesp) > (size_t)max_sources)
436                               srclimit_early_expire_penalties(); /* permissive */
437                     return;
438           }
439           debug_f("%s penalty for %s %s already exists, %lld seconds remaining",
440               existing->active ? "active" : "inactive", t,
441               addrnetmask, (long long)(existing->expiry - now));
442           /* Expiry information is about to change, remove from tree */
443           if (RB_REMOVE(penalties_by_expiry, by_expiry, existing) != existing)
444                     fatal_f("internal error: %s penalty table corrupt (remove)", t);
445           /* An entry already existed. Accumulate penalty up to maximum */
446           existing->expiry += penalty_secs;
447           if (existing->expiry - now > penalty_cfg.penalty_max)
448                     existing->expiry = now + penalty_cfg.penalty_max;
449           if (existing->expiry - now > penalty_cfg.penalty_min &&
450               !existing->active) {
451                     verbose_f("%s: activating %s penalty of %lld seconds for %s",
452                         addrnetmask, t, (long long)(existing->expiry - now),
453                         reason);
454                     existing->active = 1;
455           }
456           existing->reason = penalty->reason;
457           free(penalty);
458           penalty = NULL;
459           /* Re-insert into expiry tree */
460           if (RB_INSERT(penalties_by_expiry, by_expiry, existing) != NULL)
461                     fatal_f("internal error: %s penalty table corrupt (insert)", t);
462 }
463 
464 static void
srclimit_penalty_info_for_tree(const char * t,struct penalties_by_expiry * by_expiry,size_t npenalties)465 srclimit_penalty_info_for_tree(const char *t,
466     struct penalties_by_expiry *by_expiry, size_t npenalties)
467 {
468           struct penalty *p = NULL;
469           int bits;
470           char s[NI_MAXHOST + 4];
471           time_t now;
472 
473           now = monotime();
474           logit("%zu active %s penalties", npenalties, t);
475           RB_FOREACH(p, penalties_by_expiry, by_expiry) {
476                     bits = p->addr.af == AF_INET ? ipv4_masklen : ipv6_masklen;
477                     addr_masklen_ntop(&p->addr, bits, s, sizeof(s));
478                     if (p->expiry < now)
479                               logit("client %s %s (expired)", s, p->reason);
480                     else {
481                               logit("client %s %s (%llu secs left)", s, p->reason,
482                                  (long long)(p->expiry - now));
483                     }
484           }
485 }
486 
487 void
srclimit_penalty_info(void)488 srclimit_penalty_info(void)
489 {
490           srclimit_penalty_info_for_tree("ipv4",
491               &penalties_by_expiry4, npenalties4);
492           srclimit_penalty_info_for_tree("ipv6",
493               &penalties_by_expiry6, npenalties6);
494 }
495