ssh-sentry.c 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450
  1. /*
  2. * Copyright (c) 2020 Markus Hennecke <markus-hennecke@markus-hennecke.de>
  3. *
  4. * Permission to use, copy, modify, and distribute this software for any
  5. * purpose with or without fee is hereby granted, provided that the above
  6. * copyright notice and this permission notice appear in all copies.
  7. *
  8. * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  9. * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  10. * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
  11. * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  12. * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  13. * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
  14. * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  15. */
  16. #include <sys/types.h>
  17. #include <sys/queue.h>
  18. #include <sys/uio.h>
  19. #include <sys/event.h>
  20. #include <sys/time.h>
  21. #include <sys/socket.h>
  22. #include <sys/ioctl.h>
  23. #include <sys/fcntl.h>
  24. #include <sys/param.h>
  25. #include <sys/wait.h>
  26. #include <arpa/inet.h>
  27. #include <net/if.h>
  28. #include <net/pfvar.h>
  29. #include <err.h>
  30. #include <errno.h>
  31. #include <poll.h>
  32. #include <regex.h>
  33. #include <signal.h>
  34. #include <stdarg.h>
  35. #include <stdint.h>
  36. #include <stdio.h>
  37. #include <stdlib.h>
  38. #include <string.h>
  39. #include <strings.h>
  40. #include <syslog.h>
  41. #include <unistd.h>
  42. #include <imsg.h>
  43. #include "log.h"
  44. int run = 1;
  45. char *blacklist_table = "blacklist";
  46. regex_t rx_failed_invalid_user;
  47. /*
  48. * Simplified regex match for IPv6 addresses, the code in send_add_address()
  49. * will check for a valid IPv4/IPv6 address anyway so keep the regex
  50. * simple.
  51. */
  52. #define RX_FAILED_INVALID_USER "Failed password for " \
  53. "(invalid user [^ ]+|root) from " \
  54. "(" \
  55. "([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})" \
  56. "|" \
  57. "([0-9A-Fa-f][0-9A-Fa-f:]+)" \
  58. ")" \
  59. " port [0-9]+ ssh"
  60. #define AUTHLOG_FILENAME "/var/log/authlog"
  61. #define MAXLINELEN 1024
  62. enum imsg_type {
  63. IMSG_ADD_ADDRESS,
  64. };
  65. typedef struct __attribute__((packed)) {
  66. union {
  67. struct in_addr addr4;
  68. struct in6_addr addr6;
  69. } addr;
  70. sa_family_t af;
  71. } add_address_msg_t;
  72. static int add_address(int, add_address_msg_t *, const char *);
  73. static char *rx_error_string(int, regex_t *);
  74. static void sighandler(int);
  75. static void usage(void);
  76. static void handle_authlog(struct imsgbuf *, FILE *);
  77. static void dispatch_imsg(int dev, struct imsgbuf *);
  78. static int pfdev_main(struct imsgbuf *);
  79. int send_add_address(struct imsgbuf *, const char *);
  80. __dead void
  81. usage(void)
  82. {
  83. fprintf(stderr, "usage: %s [-dv]\n", getprogname());
  84. exit(1);
  85. }
  86. char *
  87. rx_error_string(int rc, regex_t *rx)
  88. {
  89. static char error[1024];
  90. regerror(rc, rx, error, sizeof(error));
  91. return error;
  92. }
  93. void
  94. sighandler(int sig)
  95. {
  96. switch (sig) {
  97. case SIGTERM:
  98. case SIGINT:
  99. run = 0;
  100. break;
  101. case SIGCHLD:
  102. wait(NULL);
  103. run = 0;
  104. break;
  105. default:
  106. break;
  107. }
  108. }
  109. void
  110. handle_authlog(struct imsgbuf *ibuf, FILE *authlog)
  111. {
  112. char line[MAXLINELEN];
  113. while ((fgets(line, MAXLINELEN, authlog)) != NULL) {
  114. regmatch_t match[3];
  115. int rc = regexec(&rx_failed_invalid_user, line, 3, match, 0);
  116. switch (rc) {
  117. case REG_NOMATCH:
  118. log_debug("nomatch, line: %s", line);
  119. break;
  120. case 0: {
  121. const regmatch_t *m = &match[2];
  122. const size_t len = m->rm_eo - m->rm_so;
  123. char *ip = strndup(line + m->rm_so, len);
  124. send_add_address(ibuf, ip);
  125. free(ip);
  126. }
  127. break;
  128. default:
  129. log_warn("regexec: %s",
  130. rx_error_string(rc, &rx_failed_invalid_user));
  131. break;
  132. }
  133. }
  134. if (feof(authlog))
  135. clearerr(authlog);
  136. else if (ferror(authlog))
  137. fatal(NULL);
  138. }
  139. int
  140. send_add_address(struct imsgbuf *ibuf, const char *ip)
  141. {
  142. add_address_msg_t msg;
  143. int n, rc;
  144. bzero(&msg, sizeof(msg));
  145. if ((rc = inet_pton(AF_INET, ip, &msg.addr)) < 1) {
  146. if (-1 == rc) {
  147. log_warn("inet_pton");
  148. return -1;
  149. }
  150. if ((rc = inet_pton(AF_INET6, ip, &msg.addr)) == -1) {
  151. log_warn("inet_pton");
  152. return -1;
  153. }
  154. if (0 == rc)
  155. return 0;
  156. msg.af = AF_INET6;
  157. } else {
  158. msg.af = AF_INET;
  159. }
  160. imsg_compose(ibuf, IMSG_ADD_ADDRESS, 0, 0, -1, &msg, sizeof(msg));
  161. send:
  162. if ((n = msgbuf_write(&ibuf->w)) == -1 && errno != EAGAIN)
  163. fatal("msgbuf_write");
  164. if (-1 == n && errno == EAGAIN) {
  165. log_warnx("msgbuf_write, EAGAIN");
  166. goto send;
  167. }
  168. if (n == 0)
  169. fatal("msgbuf_write, connection closed");
  170. return 1;
  171. }
  172. int
  173. add_address(int dev, add_address_msg_t *msg, const char *tablename)
  174. {
  175. struct pfr_addr addr;
  176. struct pfioc_table io;
  177. struct pfr_table table;
  178. bzero(&addr, sizeof(addr));
  179. memcpy(&addr.pfra_u, &msg->addr, sizeof(addr.pfra_u));
  180. addr.pfra_af = msg->af;
  181. addr.pfra_net = (msg->af == AF_INET) ? 32 : 128;
  182. bzero(&table, sizeof(struct pfr_table));
  183. strlcpy(table.pfrt_name, tablename, PF_TABLE_NAME_SIZE);
  184. bzero(&io, sizeof(struct pfioc_table));
  185. io.pfrio_flags = PFR_FLAG_FEEDBACK;
  186. io.pfrio_table = table;
  187. io.pfrio_buffer = &addr;
  188. io.pfrio_esize = sizeof(struct pfr_addr);
  189. io.pfrio_size = 1;
  190. if (ioctl(dev, DIOCRADDADDRS, &io) == -1) {
  191. log_warn("DIOCRADDADDRS");
  192. return -1;
  193. }
  194. return io.pfrio_nadd;
  195. }
  196. int
  197. pfdev_main(struct imsgbuf *ibuf)
  198. {
  199. int nready;
  200. int dev;
  201. struct pollfd pfd[1];
  202. dev = open("/dev/pf", O_RDWR);
  203. if (-1 == dev)
  204. fatal("open /dev/pf failed");
  205. if (unveil(NULL, NULL) == -1)
  206. fatal("unveil");
  207. pfd[0].fd = ibuf->fd;
  208. pfd[0].events = POLLIN;
  209. while (run && ((nready = poll(pfd, 1, INFTIM)) != -1)) {
  210. if (nready == 0)
  211. fatal("time out");
  212. if ((pfd[0].revents & (POLLERR|POLLNVAL)))
  213. fatal("bad fd %d", pfd[0].fd);
  214. if ((pfd[0].revents & (POLLIN|POLLHUP)))
  215. dispatch_imsg(dev, ibuf);
  216. }
  217. close(dev);
  218. return 0;
  219. }
  220. void
  221. dispatch_imsg(int dev, struct imsgbuf *ibuf)
  222. {
  223. struct imsg imsg;
  224. ssize_t n, datalen;
  225. int numadd;
  226. add_address_msg_t msg;
  227. again:
  228. if (((n = imsg_read(ibuf)) == -1) && errno != EAGAIN)
  229. fatal("imsg_read");
  230. if (n == 0 && errno == EAGAIN)
  231. goto again;
  232. if (n == 0)
  233. return;
  234. while (run) {
  235. if ((n = imsg_get(ibuf, &imsg)) == -1)
  236. fatal("imsg_get");
  237. if (n == 0)
  238. break;
  239. datalen = imsg.hdr.len - IMSG_HEADER_SIZE;
  240. switch (imsg.hdr.type) {
  241. case IMSG_ADD_ADDRESS:
  242. if (datalen < (ssize_t )sizeof(msg)) {
  243. fatal("corrupt imsg");
  244. }
  245. memcpy(&msg, imsg.data, sizeof(msg));
  246. numadd = add_address(dev, &msg, blacklist_table);
  247. if (numadd > 0) {
  248. char p[INET6_ADDRSTRLEN];
  249. inet_ntop(msg.af, &msg.addr, p, sizeof(p));
  250. log_info("Added %s to <%s>", p,
  251. blacklist_table);
  252. }
  253. break;
  254. default:
  255. log_warn("Unknown imsg received");
  256. break;
  257. }
  258. imsg_free(&imsg);
  259. }
  260. }
  261. int
  262. main(int argc, char **argv)
  263. {
  264. int queue, nev;
  265. int ch, rc;
  266. struct kevent ev[2];
  267. int debug = 0, verbose = 0;
  268. FILE *authlog = NULL;
  269. struct imsgbuf parent_ibuf, child_ibuf;
  270. int imsg_fds[2];
  271. int logdest = LOG_TO_STDERR;
  272. if (geteuid() != 0)
  273. errx(1, "Need root privileges to run.");
  274. while ((ch = getopt(argc, argv, "dv")) != -1) {
  275. switch (ch) {
  276. case 'd':
  277. debug = 2;
  278. break;
  279. case 'v':
  280. verbose++;
  281. break;
  282. default:
  283. usage();
  284. /* NOT REACHED */
  285. }
  286. }
  287. argc -= optind;
  288. argv += optind;
  289. if (argc != 0)
  290. usage();
  291. setprogname("ssh-sentry");
  292. if (!debug)
  293. logdest |= LOG_TO_SYSLOG;
  294. log_init(logdest, verbose, LOG_DAEMON);
  295. rc = regcomp(&rx_failed_invalid_user, RX_FAILED_INVALID_USER,
  296. REG_EXTENDED);
  297. if (rc != 0)
  298. errx(1, "%s", rx_error_string(rc, &rx_failed_invalid_user));
  299. logdest = debug ? LOG_TO_STDERR : LOG_TO_SYSLOG;
  300. log_init(logdest, verbose, LOG_DAEMON);
  301. log_setverbose(verbose);
  302. if (!debug) {
  303. if (daemon(0, 0) == -1)
  304. fatal("daemon");
  305. }
  306. if ((queue = kqueue()) == -1)
  307. fatal("kqueue");
  308. if (socketpair(AF_UNIX, SOCK_STREAM, PF_UNSPEC, imsg_fds) == -1)
  309. fatal("socketpair");
  310. switch (fork()) {
  311. case -1:
  312. fatal("fork");
  313. case 0:
  314. /* child */
  315. signal(SIGTERM, sighandler);
  316. signal(SIGINT, sighandler);
  317. close(imsg_fds[0]);
  318. imsg_init(&child_ibuf, imsg_fds[1]);
  319. exit(pfdev_main(&child_ibuf));
  320. }
  321. close(imsg_fds[1]);
  322. imsg_init(&parent_ibuf, imsg_fds[0]);
  323. if (unveil(AUTHLOG_FILENAME, "r") == -1)
  324. fatal("unveil");
  325. if (unveil(NULL, NULL) == -1)
  326. fatal("unveil");
  327. signal(SIGTERM, sighandler);
  328. signal(SIGINT, sighandler);
  329. signal(SIGCHLD, sighandler);
  330. if (pledge("stdio rpath", "") == -1)
  331. fatal("pledge");
  332. if ((authlog = fopen(AUTHLOG_FILENAME, "r")) == NULL)
  333. fatal("%s", AUTHLOG_FILENAME);
  334. if (fseek(authlog, 0L, SEEK_END) < 0)
  335. fatal("fseek");
  336. reopen:
  337. while (NULL == authlog) {
  338. sleep(1);
  339. authlog = fopen(AUTHLOG_FILENAME, "r");
  340. }
  341. add_events:
  342. EV_SET(&ev[0], fileno(authlog), EVFILT_READ,
  343. EV_ENABLE | EV_ADD | EV_CLEAR, 0, 0, NULL);
  344. EV_SET(&ev[1], fileno(authlog), EVFILT_VNODE,
  345. EV_ENABLE | EV_ADD | EV_CLEAR,
  346. NOTE_DELETE | NOTE_RENAME, 0, NULL);
  347. if (kevent(queue, ev, 2, NULL, 0, NULL))
  348. fatal(NULL);
  349. log_info("Starting event loop...");
  350. struct kevent ke;
  351. while (run) {
  352. nev = kevent(queue, NULL, 0, &ke, 1, NULL);
  353. if (nev == -1) {
  354. if (errno != EINTR) {
  355. log_warn("kevent");
  356. break;
  357. }
  358. }
  359. if (nev > 0) {
  360. if (ke.filter == EVFILT_READ) {
  361. handle_authlog(&parent_ibuf, authlog);
  362. } else if (ke.filter == EVFILT_VNODE) {
  363. if (ke.fflags & NOTE_RENAME) {
  364. log_info("%s renamed...",
  365. AUTHLOG_FILENAME);
  366. authlog = freopen(AUTHLOG_FILENAME,
  367. "r", authlog);
  368. if (authlog == NULL)
  369. goto reopen;
  370. goto add_events;
  371. } else if (ke.fflags & NOTE_DELETE) {
  372. log_info("%s deleted...",
  373. AUTHLOG_FILENAME);
  374. authlog = freopen(AUTHLOG_FILENAME,
  375. "r", authlog);
  376. if (authlog == NULL)
  377. goto reopen;
  378. goto add_events;
  379. }
  380. }
  381. }
  382. }
  383. close(queue);
  384. regfree(&rx_failed_invalid_user);
  385. return 0;
  386. }