forbidhosts/forbidhosts.cpp

476 lines
11 KiB
C++

/**
* @PROJECT ForbidHosts
* @COPYRIGHT See COPYING in the top level directory
* @FILE forbidhosts.cpp
* @PURPOSE Tool for checking IPv4 & IPv6 failed connections
* @DEVELOPERS Pierre Schweitzer <pierre@reactos.org>
* Rafal Kupiec <belliash@asiotec.eu.org>
*/
#include <sys/stat.h>
#include <sys/inotify.h>
#include <poll.h>
#include <errno.h>
#include <fcntl.h>
#include <syslog.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <cstdarg>
#include <csignal>
#include "forbidhosts.h"
using namespace std;
void assertException(const char *file, unsigned int line, const char *assert, bool critical) {
syslog(LOG_ERR, "Assertion '%s' failed at line %d in file %s", assert, line, file);
if(critical) {
shutdown(EXIT_FAILURE);
}
}
void assignGlobalVars() {
global.max_attempts = MAXATTEMPTS;
global.host_expire = HOSTEXPIRE;
global.failure_penalty = FAILUREPENALTY;
global.deny_file = DENYFILE;
global.log_file = LOGFILE;
global.pid_file = PIDFILE;
global.protocol = "sshd";
}
bool compare(const host_t &lhs, const host_t &rhs) {
return (lhs.expires > rhs.expires);
}
void daemonize(string workdir) {
int pidfile;
char pid[10];
struct sigaction sigact;
pid_t daemon;
if(getppid() == 1) {
return;
}
setlogmask(LOG_MASK(LOG_INFO) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_CRIT));
openlog("ForbidHosts", LOG_CONS, LOG_USER);
syslog(LOG_INFO, "Starting up daemon.");
daemon = fork();
if(daemon < 0) {
syslog(LOG_CRIT, "Unable to execute fork().");
shutdown(EXIT_FAILURE);
}
if(daemon > 0) {
syslog(LOG_INFO, "Child process created, working in background. Parent exiting.");
exit(EXIT_SUCCESS);
}
sigact.sa_handler = &signalHandler;
sigact.sa_flags = SA_RESTART;
sigfillset(&sigact.sa_mask);
sigaction(SIGABRT, &sigact, NULL);
sigaction(SIGHUP, &sigact, NULL);
sigaction(SIGINT, &sigact, NULL);
sigaction(SIGKILL, &sigact, NULL);
sigaction(SIGQUIT, &sigact, NULL);
sigaction(SIGTERM, &sigact, NULL);
sigaction(SIGUSR1, &sigact, NULL);
sigaction(SIGUSR2, &sigact, NULL);
umask(0);
if(setsid() < 0) {
shutdown(EXIT_FAILURE);
}
if((chdir(workdir.c_str())) < 0) {
syslog(LOG_CRIT, "Unable to change working directory to '%s'.", workdir.c_str());
shutdown(EXIT_FAILURE);
}
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
pidfile = open(global.pid_file.c_str(), O_RDWR | O_CREAT, 0600);
if(pidfile == -1) {
syslog(LOG_CRIT, "Unable to open '%s' PID lock file.", global.pid_file.c_str());
shutdown(EXIT_FAILURE);
}
if(lockf(pidfile, F_TLOCK,0) == -1) {
syslog(LOG_CRIT, "Unable to lock '%s' PID lock file.", global.pid_file.c_str());
shutdown(EXIT_FAILURE);
}
sprintf(pid, "%d\n", getpid());
assertSoft(write(pidfile, pid, strlen(pid)) == (ssize_t) strlen(pid));
close(pidfile);
syslog(LOG_INFO, "Daemon is UP & RUNNING.");
}
void debug(const char *msg, ...) {
int fp;
char *log = NULL;
va_list args;
if(global.debug.length()) {
if((fp = open(global.debug.c_str(), O_WRONLY | O_APPEND))) {
va_start(args, msg);
sprintf(log, msg, args);
va_end(args);
assertSoft(write(fp, log, strlen(log)) == (ssize_t) strlen(log));
close(fp);
sync();
}
}
}
void denyHost(string host) {
int colon;
int deny;
string entry;
deny = open(global.deny_file.c_str(), O_WRONLY | O_APPEND);
if(deny < 0) {
debug("Banning host '%s' in file %s failed for unknown reason.", host.c_str(), global.deny_file.c_str());
syslog(LOG_ERR, "Banning host '%s' in file %s failed for unknown reason.", host.c_str(), global.deny_file.c_str());
shutdown(EXIT_FAILURE);
}
colon = host.find_first_of(':');
if(colon != -1) {
entry = global.protocol + ": [" + host + "]\n";
} else {
entry = global.protocol + ": " + host + "\n";
}
assertSoft(write(deny, entry.c_str(), entry.length()) == (ssize_t) entry.length());
close(deny);
sync();
debug("Banned host '%s' for excessive abusive.", host.c_str());
syslog(LOG_INFO, "Banned host '%s' for excessive abusive.", host.c_str());
}
int main(int argc, char *argv[]) {
int i_auth;
int i_notify;
int next_opt;
int val;
vector<banned_t> banned;
vector<host_t> hosts;
assignGlobalVars();
while((next_opt = getopt(argc, argv, "t:p:n:l:e:d:D:av")) != -1) {
switch(next_opt) {
case 'a':
global.protocol = "ALL";
break;
case 'd':
global.deny_file = strdup(optarg);
break;
case 'D':
global.debug = strdup(optarg);
break;
case 'e':
val = sscanf(optarg, "%u", &global.host_expire);
if(val > 0) {
global.host_expire = global.host_expire > 0 ? global.host_expire : HOSTEXPIRE;
} else {
global.host_expire = HOSTEXPIRE;
}
break;
case 'l':
global.log_file = strdup(optarg);
break;
case 'n':
global.banned_names = strdup(optarg);
break;
case 'p':
global.pid_file = strdup(optarg);
break;
case 't':
val = sscanf(optarg, "%u", &global.max_attempts);
if(val > 0) {
global.max_attempts = global.max_attempts > 0 ? global.max_attempts : MAXATTEMPTS;
} else {
global.max_attempts = MAXATTEMPTS;
}
break;
case 'v':
printVersion();
break;
}
}
daemonize("/tmp");
logfile = open(global.log_file.c_str(), O_RDONLY | O_NONBLOCK);
if(logfile < 0) {
syslog(LOG_CRIT, "Unable to open '%s' log file for reading.", global.log_file.c_str());
shutdown(EXIT_FAILURE);
}
lseek(logfile, 0, SEEK_END);
i_notify = inotify_init1(IN_NONBLOCK);
if(i_notify < 0) {
close(logfile);
syslog(LOG_CRIT, "Unable to initialize iNotify subsystem.");
shutdown(EXIT_FAILURE);
}
i_auth = inotify_add_watch(i_notify, global.log_file.c_str(), IN_MODIFY);
if(i_auth < 0) {
close(i_notify);
close(logfile);
syslog(LOG_CRIT, "Unable to attach a watcher on a log file.");
shutdown(EXIT_FAILURE);
}
for(;;) {
struct pollfd fds[] = {i_notify, POLLIN, 0};
int timeout = -1;
if(!hosts.empty()) {
timeout = hosts.back().expires - time(0) * 1000;
}
int event = poll(fds, 1, timeout);
if(event > 0) {
struct inotify_event i_event;
assertSoft(read(i_notify, &i_event, sizeof(struct inotify_event)) == sizeof(struct inotify_event));
}
while(!readLine(logfile, hosts, banned));
while(!banned.empty()) {
if(banned.back().expires > time(0)) {
break;
}
banned.pop_back();
}
while(!hosts.empty()) {
if(hosts.back().expires > time(0)) {
break;
}
hosts.pop_back();
}
sleep(1);
}
close(i_auth);
close(i_notify);
close(logfile);
}
void printVersion() {
printf("ForbidHosts Daemon v%s\n\n", FHVERSION);
exit(EXIT_SUCCESS);
}
unsigned int readLine(int file, vector<host_t> &hosts, vector<banned_t> &banned) {
char line[255];
char *address;
string host;
bool loggedin = false;
static string last_addr = "";
unsigned int length;
unsigned int addr_length;
unsigned int red = 0;
unsigned int repeated = 1;
for(;;) {
red = 0;
while(red < sizeof(line) / sizeof(char)) {
length = read(file, &line[red], sizeof(char));
if(length < 1) {
if(red == 0) {
return length;
} else {
break;
}
}
if(line[red] == '\n') {
line[red] = '\0';
break;
}
red++;
}
if(!validateEntry(line, &address, &addr_length, &loggedin)) {
if(!last_addr.empty()) {
repeated = validateRepeated(line);
if(repeated == 0) {
last_addr = "";
return length;
}
} else {
last_addr = "";
return length;
}
} else {
host = address;
host.erase(addr_length);
last_addr = host;
}
if(updateHost(last_addr, hosts, banned, repeated, loggedin)) {
hosts.push_back(host_t(time(0), host));
}
sort(hosts.begin(), hosts.end(), compare);
}
}
void shutdown(int code) {
if(code != 0) {
syslog(LOG_CRIT, "Emergency daemon shutdown due to errors.");
} else {
syslog(LOG_INFO, "Shutting down daemon.");
}
unlink(global.pid_file.c_str());
exit(code);
}
void signalHandler(int signal) {
switch(signal) {
case SIGHUP:
syslog(LOG_WARNING, "Received SIGHUP signal!");
break;
case SIGABRT:
case SIGINT:
case SIGQUIT:
case SIGTERM:
shutdown(EXIT_SUCCESS);
break;
case SIGUSR1:
case SIGUSR2:
syslog(LOG_INFO, "Received SIGUSR1 signal - probably logs rotated.");
syslog(LOG_INFO, "Reloading system log file.");
lseek(logfile, 0, SEEK_END);
break;
default:
syslog(LOG_WARNING, "Unhandled signal caught: %s.", strsignal(signal));
return;
}
}
bool updateHost(const string &host, vector<host_t> &hosts, vector<banned_t> &banned, unsigned int repeated, bool loggedin) {
bool insert = true;
assertSoft(!banned.empty());
for(vector<banned_t>::iterator it = banned.begin(); it != banned.end(); ++it) {
if((*it).ipaddr.compare(host) == 0) {
insert = false;
break;
}
}
if(insert) {
assertSoft(!host.empty());
for(vector<host_t>::iterator it = hosts.begin(); it != hosts.end(); ++it) {
if((*it).ipaddr.compare(host) == 0) {
insert = false;
(*it).attempts += repeated;
if(loggedin) {
hosts.erase(it);
} else if((*it).attempts >= global.max_attempts) {
denyHost((*it).ipaddr);
hosts.erase(it);
banned.push_back(banned_t(time(0), host));
} else {
(*it).expires += (global.host_expire * 60);
}
break;
}
}
}
assertSoft((insert && repeated == 1) || !insert);
return (loggedin ? false : insert);
}
bool validateEntry(char *entry, char **address, unsigned int *length, bool *loggedin) {
char *sshd;
char *method;
char *user;
char *host;
char *end;
*loggedin = false;
sshd = strstr(entry, " sshd[");
if(sshd != 0) {
method = strstr(sshd, ": Failed ");
if(method != 0) {
method += sizeof(": Failed ");
user = strstr(method, " for ");
if(user == 0) {
return false;
}
user += sizeof(" for ");
host = strstr(user, " from ");
if(host == 0) {
return false;
}
host += sizeof(" from ") - sizeof('\0');
end = strstr(host, " port ");
if(end == 0) {
return false;
}
} else {
method = strstr(sshd, ": Invalid ");
if(method != 0) {
user = strstr(method, " user ");
if(user == 0) {
return false;
}
user += sizeof (" user ");
host = strstr(user, " from ");
if(host == 0) {
return false;
}
host += sizeof(" from ") - sizeof('\0');
end = strchr(host, '\0');
} else {
method = strstr(sshd, ": Address ");
if(method != 0) {
host = strdup(method);
host += sizeof(": Address ") - sizeof('\0');
user = strstr(host, " POSSIBLE BREAK-IN ATTEMPT");
if(user == 0) {
return false;
}
end = strstr(host, " maps to ");
if(end == 0) {
return false;
}
} else {
method = strstr(sshd, ": Accepted ");
if(method != 0) {
user = strstr(method, " for ");
if(user == 0) {
return false;
}
user += sizeof(" for ");
host = strstr(user, " from ");
if(host == 0) {
return false;
}
host += sizeof(" from ") - sizeof('\0');
end = strchr(host, '\0');
*loggedin = true;
} else {
return false;
}
}
}
}
} else {
return false;
}
*address = host;
*length = (end - host);
return true;
}
unsigned int validateRepeated(char *line) {
char *sshd;
char *times;
char *end;
sshd = strstr(line, " sshd[");
if(sshd == 0) {
return 0;
}
times = strstr(sshd, ": last message repeated ");
if(times == 0) {
return 0;
}
times += sizeof(": last message repeated ") - sizeof('\0');
end = strstr(times, " times");
if(end == 0) {
return 0;
}
return strtoul(times, 0, 10);
}