Files
tallow/tallow.c
2017-05-07 00:17:48 -07:00

364 lines
7.3 KiB
C

/*
* tallow.c - IP block sshd login abuse
*
* (C) Copyright 2015 Intel Corporation
* Authors:
* Auke Kok <auke-jan.h.kok@intel.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; version 3
* of the License.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <signal.h>
#include <unistd.h>
#include <limits.h>
#include <sys/time.h>
#include <systemd/sd-journal.h>
struct tallow_struct {
char *ip;
int count;
struct timeval time;
struct tallow_struct *next;
};
static struct tallow_struct *head;
static struct tallow_struct *whitelist;
#define FILTER_STRING "SYSLOG_IDENTIFIER=sshd"
static char ipt_path[PATH_MAX];
static int threshold = 3;
static int expires = 3600;
static int has_ipv6 = 0;
static sd_journal *j;
static int ext(char *fmt, ...)
{
va_list args;
char cmd[1024];
int ret = 0;
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
ret = system(cmd);
if (ret)
fprintf(stderr, "Error executing \"%s\": returned %d\n", cmd, ret);
return (ret);
}
static void ext_ignore(char *fmt, ...)
{
va_list args;
char cmd[1024];
va_start(args, fmt);
vsnprintf(cmd, sizeof(cmd), fmt, args);
va_end(args);
__attribute__((unused)) int ret = system(cmd);
}
static void block(struct tallow_struct *s)
{
if (s->count != threshold)
return;
if (strchr(s->ip, ':')) {
if (has_ipv6)
(void) ext("%s/ipset -A tallow6 %s", ipt_path, s->ip);
} else {
(void) ext("%s/ipset -A tallow %s", ipt_path, s->ip);
}
fprintf(stderr, "Blocked %s\n", s->ip);
/* remove entry from the list */
if (head == s) {
head = s->next;
free(s->ip);
free(s);
} else {
struct tallow_struct *p = s;
s = s->next;
free(p->ip);
free(p);
}
}
static void whitelist_add(char *ip)
{
struct tallow_struct *w = whitelist;
struct tallow_struct *n;
while (w && w->next)
w = w->next;
n = malloc(sizeof(struct tallow_struct));
if (!n) {
fprintf(stderr, "Out of memory.\n");
exit(1);
}
memset(n, 0, sizeof(struct tallow_struct));
n->ip = strdup(ip);
n->next = NULL;
if (!whitelist)
whitelist = n;
else
w->next = n;
}
static void find(char *ip)
{
struct tallow_struct *s = head;
struct tallow_struct *n;
struct tallow_struct *w = whitelist;
/*
* not validating the IP address format here, just
* making sure we're not passing special characters
* to system().
*/
if (strspn(ip, "0123456789abcdef:.") != strlen(ip))
return;
/* whitelist */
while (w) {
if (!strcmp(w->ip, ip))
return;
w = w->next;
}
/* walk and update entry */
while (s) {
if (!strcmp(s->ip, ip)) {
s->count++;
(void) gettimeofday(&s->time, NULL);
block(s);
return;
}
if (s->next)
s = s->next;
else
break;
}
/* append */
n = malloc(sizeof(struct tallow_struct));
if (!n) {
fprintf(stderr, "Out of memory.\n");
exit(1);
}
memset(n, 0, sizeof(struct tallow_struct));
if (!head)
head = n;
else
s->next = n;
n->ip = strdup(ip);
n->count = 1;
n->next = NULL;
(void) gettimeofday(&n->time, NULL);
block(n);
return;
}
static void dump(void)
{
struct tallow_struct *s = head;
fprintf(stderr, "Received SIGUSR1 - dumping address table: address: count, time\n");
while (s) {
fprintf(stderr, "%s: %d, %lu.%lu\n", s->ip, s->count, s->time.tv_sec, s->time.tv_usec);
s = s->next;
}
}
static void sig(int s)
{
if (s == SIGUSR1) {
dump();
} else {
fprintf(stderr, "Exiting on request.\n");
sd_journal_close(j);
struct tallow_struct *s = head;
while (s) {
struct tallow_struct *n = NULL;
free(s->ip);
n = s;
s = s->next;
free(n);
}
exit(0);
}
}
int main(void)
{
int r;
FILE *f;
struct sigaction s;
int timeout = 60;
strcpy(ipt_path, "/usr/sbin");
memset(&s, 0, sizeof(struct sigaction));
s.sa_handler = sig;
sigaction(SIGUSR1, &s, NULL);
sigaction(SIGHUP, &s, NULL);
sigaction(SIGTERM, &s, NULL);
sigaction(SIGINT, &s, NULL);
if (access("/proc/sys/net/ipv6", R_OK | X_OK) == 0)
has_ipv6 = 1;
f = fopen("/etc/tallow.conf", "r");
if (f) {
char buf[256];
char *key;
char *val;
while (fgets(buf, 80, f) != NULL) {
char *c;
c = strchr(buf, '\n');
if (c) *c = 0; /* remove trailing \n */
if (buf[0] == '#')
continue; /* comment line */
key = strtok(buf, "=");
if (!key)
continue;
val = strtok(NULL, "=");
if (!val)
continue;
// todo: filter leading/trailing whitespace
if (!strcmp(key, "ipt_path"))
strncpy(ipt_path, val, PATH_MAX - 1);
if (!strcmp(key, "threshold"))
threshold = atoi(val);
if (!strcmp(key, "expires"))
expires = atoi(val);
if (!strcmp(key, "whitelist"))
whitelist_add(val);
if (!strcmp(key, "ipv6"))
has_ipv6 = atoi(val);
}
fclose(f);
}
if (!has_ipv6)
fprintf(stdout, "ipv6 support disabled.\n");
if (!whitelist)
whitelist_add("127.0.0.1");
r = sd_journal_open(&j, SD_JOURNAL_LOCAL_ONLY);
if (r < 0) {
fprintf(stderr, "Failed to open journal: %s\n", strerror(-r));
exit(1);
}
/* init ipset and iptables */
/* delete iptables ref to set before the ipset! */
ext_ignore("%s/iptables -t filter -D INPUT -m set --match-set tallow src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow 2> /dev/null", ipt_path);
if (ext("%s/ipset create tallow hash:ip family inet timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv4 ipset.\n");
exit(1);
}
if (ext("%s/iptables -t filter -A INPUT -m set --match-set tallow src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create iptables rule.\n");
exit(1);
}
if (has_ipv6) {
ext_ignore("%s/ip6tables -t filter -D INPUT -m set --match-set tallow6 src -j DROP 2> /dev/null", ipt_path);
ext_ignore("%s/ipset destroy tallow6 2> /dev/null", ipt_path);
if (ext("%s/ipset create tallow6 hash:ip family inet6 timeout %d", ipt_path, expires)) {
fprintf(stderr, "Unable to create ipv6 ipset.\n");
exit(1);
}
if (ext("%s/ip6tables -t filter -A INPUT -m set --match-set tallow6 src -j DROP", ipt_path)) {
fprintf(stderr, "Unable to create ipt6ables rule.\n");
exit(1);
}
}
/* ffwd journal */
sd_journal_add_match(j, FILTER_STRING, 0);
r = sd_journal_seek_tail(j);
sd_journal_wait(j, (uint64_t) 0);
fprintf(stderr, "sd_journal_seek_tail() returned %d\n", r);
while (sd_journal_next(j) != 0)
r++;
fprintf(stderr, "Forwarded through %d items in the journal to reach the end\n", r);
fprintf(stderr, "Started\n");
for (;;) {
const void *d;
size_t l;
r = sd_journal_wait(j, (uint64_t) timeout * 1000000);
if (r == SD_JOURNAL_INVALIDATE) {
fprintf(stderr, "Journal was rotated, resetting\n");
sd_journal_seek_tail(j);
}
while (sd_journal_next(j) != 0) {
char *t;
char *m;
int i;
if (sd_journal_get_data(j, "MESSAGE", &d, &l) < 0) {
fprintf(stderr, "Failed to read message field: %s\n", strerror(-r));
continue;
}
m = strndup(d, l+1);
m[l] = '\0';
if (strstr(m, "MESSAGE=Failed password for invalid user ")) {
t = strtok(m, " ");
for (i = 0; i < 7; i++)
t = strtok(NULL, " ");
find(t);
}
if (strstr(m, "MESSAGE=Failed password for root ")) {
t = strtok(m, " ");
for (i = 0; i < 5; i++)
t = strtok(NULL, " ");
find(t);
}
free(m);
}
}
sd_journal_close(j);
exit(0);
}