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
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
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
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>
56 #include <arpa/inet.h>
57 #include <netinet/in.h>
59 #include <net/ipfw/ip_fw.h>
70 typedef struct iphist
{
86 #define FW_IS_IPFWTBL 3
89 #define HMASK (HSIZE - 1)
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
);
109 block_ip(const char *ips
)
111 struct ipfw_ioc_tblcont ent
;
112 struct ipfw_ioc_tblent
*te
;
116 switch (args
.fw_type
) {
118 r
= snprintf(buf
, sizeof(buf
),
119 "pfctl -t%s -Tadd %s", args
.arg1
, ips
);
123 r
= snprintf(buf
, sizeof(buf
),
124 "ipfw add %s deny tcp from %s to me 22",
129 memset(&ent
, 0, sizeof(ent
));
130 ent
.tableid
= args
.arg2
;
134 r
= inet_pton(AF_INET
, ips
, &te
->key
.sin_addr
);
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) {
149 if (r
> 0 && (int)strlen(buf
) == r
) {
152 syslog(LOG_ERR
, "sshlockout: invalid command");
157 * Stupid simple string hash
160 iphash(const char *str
)
165 hv
= (hv
<< 5) ^ *str
^ (hv
>> 23);
173 parse_args(int ac
, char **av
)
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
;
187 if (strcmp(av
[1], "-ipfw") == 0 && ac
== 3) {
191 if (strlen(rule
) > 0 && strlen(rule
) <= 5) {
192 for (char *s
= rule
; *s
; ++s
) {
198 if (atoi(rule
) > 65535)
200 args
.fw_type
= FW_IS_IPFW
;
206 if (strcmp(av
[1], "-ipfwtbl") == 0 && ac
== 3) {
207 /* -ipfwtbl <tableid> */
211 tableid
= strtoul(av
[2], &eptr
, 0);
215 ipfw_sock
= socket(AF_INET
, SOCK_RAW
, IPPROTO_RAW
);
219 args
.fw_type
= FW_IS_IPFWTBL
;
229 main(int ac
, char **av
)
237 if (!parse_args(ac
, av
)) {
238 syslog(LOG_ERR
, "sshlockout: invalid argument");
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
)
254 syslog(LOG_ERR
, "sshlockout exiting");
259 checkip(const char *str
, const char *reason1
, const char *reason2
)
266 time_t t
= time(NULL
);
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
);
274 * Check for IPv6 address (primitive way)
277 while (str
[cnt
] == ':' || isxdigit(str
[cnt
])) {
280 if (cnt
> 0 && cnt
< (int)sizeof(ips
)) {
281 memcpy(ips
, str
, cnt
);
287 * We do not block localhost as is makes no sense.
289 if (strcmp(ips
, "127.0.0.1") == 0)
291 if (strcmp(ips
, "::1") == 0)
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
);
309 "Detected overflow attack, "
318 if (insert_iph(ips
, t
)) {
320 "Detected ssh %s attempt "
321 "for %s, locking out %s\n",
322 reason1
, reason2
, ips
);
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'))
341 checkip(str
, "password login", "root or admin");
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
) {
353 while (*str
&& *str
!= ' ')
355 if (strncmp(str
, " from", 5) == 0) {
356 checkip(str
+ 5, "password login", "an invalid user");
362 * ssh login attempt for non-existant user.
364 if ((str
= strstr(buf
, "Invalid user")) != NULL
) {
368 while (*str
&& *str
!= ' ')
370 if (strncmp(str
, " from", 5) == 0) {
371 checkip(str
+ 5, "login", "an invalid user");
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");
387 * Maximum authentication attempts exceeded
389 if ((str
= strstr(buf
, "maximum authentication "
390 "attempts exceeded for ")) != NULL
&&
391 strstr(buf
, "[preauth]") != NULL
) {
395 while (*str
&& *str
!= ' ')
397 if (strncmp(str
, " from", 5) == 0) {
398 checkip(str
+ 5, "login", "many attempts");
408 insert_iph(const char *ips
, time_t t
)
410 iphist_t
*ip
= malloc(sizeof(*ip
));
414 ip
->hv
= iphash(ips
);
415 ip
->ips
= strdup(ips
);
418 ip
->hnext
= hist_hash
[ip
->hv
& HMASK
];
419 hist_hash
[ip
->hv
& HMASK
] = ip
;
422 hist_tail
= &ip
->next
;
428 if (hist_count
> MAXHIST
+ 16) {
429 while (hist_count
> MAXHIST
)
430 delete_iph(hist_base
);
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
);
442 if (found
> SSHLIMIT
)
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.
455 delete_iph(iphist_t
*ip
)
461 while ((scan
= *scanp
) != ip
) {
465 if (hist_tail
== &ip
->next
)
468 scanp
= &hist_hash
[ip
->hv
& HMASK
];
469 while ((scan
= *scanp
) != ip
) {
470 scanp
= &scan
->hnext
;
482 hist_tail
= &hist_base
;
483 for (int i
= 0; i
< HSIZE
; i
++) {