/** * @PROJECT ForbidHosts * @COPYRIGHT See COPYING in the top level directory * @FILE forbidhosts.cpp * @PURPOSE Tool for checking IPv4 & IPv6 failed connections * @DEVELOPERS Pierre Schweitzer * Rafal Kupiec */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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; vector 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 &hosts, vector &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 &hosts, vector &banned, unsigned int repeated, bool loggedin) { bool insert = true; assertSoft(!banned.empty()); for(vector::iterator it = banned.begin(); it != banned.end(); ++it) { if((*it).ipaddr.compare(host) == 0) { insert = false; break; } } if(insert) { assertSoft(!host.empty()); for(vector::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); }