bridge(4): document net.link.bridge.pfil_onlyip
[dragonfly.git] / usr.sbin / sshlockout / sshlockout.c
blob96d10f17f9bf1b28b86a5f0d841d2b0c8686c1b5
1 /*
2 * Copyright (c) 2015 The DragonFly Project. All rights reserved.
4 * This code is derived from software contributed to The DragonFly Project
5 * by Matthew Dillon <dillon@dragonflybsd.org>
6 * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org>
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
12 * 1. Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in
16 * the documentation and/or other materials provided with the
17 * distribution.
18 * 3. Neither the name of The DragonFly Project nor the names of its
19 * contributors may be used to endorse or promote products derived
20 * from this software without specific, prior written permission.
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26 * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27 * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33 * SUCH DAMAGE.
36 * Use: pipe syslog auth output to this program.
38 * Detects failed ssh login attempts and maps out the originating IP and
39 * issues, in case of a PF firewall, adds to a PF table <lockout> using
40 * 'pfctl -tlockout -Tadd' commands.
42 * /etc/syslog.conf line example:
43 * auth.info;authpriv.info |exec /usr/sbin/sshlockout -pf lockout
45 * Also suggest a cron entry to clean out the PF table at least once a day.
46 * 3 3 * * * pfctl -tlockout -Tflush
48 * Alternatively there is an ipfw(8) mode (-ipfw <rulenum>).
51 #include <sys/types.h>
52 #include <sys/socket.h>
53 #include <sys/sysctl.h>
54 #include <sys/time.h>
56 #include <arpa/inet.h>
57 #include <netinet/in.h>
58 #include <net/if.h>
59 #include <net/ipfw/ip_fw.h>
61 #include <stdio.h>
62 #include <stdlib.h>
63 #include <unistd.h>
64 #include <string.h>
65 #include <stdarg.h>
66 #include <syslog.h>
67 #include <ctype.h>
68 #include <stdbool.h>
70 typedef struct iphist {
71 struct iphist *next;
72 struct iphist *hnext;
73 char *ips;
74 time_t t;
75 int hv;
76 } iphist_t;
78 struct args {
79 int fw_type;
80 char *arg1;
81 int arg2;
84 #define FW_IS_PF 1
85 #define FW_IS_IPFW 2
86 #define FW_IS_IPFWTBL 3
88 #define HSIZE 1024
89 #define HMASK (HSIZE - 1)
90 #define MAXHIST 100
91 #define SSHLIMIT 5 /* per hour */
92 #define MAX_TABLE_NAME 20 /* PF table name limit */
94 static iphist_t *hist_base;
95 static iphist_t **hist_tail = &hist_base;
96 static iphist_t *hist_hash[HSIZE];
97 static int hist_count = 0;
99 static int ipfw_sock = -1;
101 static struct args args;
103 static void init_iphist(void);
104 static void checkline(char *buf);
105 static int insert_iph(const char *ips, time_t t);
106 static void delete_iph(iphist_t *ip);
108 static void
109 block_ip(const char *ips)
111 struct ipfw_ioc_tblcont ent;
112 struct ipfw_ioc_tblent *te;
113 char buf[128];
114 int r = 0;
116 switch (args.fw_type) {
117 case FW_IS_PF:
118 r = snprintf(buf, sizeof(buf),
119 "pfctl -t%s -Tadd %s", args.arg1, ips);
120 break;
122 case FW_IS_IPFW:
123 r = snprintf(buf, sizeof(buf),
124 "ipfw add %s deny tcp from %s to me 22",
125 args.arg1, ips);
126 break;
128 case FW_IS_IPFWTBL:
129 memset(&ent, 0, sizeof(ent));
130 ent.tableid = args.arg2;
131 ent.entcnt = 1;
132 te = &ent.ent[0];
134 r = inet_pton(AF_INET, ips, &te->key.sin_addr);
135 if (r <= 0)
136 break;
137 te->key.sin_family = AF_INET;
138 te->key.sin_len = sizeof(struct sockaddr_in);
140 if (setsockopt(ipfw_sock, IPPROTO_IP, IP_FW_TBL_ADD,
141 &ent, sizeof(ent)) < 0) {
142 r = -1;
143 break;
145 /* Done */
146 return;
149 if (r > 0 && (int)strlen(buf) == r) {
150 system(buf);
151 } else {
152 syslog(LOG_ERR, "sshlockout: invalid command");
157 * Stupid simple string hash
159 static __inline int
160 iphash(const char *str)
162 int hv = 0xA1B3569D;
164 while (*str) {
165 hv = (hv << 5) ^ *str ^ (hv >> 23);
166 ++str;
168 return hv;
172 static bool
173 parse_args(int ac, char **av)
175 if (ac >= 2) {
176 if (strcmp(av[1], "-pf") == 0 && ac == 3) {
177 /* -pf <tablename> */
178 char *tablename = av[2];
180 if (strlen(tablename) > 0 &&
181 strlen(tablename) < MAX_TABLE_NAME) {
182 args.fw_type = FW_IS_PF;
183 args.arg1 = tablename;
184 return true;
187 if (strcmp(av[1], "-ipfw") == 0 && ac == 3) {
188 /* -ipfw <rule> */
189 char *rule = av[2];
191 if (strlen(rule) > 0 && strlen(rule) <= 5) {
192 for (char *s = rule; *s; ++s) {
193 if (!isdigit(*s))
194 return false;
196 if (atoi(rule) < 1)
197 return false;
198 if (atoi(rule) > 65535)
199 return false;
200 args.fw_type = FW_IS_IPFW;
201 args.arg1 = rule;
202 return true;
206 if (strcmp(av[1], "-ipfwtbl") == 0 && ac == 3) {
207 /* -ipfwtbl <tableid> */
208 int tableid;
209 char *eptr;
211 tableid = strtoul(av[2], &eptr, 0);
212 if (*eptr != '\0')
213 return false;
215 ipfw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
216 if (ipfw_sock < 0)
217 return false;
219 args.fw_type = FW_IS_IPFWTBL;
220 args.arg2 = tableid;
221 return true;
225 return false;
229 main(int ac, char **av)
231 char buf[1024];
233 args.fw_type = 0;
234 args.arg1 = NULL;
235 args.arg2 = 0;
237 if (!parse_args(ac, av)) {
238 syslog(LOG_ERR, "sshlockout: invalid argument");
239 return(1);
242 init_iphist();
244 openlog("sshlockout", LOG_PID|LOG_CONS, LOG_AUTH);
245 syslog(LOG_ERR, "sshlockout starting up");
246 freopen("/dev/null", "w", stdout);
247 freopen("/dev/null", "w", stderr);
249 while (fgets(buf, sizeof(buf), stdin) != NULL) {
250 if (strstr(buf, "sshd") == NULL)
251 continue;
252 checkline(buf);
254 syslog(LOG_ERR, "sshlockout exiting");
255 return(0);
258 static void
259 checkip(const char *str, const char *reason1, const char *reason2)
261 char ips[128];
262 int n1;
263 int n2;
264 int n3;
265 int n4;
266 time_t t = time(NULL);
268 ips[0] = '\0';
270 if (sscanf(str, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) {
271 snprintf(ips, sizeof(ips), "%d.%d.%d.%d", n1, n2, n3, n4);
272 } else {
274 * Check for IPv6 address (primitive way)
276 int cnt = 0;
277 while (str[cnt] == ':' || isxdigit(str[cnt])) {
278 ++cnt;
280 if (cnt > 0 && cnt < (int)sizeof(ips)) {
281 memcpy(ips, str, cnt);
282 ips[cnt] = '\0';
287 * We do not block localhost as is makes no sense.
289 if (strcmp(ips, "127.0.0.1") == 0)
290 return;
291 if (strcmp(ips, "::1") == 0)
292 return;
294 if (strlen(ips) > 0) {
297 * Check for DoS attack. When connections from too many
298 * IP addresses come in at the same time, our hash table
299 * would overflow, so we delete the oldest entries AND
300 * block it's IP when they are younger than 10 seconds.
301 * This prevents massive attacks from arbitrary IPs.
303 if (hist_count > MAXHIST + 16) {
304 while (hist_count > MAXHIST) {
305 iphist_t *iph = hist_base;
306 int dt = (int)(t - iph->t);
307 if (dt < 10) {
308 syslog(LOG_ERR,
309 "Detected overflow attack, "
310 "locking out %s\n",
311 iph->ips);
312 block_ip(iph->ips);
314 delete_iph(iph);
318 if (insert_iph(ips, t)) {
319 syslog(LOG_ERR,
320 "Detected ssh %s attempt "
321 "for %s, locking out %s\n",
322 reason1, reason2, ips);
323 block_ip(ips);
328 static void
329 checkline(char *buf)
331 char *str;
334 * ssh login attempt with password (only hit if ssh allows
335 * password entry). Root or admin.
337 if ((str = strstr(buf, "Failed password for root from")) != NULL ||
338 (str = strstr(buf, "Failed password for admin from")) != NULL) {
339 while (*str && (*str < '0' || *str > '9'))
340 ++str;
341 checkip(str, "password login", "root or admin");
342 return;
346 * ssh login attempt with password (only hit if ssh allows password
347 * entry). Non-existant user.
349 if ((str = strstr(buf, "Failed password for invalid user")) != NULL) {
350 str += 32;
351 while (*str == ' ')
352 ++str;
353 while (*str && *str != ' ')
354 ++str;
355 if (strncmp(str, " from", 5) == 0) {
356 checkip(str + 5, "password login", "an invalid user");
358 return;
362 * ssh login attempt for non-existant user.
364 if ((str = strstr(buf, "Invalid user")) != NULL) {
365 str += 12;
366 while (*str == ' ')
367 ++str;
368 while (*str && *str != ' ')
369 ++str;
370 if (strncmp(str, " from", 5) == 0) {
371 checkip(str + 5, "login", "an invalid user");
373 return;
377 * Premature disconnect in pre-authorization phase, typically an
378 * attack but require 5 attempts in an hour before cleaning it out.
380 if ((str = strstr(buf, "Received disconnect from ")) != NULL &&
381 strstr(buf, "[preauth]") != NULL) {
382 checkip(str + 25, "preauth", "an invalid user");
383 return;
387 * Maximum authentication attempts exceeded
389 if ((str = strstr(buf, "maximum authentication "
390 "attempts exceeded for ")) != NULL &&
391 strstr(buf, "[preauth]") != NULL) {
392 str += 45;
393 while (*str == ' ')
394 ++str;
395 while (*str && *str != ' ')
396 ++str;
397 if (strncmp(str, " from", 5) == 0) {
398 checkip(str + 5, "login", "many attempts");
400 return;
405 * Insert IP record
407 static int
408 insert_iph(const char *ips, time_t t)
410 iphist_t *ip = malloc(sizeof(*ip));
411 iphist_t *scan;
412 int found;
414 ip->hv = iphash(ips);
415 ip->ips = strdup(ips);
416 ip->t = t;
418 ip->hnext = hist_hash[ip->hv & HMASK];
419 hist_hash[ip->hv & HMASK] = ip;
420 ip->next = NULL;
421 *hist_tail = ip;
422 hist_tail = &ip->next;
423 ++hist_count;
426 * hysteresis
428 if (hist_count > MAXHIST + 16) {
429 while (hist_count > MAXHIST)
430 delete_iph(hist_base);
434 * Check limit
436 found = 0;
437 for (scan = hist_hash[ip->hv & HMASK]; scan; scan = scan->hnext) {
438 if (scan->hv == ip->hv && strcmp(scan->ips, ip->ips) == 0) {
439 int dt = (int)(t - ip->t);
440 if (dt < 60 * 60) {
441 ++found;
442 if (found > SSHLIMIT)
443 break;
447 return (found > SSHLIMIT);
451 * Delete an ip record. Note that we always delete from the head of the
452 * list, but we will still wind up scanning hash chains.
454 static void
455 delete_iph(iphist_t *ip)
457 iphist_t **scanp;
458 iphist_t *scan;
460 scanp = &hist_base;
461 while ((scan = *scanp) != ip) {
462 scanp = &scan->next;
464 *scanp = ip->next;
465 if (hist_tail == &ip->next)
466 hist_tail = scanp;
468 scanp = &hist_hash[ip->hv & HMASK];
469 while ((scan = *scanp) != ip) {
470 scanp = &scan->hnext;
472 *scanp = ip->hnext;
474 --hist_count;
475 free(ip);
478 static void
479 init_iphist(void)
481 hist_base = NULL;
482 hist_tail = &hist_base;
483 for (int i = 0; i < HSIZE; i++) {
484 hist_hash[i] = NULL;
486 hist_count = 0;