1a4ac8286SMatthew Dillon /*
2a4ac8286SMatthew Dillon * Copyright (c) 2015 The DragonFly Project. All rights reserved.
3a4ac8286SMatthew Dillon *
4a4ac8286SMatthew Dillon * This code is derived from software contributed to The DragonFly Project
5a4ac8286SMatthew Dillon * by Matthew Dillon <dillon@dragonflybsd.org>
6a4ac8286SMatthew Dillon * by Venkatesh Srinivas <vsrinivas@dragonflybsd.org>
7a4ac8286SMatthew Dillon *
8a4ac8286SMatthew Dillon * Redistribution and use in source and binary forms, with or without
9a4ac8286SMatthew Dillon * modification, are permitted provided that the following conditions
10a4ac8286SMatthew Dillon * are met:
11a4ac8286SMatthew Dillon *
12a4ac8286SMatthew Dillon * 1. Redistributions of source code must retain the above copyright
13a4ac8286SMatthew Dillon * notice, this list of conditions and the following disclaimer.
14a4ac8286SMatthew Dillon * 2. Redistributions in binary form must reproduce the above copyright
15a4ac8286SMatthew Dillon * notice, this list of conditions and the following disclaimer in
16a4ac8286SMatthew Dillon * the documentation and/or other materials provided with the
17a4ac8286SMatthew Dillon * distribution.
18a4ac8286SMatthew Dillon * 3. Neither the name of The DragonFly Project nor the names of its
19a4ac8286SMatthew Dillon * contributors may be used to endorse or promote products derived
20a4ac8286SMatthew Dillon * from this software without specific, prior written permission.
21a4ac8286SMatthew Dillon *
22a4ac8286SMatthew Dillon * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23a4ac8286SMatthew Dillon * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24a4ac8286SMatthew Dillon * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
25a4ac8286SMatthew Dillon * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
26a4ac8286SMatthew Dillon * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
27a4ac8286SMatthew Dillon * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
28a4ac8286SMatthew Dillon * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
29a4ac8286SMatthew Dillon * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
30a4ac8286SMatthew Dillon * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
31a4ac8286SMatthew Dillon * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
32a4ac8286SMatthew Dillon * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
33a4ac8286SMatthew Dillon * SUCH DAMAGE.
34a4ac8286SMatthew Dillon */
35a4ac8286SMatthew Dillon /*
36a4ac8286SMatthew Dillon * Use: pipe syslog auth output to this program.
37a4ac8286SMatthew Dillon *
38a4ac8286SMatthew Dillon * Detects failed ssh login attempts and maps out the originating IP and
398adecb9cSMichael Neumann * issues, in case of a PF firewall, adds to a PF table <lockout> using
408adecb9cSMichael Neumann * 'pfctl -tlockout -Tadd' commands.
41a4ac8286SMatthew Dillon *
42a4ac8286SMatthew Dillon * /etc/syslog.conf line example:
438adecb9cSMichael Neumann * auth.info;authpriv.info |exec /usr/sbin/sshlockout -pf lockout
44a4ac8286SMatthew Dillon *
45ed17c172SMichael Neumann * Also suggest a cron entry to clean out the PF table at least once a day.
46ed17c172SMichael Neumann * 3 3 * * * pfctl -tlockout -Tflush
478adecb9cSMichael Neumann *
488adecb9cSMichael Neumann * Alternatively there is an ipfw(8) mode (-ipfw <rulenum>).
49a4ac8286SMatthew Dillon */
50a4ac8286SMatthew Dillon
51a4ac8286SMatthew Dillon #include <sys/types.h>
52*1e7ff9c9SSepherosa Ziehau #include <sys/socket.h>
53*1e7ff9c9SSepherosa Ziehau #include <sys/sysctl.h>
54a4ac8286SMatthew Dillon #include <sys/time.h>
55*1e7ff9c9SSepherosa Ziehau
56*1e7ff9c9SSepherosa Ziehau #include <arpa/inet.h>
57*1e7ff9c9SSepherosa Ziehau #include <netinet/in.h>
58*1e7ff9c9SSepherosa Ziehau #include <net/if.h>
59*1e7ff9c9SSepherosa Ziehau #include <net/ipfw/ip_fw.h>
60*1e7ff9c9SSepherosa Ziehau
61a4ac8286SMatthew Dillon #include <stdio.h>
62a4ac8286SMatthew Dillon #include <stdlib.h>
63a4ac8286SMatthew Dillon #include <unistd.h>
64a4ac8286SMatthew Dillon #include <string.h>
65a4ac8286SMatthew Dillon #include <stdarg.h>
66a4ac8286SMatthew Dillon #include <syslog.h>
6746f92f6bSMichael Neumann #include <ctype.h>
688adecb9cSMichael Neumann #include <stdbool.h>
69a4ac8286SMatthew Dillon
70a4ac8286SMatthew Dillon typedef struct iphist {
71a4ac8286SMatthew Dillon struct iphist *next;
72a4ac8286SMatthew Dillon struct iphist *hnext;
73a4ac8286SMatthew Dillon char *ips;
74a4ac8286SMatthew Dillon time_t t;
75a4ac8286SMatthew Dillon int hv;
76a4ac8286SMatthew Dillon } iphist_t;
77a4ac8286SMatthew Dillon
788adecb9cSMichael Neumann struct args {
798adecb9cSMichael Neumann int fw_type;
808adecb9cSMichael Neumann char *arg1;
81*1e7ff9c9SSepherosa Ziehau int arg2;
828adecb9cSMichael Neumann };
838adecb9cSMichael Neumann
848adecb9cSMichael Neumann #define FW_IS_PF 1
858adecb9cSMichael Neumann #define FW_IS_IPFW 2
86*1e7ff9c9SSepherosa Ziehau #define FW_IS_IPFWTBL 3
878adecb9cSMichael Neumann
88a4ac8286SMatthew Dillon #define HSIZE 1024
89a4ac8286SMatthew Dillon #define HMASK (HSIZE - 1)
90a4ac8286SMatthew Dillon #define MAXHIST 100
91a4ac8286SMatthew Dillon #define SSHLIMIT 5 /* per hour */
92cc3c46a7SMichael Neumann #define MAX_TABLE_NAME 20 /* PF table name limit */
93a4ac8286SMatthew Dillon
94ed17c172SMichael Neumann static iphist_t *hist_base;
95ed17c172SMichael Neumann static iphist_t **hist_tail = &hist_base;
96ed17c172SMichael Neumann static iphist_t *hist_hash[HSIZE];
97ed17c172SMichael Neumann static int hist_count = 0;
98a4ac8286SMatthew Dillon
99*1e7ff9c9SSepherosa Ziehau static int ipfw_sock = -1;
100*1e7ff9c9SSepherosa Ziehau
1018adecb9cSMichael Neumann static struct args args;
102ed17c172SMichael Neumann
103ed17c172SMichael Neumann static void init_iphist(void);
104a4ac8286SMatthew Dillon static void checkline(char *buf);
10506857486SMichael Neumann static int insert_iph(const char *ips, time_t t);
106a4ac8286SMatthew Dillon static void delete_iph(iphist_t *ip);
107a4ac8286SMatthew Dillon
108a94e1047SSepherosa Ziehau static void
block_ip(const char * ips)109a94e1047SSepherosa Ziehau block_ip(const char *ips)
110a94e1047SSepherosa Ziehau {
111*1e7ff9c9SSepherosa Ziehau struct ipfw_ioc_tblcont ent;
112*1e7ff9c9SSepherosa Ziehau struct ipfw_ioc_tblent *te;
113cc3c46a7SMichael Neumann char buf[128];
1148adecb9cSMichael Neumann int r = 0;
1158adecb9cSMichael Neumann
1168adecb9cSMichael Neumann switch (args.fw_type) {
1178adecb9cSMichael Neumann case FW_IS_PF:
1188adecb9cSMichael Neumann r = snprintf(buf, sizeof(buf),
1198adecb9cSMichael Neumann "pfctl -t%s -Tadd %s", args.arg1, ips);
1208adecb9cSMichael Neumann break;
121*1e7ff9c9SSepherosa Ziehau
1228adecb9cSMichael Neumann case FW_IS_IPFW:
1238adecb9cSMichael Neumann r = snprintf(buf, sizeof(buf),
1248adecb9cSMichael Neumann "ipfw add %s deny tcp from %s to me 22",
1258adecb9cSMichael Neumann args.arg1, ips);
1268adecb9cSMichael Neumann break;
127*1e7ff9c9SSepherosa Ziehau
128*1e7ff9c9SSepherosa Ziehau case FW_IS_IPFWTBL:
129*1e7ff9c9SSepherosa Ziehau memset(&ent, 0, sizeof(ent));
130*1e7ff9c9SSepherosa Ziehau ent.tableid = args.arg2;
131*1e7ff9c9SSepherosa Ziehau ent.entcnt = 1;
132*1e7ff9c9SSepherosa Ziehau te = &ent.ent[0];
133*1e7ff9c9SSepherosa Ziehau
134*1e7ff9c9SSepherosa Ziehau r = inet_pton(AF_INET, ips, &te->key.sin_addr);
135*1e7ff9c9SSepherosa Ziehau if (r <= 0)
136*1e7ff9c9SSepherosa Ziehau break;
137*1e7ff9c9SSepherosa Ziehau te->key.sin_family = AF_INET;
138*1e7ff9c9SSepherosa Ziehau te->key.sin_len = sizeof(struct sockaddr_in);
139*1e7ff9c9SSepherosa Ziehau
140*1e7ff9c9SSepherosa Ziehau if (setsockopt(ipfw_sock, IPPROTO_IP, IP_FW_TBL_ADD,
141*1e7ff9c9SSepherosa Ziehau &ent, sizeof(ent)) < 0) {
142*1e7ff9c9SSepherosa Ziehau r = -1;
143*1e7ff9c9SSepherosa Ziehau break;
144*1e7ff9c9SSepherosa Ziehau }
145*1e7ff9c9SSepherosa Ziehau /* Done */
146*1e7ff9c9SSepherosa Ziehau return;
1478adecb9cSMichael Neumann }
1488adecb9cSMichael Neumann
1498adecb9cSMichael Neumann if (r > 0 && (int)strlen(buf) == r) {
150cc3c46a7SMichael Neumann system(buf);
151a94e1047SSepherosa Ziehau } else {
1528adecb9cSMichael Neumann syslog(LOG_ERR, "sshlockout: invalid command");
153cc3c46a7SMichael Neumann }
154cc3c46a7SMichael Neumann }
155cc3c46a7SMichael Neumann
156a4ac8286SMatthew Dillon /*
157a4ac8286SMatthew Dillon * Stupid simple string hash
158a4ac8286SMatthew Dillon */
159a94e1047SSepherosa Ziehau static __inline int
iphash(const char * str)160a4ac8286SMatthew Dillon iphash(const char *str)
161a4ac8286SMatthew Dillon {
162a4ac8286SMatthew Dillon int hv = 0xA1B3569D;
163a94e1047SSepherosa Ziehau
164a4ac8286SMatthew Dillon while (*str) {
165a4ac8286SMatthew Dillon hv = (hv << 5) ^ *str ^ (hv >> 23);
166a4ac8286SMatthew Dillon ++str;
167a4ac8286SMatthew Dillon }
168a4ac8286SMatthew Dillon return hv;
169a4ac8286SMatthew Dillon }
170a4ac8286SMatthew Dillon
1718adecb9cSMichael Neumann
1728adecb9cSMichael Neumann static bool
parse_args(int ac,char ** av)1738adecb9cSMichael Neumann parse_args(int ac, char **av)
1748adecb9cSMichael Neumann {
1756d9d77bbSMatthew Dillon if (ac >= 2) {
176*1e7ff9c9SSepherosa Ziehau if (strcmp(av[1], "-pf") == 0 && ac == 3) {
177*1e7ff9c9SSepherosa Ziehau /* -pf <tablename> */
1788adecb9cSMichael Neumann char *tablename = av[2];
179*1e7ff9c9SSepherosa Ziehau
1808adecb9cSMichael Neumann if (strlen(tablename) > 0 &&
1818adecb9cSMichael Neumann strlen(tablename) < MAX_TABLE_NAME) {
1828adecb9cSMichael Neumann args.fw_type = FW_IS_PF;
1838adecb9cSMichael Neumann args.arg1 = tablename;
1848adecb9cSMichael Neumann return true;
1858adecb9cSMichael Neumann }
1868adecb9cSMichael Neumann }
187*1e7ff9c9SSepherosa Ziehau if (strcmp(av[1], "-ipfw") == 0 && ac == 3) {
188*1e7ff9c9SSepherosa Ziehau /* -ipfw <rule> */
1898adecb9cSMichael Neumann char *rule = av[2];
190*1e7ff9c9SSepherosa Ziehau
1918adecb9cSMichael Neumann if (strlen(rule) > 0 && strlen(rule) <= 5) {
1928adecb9cSMichael Neumann for (char *s = rule; *s; ++s) {
1938adecb9cSMichael Neumann if (!isdigit(*s))
1948adecb9cSMichael Neumann return false;
1958adecb9cSMichael Neumann }
1968adecb9cSMichael Neumann if (atoi(rule) < 1)
1978adecb9cSMichael Neumann return false;
1988adecb9cSMichael Neumann if (atoi(rule) > 65535)
1998adecb9cSMichael Neumann return false;
2008adecb9cSMichael Neumann args.fw_type = FW_IS_IPFW;
2018adecb9cSMichael Neumann args.arg1 = rule;
2028adecb9cSMichael Neumann return true;
2038adecb9cSMichael Neumann }
2048adecb9cSMichael Neumann }
205*1e7ff9c9SSepherosa Ziehau
206*1e7ff9c9SSepherosa Ziehau if (strcmp(av[1], "-ipfwtbl") == 0 && ac == 3) {
207*1e7ff9c9SSepherosa Ziehau /* -ipfwtbl <tableid> */
208*1e7ff9c9SSepherosa Ziehau int tableid;
209*1e7ff9c9SSepherosa Ziehau char *eptr;
210*1e7ff9c9SSepherosa Ziehau
211*1e7ff9c9SSepherosa Ziehau tableid = strtoul(av[2], &eptr, 0);
212*1e7ff9c9SSepherosa Ziehau if (*eptr != '\0')
213*1e7ff9c9SSepherosa Ziehau return false;
214*1e7ff9c9SSepherosa Ziehau
215*1e7ff9c9SSepherosa Ziehau ipfw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
216*1e7ff9c9SSepherosa Ziehau if (ipfw_sock < 0)
217*1e7ff9c9SSepherosa Ziehau return false;
218*1e7ff9c9SSepherosa Ziehau
219*1e7ff9c9SSepherosa Ziehau args.fw_type = FW_IS_IPFWTBL;
220*1e7ff9c9SSepherosa Ziehau args.arg2 = tableid;
221*1e7ff9c9SSepherosa Ziehau return true;
2228adecb9cSMichael Neumann }
2238adecb9cSMichael Neumann }
2248adecb9cSMichael Neumann
2258adecb9cSMichael Neumann return false;
2268adecb9cSMichael Neumann }
2278adecb9cSMichael Neumann
228a4ac8286SMatthew Dillon int
main(int ac,char ** av)229ed17c172SMichael Neumann main(int ac, char **av)
230a4ac8286SMatthew Dillon {
231a4ac8286SMatthew Dillon char buf[1024];
232a4ac8286SMatthew Dillon
2338adecb9cSMichael Neumann args.fw_type = 0;
2348adecb9cSMichael Neumann args.arg1 = NULL;
235*1e7ff9c9SSepherosa Ziehau args.arg2 = 0;
236ed17c172SMichael Neumann
2378adecb9cSMichael Neumann if (!parse_args(ac, av)) {
238ed17c172SMichael Neumann syslog(LOG_ERR, "sshlockout: invalid argument");
239ed17c172SMichael Neumann return(1);
240ed17c172SMichael Neumann }
241ed17c172SMichael Neumann
2428adecb9cSMichael Neumann init_iphist();
2438adecb9cSMichael Neumann
244a4ac8286SMatthew Dillon openlog("sshlockout", LOG_PID|LOG_CONS, LOG_AUTH);
245a4ac8286SMatthew Dillon syslog(LOG_ERR, "sshlockout starting up");
246a4ac8286SMatthew Dillon freopen("/dev/null", "w", stdout);
247a4ac8286SMatthew Dillon freopen("/dev/null", "w", stderr);
248a4ac8286SMatthew Dillon
249a4ac8286SMatthew Dillon while (fgets(buf, sizeof(buf), stdin) != NULL) {
250a4ac8286SMatthew Dillon if (strstr(buf, "sshd") == NULL)
251a4ac8286SMatthew Dillon continue;
252a4ac8286SMatthew Dillon checkline(buf);
253a4ac8286SMatthew Dillon }
254a4ac8286SMatthew Dillon syslog(LOG_ERR, "sshlockout exiting");
255a4ac8286SMatthew Dillon return(0);
256a4ac8286SMatthew Dillon }
257a4ac8286SMatthew Dillon
258a94e1047SSepherosa Ziehau static void
checkip(const char * str,const char * reason1,const char * reason2)259a94e1047SSepherosa Ziehau checkip(const char *str, const char *reason1, const char *reason2)
260a94e1047SSepherosa Ziehau {
261a4ac8286SMatthew Dillon char ips[128];
262a4ac8286SMatthew Dillon int n1;
263a4ac8286SMatthew Dillon int n2;
264a4ac8286SMatthew Dillon int n3;
265a4ac8286SMatthew Dillon int n4;
26606857486SMichael Neumann time_t t = time(NULL);
267a4ac8286SMatthew Dillon
2687dc85614SMichael Neumann ips[0] = '\0';
2697dc85614SMichael Neumann
270cc3c46a7SMichael Neumann if (sscanf(str, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) {
271cc3c46a7SMichael Neumann snprintf(ips, sizeof(ips), "%d.%d.%d.%d", n1, n2, n3, n4);
272a94e1047SSepherosa Ziehau } else {
27346f92f6bSMichael Neumann /*
27446f92f6bSMichael Neumann * Check for IPv6 address (primitive way)
27546f92f6bSMichael Neumann */
27646f92f6bSMichael Neumann int cnt = 0;
27746f92f6bSMichael Neumann while (str[cnt] == ':' || isxdigit(str[cnt])) {
27846f92f6bSMichael Neumann ++cnt;
27946f92f6bSMichael Neumann }
28046f92f6bSMichael Neumann if (cnt > 0 && cnt < (int)sizeof(ips)) {
28146f92f6bSMichael Neumann memcpy(ips, str, cnt);
28246f92f6bSMichael Neumann ips[cnt] = '\0';
28346f92f6bSMichael Neumann }
28446f92f6bSMichael Neumann }
28546f92f6bSMichael Neumann
28646f92f6bSMichael Neumann /*
28746f92f6bSMichael Neumann * We do not block localhost as is makes no sense.
28846f92f6bSMichael Neumann */
28946f92f6bSMichael Neumann if (strcmp(ips, "127.0.0.1") == 0)
29046f92f6bSMichael Neumann return;
29146f92f6bSMichael Neumann if (strcmp(ips, "::1") == 0)
29246f92f6bSMichael Neumann return;
2937dc85614SMichael Neumann
2947dc85614SMichael Neumann if (strlen(ips) > 0) {
29506857486SMichael Neumann
29606857486SMichael Neumann /*
29706857486SMichael Neumann * Check for DoS attack. When connections from too many
29806857486SMichael Neumann * IP addresses come in at the same time, our hash table
29906857486SMichael Neumann * would overflow, so we delete the oldest entries AND
30006857486SMichael Neumann * block it's IP when they are younger than 10 seconds.
30106857486SMichael Neumann * This prevents massive attacks from arbitrary IPs.
30206857486SMichael Neumann */
30306857486SMichael Neumann if (hist_count > MAXHIST + 16) {
30406857486SMichael Neumann while (hist_count > MAXHIST) {
30506857486SMichael Neumann iphist_t *iph = hist_base;
30606857486SMichael Neumann int dt = (int)(t - iph->t);
30706857486SMichael Neumann if (dt < 10) {
30806857486SMichael Neumann syslog(LOG_ERR,
30906857486SMichael Neumann "Detected overflow attack, "
31006857486SMichael Neumann "locking out %s\n",
31106857486SMichael Neumann iph->ips);
31206857486SMichael Neumann block_ip(iph->ips);
31306857486SMichael Neumann }
31406857486SMichael Neumann delete_iph(iph);
31506857486SMichael Neumann }
31606857486SMichael Neumann }
31706857486SMichael Neumann
31806857486SMichael Neumann if (insert_iph(ips, t)) {
319cc3c46a7SMichael Neumann syslog(LOG_ERR,
3207dc85614SMichael Neumann "Detected ssh %s attempt "
321cc3c46a7SMichael Neumann "for %s, locking out %s\n",
3227dc85614SMichael Neumann reason1, reason2, ips);
323cc3c46a7SMichael Neumann block_ip(ips);
324cc3c46a7SMichael Neumann }
325cc3c46a7SMichael Neumann }
326cc3c46a7SMichael Neumann }
327cc3c46a7SMichael Neumann
328a94e1047SSepherosa Ziehau static void
checkline(char * buf)329cc3c46a7SMichael Neumann checkline(char *buf)
330cc3c46a7SMichael Neumann {
331cc3c46a7SMichael Neumann char *str;
332cc3c46a7SMichael Neumann
333a4ac8286SMatthew Dillon /*
334a4ac8286SMatthew Dillon * ssh login attempt with password (only hit if ssh allows
335a4ac8286SMatthew Dillon * password entry). Root or admin.
336a4ac8286SMatthew Dillon */
337a4ac8286SMatthew Dillon if ((str = strstr(buf, "Failed password for root from")) != NULL ||
338a4ac8286SMatthew Dillon (str = strstr(buf, "Failed password for admin from")) != NULL) {
339a4ac8286SMatthew Dillon while (*str && (*str < '0' || *str > '9'))
340a4ac8286SMatthew Dillon ++str;
3417dc85614SMichael Neumann checkip(str, "password login", "root or admin");
342a4ac8286SMatthew Dillon return;
343a4ac8286SMatthew Dillon }
344a4ac8286SMatthew Dillon
345a4ac8286SMatthew Dillon /*
346a4ac8286SMatthew Dillon * ssh login attempt with password (only hit if ssh allows password
347a4ac8286SMatthew Dillon * entry). Non-existant user.
348a4ac8286SMatthew Dillon */
349a4ac8286SMatthew Dillon if ((str = strstr(buf, "Failed password for invalid user")) != NULL) {
350a4ac8286SMatthew Dillon str += 32;
351a4ac8286SMatthew Dillon while (*str == ' ')
352a4ac8286SMatthew Dillon ++str;
353a4ac8286SMatthew Dillon while (*str && *str != ' ')
354a4ac8286SMatthew Dillon ++str;
355cc3c46a7SMichael Neumann if (strncmp(str, " from", 5) == 0) {
3567dc85614SMichael Neumann checkip(str + 5, "password login", "an invalid user");
3577dc85614SMichael Neumann }
3587dc85614SMichael Neumann return;
3597dc85614SMichael Neumann }
3607dc85614SMichael Neumann
3617dc85614SMichael Neumann /*
3627dc85614SMichael Neumann * ssh login attempt for non-existant user.
3637dc85614SMichael Neumann */
3647dc85614SMichael Neumann if ((str = strstr(buf, "Invalid user")) != NULL) {
3657dc85614SMichael Neumann str += 12;
3667dc85614SMichael Neumann while (*str == ' ')
3677dc85614SMichael Neumann ++str;
3687dc85614SMichael Neumann while (*str && *str != ' ')
3697dc85614SMichael Neumann ++str;
3707dc85614SMichael Neumann if (strncmp(str, " from", 5) == 0) {
3717dc85614SMichael Neumann checkip(str + 5, "login", "an invalid user");
372a4ac8286SMatthew Dillon }
373a4ac8286SMatthew Dillon return;
374a4ac8286SMatthew Dillon }
375a4ac8286SMatthew Dillon
376a4ac8286SMatthew Dillon /*
377a4ac8286SMatthew Dillon * Premature disconnect in pre-authorization phase, typically an
378a4ac8286SMatthew Dillon * attack but require 5 attempts in an hour before cleaning it out.
379a4ac8286SMatthew Dillon */
380a4ac8286SMatthew Dillon if ((str = strstr(buf, "Received disconnect from ")) != NULL &&
381a4ac8286SMatthew Dillon strstr(buf, "[preauth]") != NULL) {
3826ea1a107SMichael Neumann checkip(str + 25, "preauth", "an invalid user");
383a4ac8286SMatthew Dillon return;
384a4ac8286SMatthew Dillon }
385880c6c96SMatthew Dillon
386880c6c96SMatthew Dillon /*
387880c6c96SMatthew Dillon * Maximum authentication attempts exceeded
388880c6c96SMatthew Dillon */
389880c6c96SMatthew Dillon if ((str = strstr(buf, "maximum authentication "
390880c6c96SMatthew Dillon "attempts exceeded for ")) != NULL &&
391880c6c96SMatthew Dillon strstr(buf, "[preauth]") != NULL) {
392880c6c96SMatthew Dillon str += 45;
393880c6c96SMatthew Dillon while (*str == ' ')
394880c6c96SMatthew Dillon ++str;
395880c6c96SMatthew Dillon while (*str && *str != ' ')
396880c6c96SMatthew Dillon ++str;
397880c6c96SMatthew Dillon if (strncmp(str, " from", 5) == 0) {
398880c6c96SMatthew Dillon checkip(str + 5, "login", "many attempts");
399880c6c96SMatthew Dillon }
400880c6c96SMatthew Dillon return;
401880c6c96SMatthew Dillon }
402a4ac8286SMatthew Dillon }
403a4ac8286SMatthew Dillon
404a4ac8286SMatthew Dillon /*
405a4ac8286SMatthew Dillon * Insert IP record
406a4ac8286SMatthew Dillon */
407a94e1047SSepherosa Ziehau static int
insert_iph(const char * ips,time_t t)40806857486SMichael Neumann insert_iph(const char *ips, time_t t)
409a4ac8286SMatthew Dillon {
410a4ac8286SMatthew Dillon iphist_t *ip = malloc(sizeof(*ip));
411a4ac8286SMatthew Dillon iphist_t *scan;
412a4ac8286SMatthew Dillon int found;
413a4ac8286SMatthew Dillon
414a4ac8286SMatthew Dillon ip->hv = iphash(ips);
415a4ac8286SMatthew Dillon ip->ips = strdup(ips);
416a4ac8286SMatthew Dillon ip->t = t;
417a4ac8286SMatthew Dillon
418a4ac8286SMatthew Dillon ip->hnext = hist_hash[ip->hv & HMASK];
419a4ac8286SMatthew Dillon hist_hash[ip->hv & HMASK] = ip;
420a4ac8286SMatthew Dillon ip->next = NULL;
421a4ac8286SMatthew Dillon *hist_tail = ip;
422a4ac8286SMatthew Dillon hist_tail = &ip->next;
423a4ac8286SMatthew Dillon ++hist_count;
424a4ac8286SMatthew Dillon
425a4ac8286SMatthew Dillon /*
426a4ac8286SMatthew Dillon * hysteresis
427a4ac8286SMatthew Dillon */
428a4ac8286SMatthew Dillon if (hist_count > MAXHIST + 16) {
429a4ac8286SMatthew Dillon while (hist_count > MAXHIST)
430a4ac8286SMatthew Dillon delete_iph(hist_base);
431a4ac8286SMatthew Dillon }
432a4ac8286SMatthew Dillon
433a4ac8286SMatthew Dillon /*
434a4ac8286SMatthew Dillon * Check limit
435a4ac8286SMatthew Dillon */
436a4ac8286SMatthew Dillon found = 0;
437a4ac8286SMatthew Dillon for (scan = hist_hash[ip->hv & HMASK]; scan; scan = scan->hnext) {
438a4ac8286SMatthew Dillon if (scan->hv == ip->hv && strcmp(scan->ips, ip->ips) == 0) {
439a4ac8286SMatthew Dillon int dt = (int)(t - ip->t);
440a4ac8286SMatthew Dillon if (dt < 60 * 60) {
441a4ac8286SMatthew Dillon ++found;
442a4ac8286SMatthew Dillon if (found > SSHLIMIT)
443a4ac8286SMatthew Dillon break;
444a4ac8286SMatthew Dillon }
445a4ac8286SMatthew Dillon }
446a4ac8286SMatthew Dillon }
447a4ac8286SMatthew Dillon return (found > SSHLIMIT);
448a4ac8286SMatthew Dillon }
449a4ac8286SMatthew Dillon
450a4ac8286SMatthew Dillon /*
451a4ac8286SMatthew Dillon * Delete an ip record. Note that we always delete from the head of the
452a4ac8286SMatthew Dillon * list, but we will still wind up scanning hash chains.
453a4ac8286SMatthew Dillon */
454a94e1047SSepherosa Ziehau static void
delete_iph(iphist_t * ip)455a4ac8286SMatthew Dillon delete_iph(iphist_t *ip)
456a4ac8286SMatthew Dillon {
457a4ac8286SMatthew Dillon iphist_t **scanp;
458a4ac8286SMatthew Dillon iphist_t *scan;
459a4ac8286SMatthew Dillon
460a4ac8286SMatthew Dillon scanp = &hist_base;
461a4ac8286SMatthew Dillon while ((scan = *scanp) != ip) {
462a4ac8286SMatthew Dillon scanp = &scan->next;
463a4ac8286SMatthew Dillon }
464a4ac8286SMatthew Dillon *scanp = ip->next;
465a4ac8286SMatthew Dillon if (hist_tail == &ip->next)
466a4ac8286SMatthew Dillon hist_tail = scanp;
467a4ac8286SMatthew Dillon
468a4ac8286SMatthew Dillon scanp = &hist_hash[ip->hv & HMASK];
469a4ac8286SMatthew Dillon while ((scan = *scanp) != ip) {
470a4ac8286SMatthew Dillon scanp = &scan->hnext;
471a4ac8286SMatthew Dillon }
472a4ac8286SMatthew Dillon *scanp = ip->hnext;
473a4ac8286SMatthew Dillon
474a4ac8286SMatthew Dillon --hist_count;
475a4ac8286SMatthew Dillon free(ip);
476a4ac8286SMatthew Dillon }
477ed17c172SMichael Neumann
478a94e1047SSepherosa Ziehau static void
init_iphist(void)479a94e1047SSepherosa Ziehau init_iphist(void)
480a94e1047SSepherosa Ziehau {
481ed17c172SMichael Neumann hist_base = NULL;
482ed17c172SMichael Neumann hist_tail = &hist_base;
483ed17c172SMichael Neumann for (int i = 0; i < HSIZE; i++) {
484ed17c172SMichael Neumann hist_hash[i] = NULL;
485ed17c172SMichael Neumann }
486ed17c172SMichael Neumann hist_count = 0;
487ed17c172SMichael Neumann }
488