IRC bouncer
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.

dispatch.c 7.3KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. /* Copyright (C) 2019 C. McEnroe <june@causal.agency>
  2. *
  3. * This program is free software: you can redistribute it and/or modify
  4. * it under the terms of the GNU General Public License as published by
  5. * the Free Software Foundation, either version 3 of the License, or
  6. * (at your option) any later version.
  7. *
  8. * This program is distributed in the hope that it will be useful,
  9. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. * GNU General Public License for more details.
  12. *
  13. * You should have received a copy of the GNU General Public License
  14. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  15. */
  16. #include <err.h>
  17. #include <fcntl.h>
  18. #include <netdb.h>
  19. #include <netinet/in.h>
  20. #include <poll.h>
  21. #include <signal.h>
  22. #include <stdint.h>
  23. #include <stdio.h>
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <sys/socket.h>
  27. #include <sys/un.h>
  28. #include <sysexits.h>
  29. #include <unistd.h>
  30. #ifdef __FreeBSD__
  31. #include <sys/capsicum.h>
  32. #endif
  33. #include "compat.h"
  34. static struct {
  35. struct pollfd *ptr;
  36. size_t len, cap;
  37. } event;
  38. static void eventAdd(int fd) {
  39. if (event.len == event.cap) {
  40. event.cap = (event.cap ? event.cap * 2 : 8);
  41. event.ptr = realloc(event.ptr, sizeof(*event.ptr) * event.cap);
  42. if (!event.ptr) err(EX_OSERR, "malloc");
  43. }
  44. event.ptr[event.len++] = (struct pollfd) {
  45. .fd = fd,
  46. .events = POLLIN,
  47. };
  48. }
  49. static void eventRemove(size_t i) {
  50. close(event.ptr[i].fd);
  51. event.ptr[i] = event.ptr[--event.len];
  52. }
  53. static ssize_t sendfd(int sock, int fd) {
  54. size_t len = CMSG_SPACE(sizeof(int));
  55. char buf[len];
  56. char x = 0;
  57. struct iovec iov = { .iov_base = &x, .iov_len = 1 };
  58. struct msghdr msg = {
  59. .msg_iov = &iov,
  60. .msg_iovlen = 1,
  61. .msg_control = buf,
  62. .msg_controllen = len,
  63. };
  64. struct cmsghdr *cmsg = CMSG_FIRSTHDR(&msg);
  65. cmsg->cmsg_len = CMSG_LEN(sizeof(int));
  66. cmsg->cmsg_level = SOL_SOCKET;
  67. cmsg->cmsg_type = SCM_RIGHTS;
  68. *(int *)CMSG_DATA(cmsg) = fd;
  69. return sendmsg(sock, &msg, 0);
  70. }
  71. static struct {
  72. uint8_t buf[4096];
  73. uint8_t *ptr;
  74. size_t len;
  75. } peek;
  76. static void skip(size_t skip) {
  77. if (peek.len < skip) skip = peek.len;
  78. peek.ptr += skip;
  79. peek.len -= skip;
  80. }
  81. static uint8_t uint8(void) {
  82. if (peek.len < 1) return 0;
  83. peek.len--;
  84. return *peek.ptr++;
  85. }
  86. static uint16_t uint16(void) {
  87. uint16_t val = uint8();
  88. return val << 8 | uint8();
  89. }
  90. static char *serverName(void) {
  91. peek.ptr = peek.buf;
  92. // TLSPlaintext
  93. if (uint8() != 22) return NULL;
  94. skip(4);
  95. // Handshake
  96. if (uint8() != 1) return NULL;
  97. skip(3);
  98. // ClientHello
  99. skip(34);
  100. skip(uint8());
  101. skip(uint16());
  102. skip(uint8());
  103. peek.len = uint16();
  104. while (peek.len) {
  105. // Extension
  106. uint16_t type = uint16();
  107. uint16_t len = uint16();
  108. if (type != 0) {
  109. skip(len);
  110. continue;
  111. }
  112. // ServerNameList
  113. skip(2);
  114. // ServerName
  115. if (uint8() != 0) return NULL;
  116. // HostName
  117. len = uint16();
  118. char *name = (char *)peek.ptr;
  119. skip(len);
  120. *peek.ptr = '\0';
  121. return name;
  122. }
  123. return NULL;
  124. }
  125. static const uint8_t Alert[] = {
  126. 0x15, 0x03, 0x03, 0x00, 0x02, // TLSPlaintext
  127. 0x02, 0x70, // Alert fatal unrecognized_name
  128. };
  129. static void alert(int sock) {
  130. ssize_t len = send(sock, Alert, sizeof(Alert), 0);
  131. if (len < 0) warn("send");
  132. }
  133. int main(int argc, char *argv[]) {
  134. const char *host = "localhost";
  135. const char *port = "6697";
  136. const char *path = NULL;
  137. int timeout = 1000;
  138. int opt;
  139. while (0 < (opt = getopt(argc, argv, "H:P:t:"))) {
  140. switch (opt) {
  141. break; case 'H': host = optarg;
  142. break; case 'P': port = optarg;
  143. break; case 't': {
  144. char *rest;
  145. timeout = strtol(optarg, &rest, 0);
  146. if (*rest) errx(EX_USAGE, "invalid timeout: %s", optarg);
  147. }
  148. break; default: return EX_USAGE;
  149. }
  150. }
  151. if (optind < argc) {
  152. path = argv[optind];
  153. } else {
  154. errx(EX_USAGE, "directory required");
  155. }
  156. int dir = open(path, O_DIRECTORY);
  157. if (dir < 0) err(EX_NOINPUT, "%s", path);
  158. int error = fchdir(dir);
  159. if (error) err(EX_NOINPUT, "%s", path);
  160. struct addrinfo *head;
  161. struct addrinfo hints = {
  162. .ai_family = AF_UNSPEC,
  163. .ai_socktype = SOCK_STREAM,
  164. .ai_protocol = IPPROTO_TCP,
  165. };
  166. error = getaddrinfo(host, port, &hints, &head);
  167. if (error) errx(EX_NOHOST, "%s:%s: %s", host, port, gai_strerror(error));
  168. size_t binds = 0;
  169. for (struct addrinfo *ai = head; ai; ai = ai->ai_next) {
  170. int sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
  171. if (sock < 0) err(EX_OSERR, "socket");
  172. int yes = 1;
  173. error = setsockopt(
  174. sock, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)
  175. );
  176. if (error) err(EX_OSERR, "setsockopt");
  177. error = bind(sock, ai->ai_addr, ai->ai_addrlen);
  178. if (error) {
  179. warn("%s:%s", host, port);
  180. close(sock);
  181. continue;
  182. }
  183. eventAdd(sock);
  184. binds++;
  185. }
  186. if (!binds) errx(EX_UNAVAILABLE, "could not bind any sockets");
  187. freeaddrinfo(head);
  188. #ifdef __FreeBSD__
  189. error = cap_enter();
  190. if (error) err(EX_OSERR, "cap_enter");
  191. cap_rights_t dirRights, sockRights, unixRights, bindRights;
  192. cap_rights_init(&dirRights, CAP_CONNECTAT);
  193. cap_rights_init(&sockRights, CAP_EVENT, CAP_RECV, CAP_SEND, CAP_SETSOCKOPT);
  194. cap_rights_init(&unixRights, CAP_CONNECT, CAP_SEND);
  195. cap_rights_init(&bindRights, CAP_LISTEN, CAP_ACCEPT);
  196. cap_rights_merge(&bindRights, &sockRights);
  197. error = cap_rights_limit(dir, &dirRights);
  198. if (error) err(EX_OSERR, "cap_rights_limit");
  199. for (size_t i = 0; i < binds; ++i) {
  200. error = cap_rights_limit(event.ptr[i].fd, &bindRights);
  201. if (error) err(EX_OSERR, "cap_rights_limit");
  202. }
  203. #endif
  204. for (size_t i = 0; i < binds; ++i) {
  205. error = listen(event.ptr[i].fd, 1);
  206. if (error) err(EX_IOERR, "listen");
  207. }
  208. signal(SIGPIPE, SIG_IGN);
  209. for (;;) {
  210. int nfds = poll(
  211. event.ptr, event.len, (event.len > binds ? timeout : -1)
  212. );
  213. if (nfds < 0) err(EX_IOERR, "poll");
  214. if (!nfds) {
  215. for (size_t i = event.len - 1; i >= binds; --i) {
  216. eventRemove(i);
  217. }
  218. continue;
  219. }
  220. for (size_t i = event.len - 1; i < event.len; --i) {
  221. if (!event.ptr[i].revents) continue;
  222. if (i < binds) {
  223. int sock = accept(event.ptr[i].fd, NULL, NULL);
  224. if (sock < 0) err(EX_IOERR, "accept");
  225. int yes = 1;
  226. error = setsockopt(
  227. sock, SOL_SOCKET, SO_NOSIGPIPE, &yes, sizeof(yes)
  228. );
  229. if (error) err(EX_OSERR, "setsockopt");
  230. eventAdd(sock);
  231. continue;
  232. }
  233. if (event.ptr[i].revents & (POLLHUP | POLLERR)) {
  234. eventRemove(i);
  235. continue;
  236. }
  237. ssize_t len = recv(
  238. event.ptr[i].fd, peek.buf, sizeof(peek.buf) - 1, MSG_PEEK
  239. );
  240. if (len < 0) {
  241. warn("recv");
  242. eventRemove(i);
  243. continue;
  244. }
  245. peek.len = len;
  246. char *name = serverName();
  247. if (!name || name[0] == '.' || name[0] == '/') {
  248. alert(event.ptr[i].fd);
  249. eventRemove(i);
  250. continue;
  251. }
  252. struct sockaddr_un addr = { .sun_family = AF_UNIX };
  253. strncpy(addr.sun_path, name, sizeof(addr.sun_path));
  254. int sock = socket(PF_UNIX, SOCK_STREAM, 0);
  255. if (sock < 0) err(EX_OSERR, "socket");
  256. #ifdef __FreeBSD__
  257. error = cap_rights_limit(sock, &unixRights);
  258. if (error) err(EX_OSERR, "cap_rights_limit");
  259. error = connectat(
  260. dir, sock, (struct sockaddr *)&addr, SUN_LEN(&addr)
  261. );
  262. #else
  263. error = connect(sock, (struct sockaddr *)&addr, SUN_LEN(&addr));
  264. #endif
  265. if (error) {
  266. warn("%s", name);
  267. alert(event.ptr[i].fd);
  268. } else {
  269. len = sendfd(sock, event.ptr[i].fd);
  270. if (len < 0) {
  271. warn("%s", name);
  272. alert(event.ptr[i].fd);
  273. }
  274. }
  275. close(sock);
  276. eventRemove(i);
  277. }
  278. }
  279. }