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 adds to a PF table <lockout> using 'pfctl -tlockout -Tadd' commands.
41 * /etc/syslog.conf line example:
42 * auth.info;authpriv.info |exec /usr/sbin/sshlockout lockout
44 * Also suggest a cron entry to clean out the PF table at least once a day.
45 * 3 3 * * * pfctl -tlockout -Tflush
48 #include <sys/types.h>
57 typedef struct iphist {
66 #define HMASK (HSIZE - 1)
68 #define SSHLIMIT 5 /* per hour */
70 static iphist_t *hist_base;
71 static iphist_t **hist_tail = &hist_base;
72 static iphist_t *hist_hash[HSIZE];
73 static int hist_count = 0;
75 static char *pftable = NULL;
77 static void init_iphist(void);
78 static void checkline(char *buf);
79 static int insert_iph(const char *ips);
80 static void delete_iph(iphist_t *ip);
83 * Stupid simple string hash
87 iphash(const char *str)
91 hv = (hv << 5) ^ *str ^ (hv >> 23);
98 main(int ac, char **av)
104 if (ac == 2 && av[1] != NULL) {
108 syslog(LOG_ERR, "sshlockout: invalid argument");
112 openlog("sshlockout", LOG_PID|LOG_CONS, LOG_AUTH);
113 syslog(LOG_ERR, "sshlockout starting up");
114 freopen("/dev/null", "w", stdout);
115 freopen("/dev/null", "w", stderr);
117 while (fgets(buf, sizeof(buf), stdin) != NULL) {
118 if (strstr(buf, "sshd") == NULL)
122 syslog(LOG_ERR, "sshlockout exiting");
138 * ssh login attempt with password (only hit if ssh allows
139 * password entry). Root or admin.
141 if ((str = strstr(buf, "Failed password for root from")) != NULL ||
142 (str = strstr(buf, "Failed password for admin from")) != NULL) {
143 while (*str && (*str < '0' || *str > '9'))
145 if (sscanf(str, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) {
146 snprintf(ips, sizeof(ips), "%d.%d.%d.%d",
148 if (insert_iph(ips)) {
150 "Detected ssh password login attempt "
151 "for root or admin, locking out %s\n",
153 snprintf(buf, sizeof(buf),
154 "pfctl -t%s -Tadd %s",
163 * ssh login attempt with password (only hit if ssh allows password
164 * entry). Non-existant user.
166 if ((str = strstr(buf, "Failed password for invalid user")) != NULL) {
170 while (*str && *str != ' ')
172 if (strncmp(str, " from", 5) == 0 &&
173 sscanf(str + 5, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) {
174 snprintf(ips, sizeof(ips), "%d.%d.%d.%d",
176 if (insert_iph(ips)) {
178 "Detected ssh password login attempt "
179 "for an invalid user, locking out %s\n",
181 snprintf(buf, sizeof(buf),
182 "pfctl -t%s -Tadd %s",
191 * Premature disconnect in pre-authorization phase, typically an
192 * attack but require 5 attempts in an hour before cleaning it out.
194 if ((str = strstr(buf, "Received disconnect from ")) != NULL &&
195 strstr(buf, "[preauth]") != NULL) {
196 if (sscanf(str + 25, "%d.%d.%d.%d", &n1, &n2, &n3, &n4) == 4) {
197 snprintf(ips, sizeof(ips), "%d.%d.%d.%d",
199 if (insert_iph(ips)) {
201 "Detected ssh password login attempt "
202 "for an invalid user, locking out %s\n",
204 snprintf(buf, sizeof(buf),
205 "pfctl -t%s -Tadd %s",
219 insert_iph(const char *ips)
221 iphist_t *ip = malloc(sizeof(*ip));
223 time_t t = time(NULL);
226 ip->hv = iphash(ips);
227 ip->ips = strdup(ips);
230 ip->hnext = hist_hash[ip->hv & HMASK];
231 hist_hash[ip->hv & HMASK] = ip;
234 hist_tail = &ip->next;
240 if (hist_count > MAXHIST + 16) {
241 while (hist_count > MAXHIST)
242 delete_iph(hist_base);
249 for (scan = hist_hash[ip->hv & HMASK]; scan; scan = scan->hnext) {
250 if (scan->hv == ip->hv && strcmp(scan->ips, ip->ips) == 0) {
251 int dt = (int)(t - ip->t);
254 if (found > SSHLIMIT)
259 return (found > SSHLIMIT);
263 * Delete an ip record. Note that we always delete from the head of the
264 * list, but we will still wind up scanning hash chains.
268 delete_iph(iphist_t *ip)
274 while ((scan = *scanp) != ip) {
278 if (hist_tail == &ip->next)
281 scanp = &hist_hash[ip->hv & HMASK];
282 while ((scan = *scanp) != ip) {
283 scanp = &scan->hnext;
295 hist_tail = &hist_base;
296 for (int i = 0; i < HSIZE; i++) {