mirror of
https://github.com/clearlinux/tallow.git
synced 2026-05-01 11:43:34 +00:00
364 lines
7.3 KiB
C
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);
|
|
}
|