You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

476 lines
11KB

  1. /**
  2. * @PROJECT ForbidHosts
  3. * @COPYRIGHT See COPYING in the top level directory
  4. * @FILE forbidhosts.cpp
  5. * @PURPOSE Tool for checking IPv4 & IPv6 failed connections
  6. * @DEVELOPERS Pierre Schweitzer <pierre@reactos.org>
  7. * Rafal Kupiec <belliash@asiotec.eu.org>
  8. */
  9. #include <sys/stat.h>
  10. #include <sys/inotify.h>
  11. #include <poll.h>
  12. #include <errno.h>
  13. #include <fcntl.h>
  14. #include <syslog.h>
  15. #include <unistd.h>
  16. #include <string>
  17. #include <vector>
  18. #include <cstring>
  19. #include <algorithm>
  20. #include <cstdio>
  21. #include <cstdarg>
  22. #include <csignal>
  23. #include "forbidhosts.h"
  24. using namespace std;
  25. void assertException(const char *file, unsigned int line, const char *assert, bool critical) {
  26. syslog(LOG_ERR, "Assertion '%s' failed at line %d in file %s", assert, line, file);
  27. if(critical) {
  28. shutdown(EXIT_FAILURE);
  29. }
  30. }
  31. void assignGlobalVars() {
  32. global.max_attempts = MAXATTEMPTS;
  33. global.host_expire = HOSTEXPIRE;
  34. global.failure_penalty = FAILUREPENALTY;
  35. global.deny_file = DENYFILE;
  36. global.log_file = LOGFILE;
  37. global.pid_file = PIDFILE;
  38. global.protocol = "sshd";
  39. }
  40. bool compare(const host_t &lhs, const host_t &rhs) {
  41. return (lhs.expires > rhs.expires);
  42. }
  43. void daemonize(string workdir) {
  44. int pidfile;
  45. char pid[10];
  46. struct sigaction sigact;
  47. pid_t daemon;
  48. if(getppid() == 1) {
  49. return;
  50. }
  51. setlogmask(LOG_MASK(LOG_INFO) | LOG_MASK(LOG_ERR) | LOG_MASK(LOG_CRIT));
  52. openlog("ForbidHosts", LOG_CONS, LOG_USER);
  53. syslog(LOG_INFO, "Starting up daemon.");
  54. daemon = fork();
  55. if(daemon < 0) {
  56. syslog(LOG_CRIT, "Unable to execute fork().");
  57. shutdown(EXIT_FAILURE);
  58. }
  59. if(daemon > 0) {
  60. syslog(LOG_INFO, "Child process created, working in background. Parent exiting.");
  61. exit(EXIT_SUCCESS);
  62. }
  63. sigact.sa_handler = &signalHandler;
  64. sigact.sa_flags = SA_RESTART;
  65. sigfillset(&sigact.sa_mask);
  66. sigaction(SIGABRT, &sigact, NULL);
  67. sigaction(SIGHUP, &sigact, NULL);
  68. sigaction(SIGINT, &sigact, NULL);
  69. sigaction(SIGKILL, &sigact, NULL);
  70. sigaction(SIGQUIT, &sigact, NULL);
  71. sigaction(SIGTERM, &sigact, NULL);
  72. sigaction(SIGUSR1, &sigact, NULL);
  73. sigaction(SIGUSR2, &sigact, NULL);
  74. umask(0);
  75. if(setsid() < 0) {
  76. shutdown(EXIT_FAILURE);
  77. }
  78. if((chdir(workdir.c_str())) < 0) {
  79. syslog(LOG_CRIT, "Unable to change working directory to '%s'.", workdir.c_str());
  80. shutdown(EXIT_FAILURE);
  81. }
  82. close(STDIN_FILENO);
  83. close(STDOUT_FILENO);
  84. close(STDERR_FILENO);
  85. pidfile = open(global.pid_file.c_str(), O_RDWR | O_CREAT, 0600);
  86. if(pidfile == -1) {
  87. syslog(LOG_CRIT, "Unable to open '%s' PID lock file.", global.pid_file.c_str());
  88. shutdown(EXIT_FAILURE);
  89. }
  90. if(lockf(pidfile, F_TLOCK,0) == -1) {
  91. syslog(LOG_CRIT, "Unable to lock '%s' PID lock file.", global.pid_file.c_str());
  92. shutdown(EXIT_FAILURE);
  93. }
  94. sprintf(pid, "%d\n", getpid());
  95. assertSoft(write(pidfile, pid, strlen(pid)) == (ssize_t) strlen(pid));
  96. close(pidfile);
  97. syslog(LOG_INFO, "Daemon is UP & RUNNING.");
  98. }
  99. void debug(const char *msg, ...) {
  100. int fp;
  101. char *log = NULL;
  102. va_list args;
  103. if(global.debug.length()) {
  104. if((fp = open(global.debug.c_str(), O_WRONLY | O_APPEND))) {
  105. va_start(args, msg);
  106. sprintf(log, msg, args);
  107. va_end(args);
  108. assertSoft(write(fp, log, strlen(log)) == (ssize_t) strlen(log));
  109. close(fp);
  110. sync();
  111. }
  112. }
  113. }
  114. void denyHost(string host) {
  115. int colon;
  116. int deny;
  117. string entry;
  118. deny = open(global.deny_file.c_str(), O_WRONLY | O_APPEND);
  119. if(deny < 0) {
  120. debug("Banning host '%s' in file %s failed for unknown reason.", host.c_str(), global.deny_file.c_str());
  121. syslog(LOG_ERR, "Banning host '%s' in file %s failed for unknown reason.", host.c_str(), global.deny_file.c_str());
  122. shutdown(EXIT_FAILURE);
  123. }
  124. colon = host.find_first_of(':');
  125. if(colon != -1) {
  126. entry = global.protocol + ": [" + host + "]\n";
  127. } else {
  128. entry = global.protocol + ": " + host + "\n";
  129. }
  130. assertSoft(write(deny, entry.c_str(), entry.length()) == (ssize_t) entry.length());
  131. close(deny);
  132. sync();
  133. debug("Banned host '%s' for excessive abusive.", host.c_str());
  134. syslog(LOG_INFO, "Banned host '%s' for excessive abusive.", host.c_str());
  135. }
  136. int main(int argc, char *argv[]) {
  137. int i_auth;
  138. int i_notify;
  139. int next_opt;
  140. int val;
  141. vector<banned_t> banned;
  142. vector<host_t> hosts;
  143. assignGlobalVars();
  144. while((next_opt = getopt(argc, argv, "t:p:n:l:e:d:D:av")) != -1) {
  145. switch(next_opt) {
  146. case 'a':
  147. global.protocol = "ALL";
  148. break;
  149. case 'd':
  150. global.deny_file = strdup(optarg);
  151. break;
  152. case 'D':
  153. global.debug = strdup(optarg);
  154. break;
  155. case 'e':
  156. val = sscanf(optarg, "%u", &global.host_expire);
  157. if(val > 0) {
  158. global.host_expire = global.host_expire > 0 ? global.host_expire : HOSTEXPIRE;
  159. } else {
  160. global.host_expire = HOSTEXPIRE;
  161. }
  162. break;
  163. case 'l':
  164. global.log_file = strdup(optarg);
  165. break;
  166. case 'n':
  167. global.banned_names = strdup(optarg);
  168. break;
  169. case 'p':
  170. global.pid_file = strdup(optarg);
  171. break;
  172. case 't':
  173. val = sscanf(optarg, "%u", &global.max_attempts);
  174. if(val > 0) {
  175. global.max_attempts = global.max_attempts > 0 ? global.max_attempts : MAXATTEMPTS;
  176. } else {
  177. global.max_attempts = MAXATTEMPTS;
  178. }
  179. break;
  180. case 'v':
  181. printVersion();
  182. break;
  183. }
  184. }
  185. daemonize("/tmp");
  186. logfile = open(global.log_file.c_str(), O_RDONLY | O_NONBLOCK);
  187. if(logfile < 0) {
  188. syslog(LOG_CRIT, "Unable to open '%s' log file for reading.", global.log_file.c_str());
  189. shutdown(EXIT_FAILURE);
  190. }
  191. lseek(logfile, 0, SEEK_END);
  192. i_notify = inotify_init1(IN_NONBLOCK);
  193. if(i_notify < 0) {
  194. close(logfile);
  195. syslog(LOG_CRIT, "Unable to initialize iNotify subsystem.");
  196. shutdown(EXIT_FAILURE);
  197. }
  198. i_auth = inotify_add_watch(i_notify, global.log_file.c_str(), IN_MODIFY);
  199. if(i_auth < 0) {
  200. close(i_notify);
  201. close(logfile);
  202. syslog(LOG_CRIT, "Unable to attach a watcher on a log file.");
  203. shutdown(EXIT_FAILURE);
  204. }
  205. for(;;) {
  206. struct pollfd fds[] = {i_notify, POLLIN, 0};
  207. int timeout = -1;
  208. if(!hosts.empty()) {
  209. timeout = hosts.back().expires - time(0) * 1000;
  210. }
  211. int event = poll(fds, 1, timeout);
  212. if(event > 0) {
  213. struct inotify_event i_event;
  214. assertSoft(read(i_notify, &i_event, sizeof(struct inotify_event)) == sizeof(struct inotify_event));
  215. }
  216. while(!readLine(logfile, hosts, banned));
  217. while(!banned.empty()) {
  218. if(banned.back().expires > time(0)) {
  219. break;
  220. }
  221. banned.pop_back();
  222. }
  223. while(!hosts.empty()) {
  224. if(hosts.back().expires > time(0)) {
  225. break;
  226. }
  227. hosts.pop_back();
  228. }
  229. sleep(1);
  230. }
  231. close(i_auth);
  232. close(i_notify);
  233. close(logfile);
  234. }
  235. void printVersion() {
  236. printf("ForbidHosts Daemon v%s\n\n", FHVERSION);
  237. exit(EXIT_SUCCESS);
  238. }
  239. unsigned int readLine(int file, vector<host_t> &hosts, vector<banned_t> &banned) {
  240. char line[255];
  241. char *address;
  242. string host;
  243. bool loggedin = false;
  244. static string last_addr = "";
  245. unsigned int length;
  246. unsigned int addr_length;
  247. unsigned int red = 0;
  248. unsigned int repeated = 1;
  249. for(;;) {
  250. red = 0;
  251. while(red < sizeof(line) / sizeof(char)) {
  252. length = read(file, &line[red], sizeof(char));
  253. if(length < 1) {
  254. if(red == 0) {
  255. return length;
  256. } else {
  257. break;
  258. }
  259. }
  260. if(line[red] == '\n') {
  261. line[red] = '\0';
  262. break;
  263. }
  264. red++;
  265. }
  266. if(!validateEntry(line, &address, &addr_length, &loggedin)) {
  267. if(!last_addr.empty()) {
  268. repeated = validateRepeated(line);
  269. if(repeated == 0) {
  270. last_addr = "";
  271. return length;
  272. }
  273. } else {
  274. last_addr = "";
  275. return length;
  276. }
  277. } else {
  278. host = address;
  279. host.erase(addr_length);
  280. last_addr = host;
  281. }
  282. if(updateHost(last_addr, hosts, banned, repeated, loggedin)) {
  283. hosts.push_back(host_t(time(0), host));
  284. }
  285. sort(hosts.begin(), hosts.end(), compare);
  286. }
  287. }
  288. void shutdown(int code) {
  289. if(code != 0) {
  290. syslog(LOG_CRIT, "Emergency daemon shutdown due to errors.");
  291. } else {
  292. syslog(LOG_INFO, "Shutting down daemon.");
  293. }
  294. unlink(global.pid_file.c_str());
  295. exit(code);
  296. }
  297. void signalHandler(int signal) {
  298. switch(signal) {
  299. case SIGHUP:
  300. syslog(LOG_WARNING, "Received SIGHUP signal!");
  301. break;
  302. case SIGABRT:
  303. case SIGINT:
  304. case SIGQUIT:
  305. case SIGTERM:
  306. shutdown(EXIT_SUCCESS);
  307. break;
  308. case SIGUSR1:
  309. case SIGUSR2:
  310. syslog(LOG_INFO, "Received SIGUSR1 signal - probably logs rotated.");
  311. syslog(LOG_INFO, "Reloading system log file.");
  312. lseek(logfile, 0, SEEK_END);
  313. break;
  314. default:
  315. syslog(LOG_WARNING, "Unhandled signal caught: %s.", strsignal(signal));
  316. return;
  317. }
  318. }
  319. bool updateHost(const string &host, vector<host_t> &hosts, vector<banned_t> &banned, unsigned int repeated, bool loggedin) {
  320. bool insert = true;
  321. assertSoft(!banned.empty());
  322. for(vector<banned_t>::iterator it = banned.begin(); it != banned.end(); ++it) {
  323. if((*it).ipaddr.compare(host) == 0) {
  324. insert = false;
  325. break;
  326. }
  327. }
  328. if(insert) {
  329. assertSoft(!host.empty());
  330. for(vector<host_t>::iterator it = hosts.begin(); it != hosts.end(); ++it) {
  331. if((*it).ipaddr.compare(host) == 0) {
  332. insert = false;
  333. (*it).attempts += repeated;
  334. if(loggedin) {
  335. hosts.erase(it);
  336. } else if((*it).attempts >= global.max_attempts) {
  337. denyHost((*it).ipaddr);
  338. hosts.erase(it);
  339. banned.push_back(banned_t(time(0), host));
  340. } else {
  341. (*it).expires += (global.host_expire * 60);
  342. }
  343. break;
  344. }
  345. }
  346. }
  347. assertSoft((insert && repeated == 1) || !insert);
  348. return (loggedin ? false : insert);
  349. }
  350. bool validateEntry(char *entry, char **address, unsigned int *length, bool *loggedin) {
  351. char *sshd;
  352. char *method;
  353. char *user;
  354. char *host;
  355. char *end;
  356. *loggedin = false;
  357. sshd = strstr(entry, " sshd[");
  358. if(sshd != 0) {
  359. method = strstr(sshd, ": Failed ");
  360. if(method != 0) {
  361. method += sizeof(": Failed ");
  362. user = strstr(method, " for ");
  363. if(user == 0) {
  364. return false;
  365. }
  366. user += sizeof(" for ");
  367. host = strstr(user, " from ");
  368. if(host == 0) {
  369. return false;
  370. }
  371. host += sizeof(" from ") - sizeof('\0');
  372. end = strstr(host, " port ");
  373. if(end == 0) {
  374. return false;
  375. }
  376. } else {
  377. method = strstr(sshd, ": Invalid ");
  378. if(method != 0) {
  379. user = strstr(method, " user ");
  380. if(user == 0) {
  381. return false;
  382. }
  383. user += sizeof (" user ");
  384. host = strstr(user, " from ");
  385. if(host == 0) {
  386. return false;
  387. }
  388. host += sizeof(" from ") - sizeof('\0');
  389. end = strchr(host, '\0');
  390. } else {
  391. method = strstr(sshd, ": Address ");
  392. if(method != 0) {
  393. host = strdup(method);
  394. host += sizeof(": Address ") - sizeof('\0');
  395. user = strstr(host, " POSSIBLE BREAK-IN ATTEMPT");
  396. if(user == 0) {
  397. return false;
  398. }
  399. end = strstr(host, " maps to ");
  400. if(end == 0) {
  401. return false;
  402. }
  403. } else {
  404. method = strstr(sshd, ": Accepted ");
  405. if(method != 0) {
  406. user = strstr(method, " for ");
  407. if(user == 0) {
  408. return false;
  409. }
  410. user += sizeof(" for ");
  411. host = strstr(user, " from ");
  412. if(host == 0) {
  413. return false;
  414. }
  415. host += sizeof(" from ") - sizeof('\0');
  416. end = strchr(host, '\0');
  417. *loggedin = true;
  418. } else {
  419. return false;
  420. }
  421. }
  422. }
  423. }
  424. } else {
  425. return false;
  426. }
  427. *address = host;
  428. *length = (end - host);
  429. return true;
  430. }
  431. unsigned int validateRepeated(char *line) {
  432. char *sshd;
  433. char *times;
  434. char *end;
  435. sshd = strstr(line, " sshd[");
  436. if(sshd == 0) {
  437. return 0;
  438. }
  439. times = strstr(sshd, ": last message repeated ");
  440. if(times == 0) {
  441. return 0;
  442. }
  443. times += sizeof(": last message repeated ") - sizeof('\0');
  444. end = strstr(times, " times");
  445. if(end == 0) {
  446. return 0;
  447. }
  448. return strtoul(times, 0, 10);
  449. }