Another copy of my dotfiles. Because I don't completely trust GitHub.
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.

364 lines
8.5 KiB

4 years ago
  1. /* See LICENSE file for copyright and license details. */
  2. #include <errno.h>
  3. #include <grp.h>
  4. #include <limits.h>
  5. #include <pwd.h>
  6. #include <regex.h>
  7. #include <signal.h>
  8. #include <stddef.h>
  9. #include <stdlib.h>
  10. #include <string.h>
  11. #include <sys/resource.h>
  12. #include <sys/time.h>
  13. #include <sys/types.h>
  14. #include <sys/wait.h>
  15. #include <unistd.h>
  16. #include "arg.h"
  17. #include "server.h"
  18. #include "sock.h"
  19. #include "util.h"
  20. static char *udsname;
  21. static void
  22. cleanup(void)
  23. {
  24. if (udsname) {
  25. sock_rem_uds(udsname);
  26. }
  27. }
  28. static void
  29. sigcleanup(int sig)
  30. {
  31. cleanup();
  32. kill(0, sig);
  33. _exit(1);
  34. }
  35. static void
  36. handlesignals(void(*hdl)(int))
  37. {
  38. struct sigaction sa = {
  39. .sa_handler = hdl,
  40. };
  41. sigemptyset(&sa.sa_mask);
  42. sigaction(SIGTERM, &sa, NULL);
  43. sigaction(SIGHUP, &sa, NULL);
  44. sigaction(SIGINT, &sa, NULL);
  45. sigaction(SIGQUIT, &sa, NULL);
  46. }
  47. static void
  48. usage(void)
  49. {
  50. const char *opts = "[-u user] [-g group] [-n num] [-d dir] [-l] "
  51. "[-i file] [-v vhost] ... [-m map] ...";
  52. die("usage: %s -p port [-h host] %s\n"
  53. " %s -U file [-p port] %s", argv0,
  54. opts, argv0, opts);
  55. }
  56. int
  57. main(int argc, char *argv[])
  58. {
  59. struct group *grp = NULL;
  60. struct passwd *pwd = NULL;
  61. struct rlimit rlim;
  62. struct server srv = {
  63. .docindex = "index.html",
  64. };
  65. size_t i;
  66. int insock, status = 0;
  67. const char *err;
  68. char *tok[4];
  69. /* defaults */
  70. size_t nthreads = 4;
  71. size_t nslots = 64;
  72. char *servedir = ".";
  73. char *user = "nobody";
  74. char *group = "nobody";
  75. ARGBEGIN {
  76. case 'd':
  77. servedir = EARGF(usage());
  78. break;
  79. case 'g':
  80. group = EARGF(usage());
  81. break;
  82. case 'h':
  83. srv.host = EARGF(usage());
  84. break;
  85. case 'i':
  86. srv.docindex = EARGF(usage());
  87. if (strchr(srv.docindex, '/')) {
  88. die("The document index must not contain '/'");
  89. }
  90. break;
  91. case 'l':
  92. srv.listdirs = 1;
  93. break;
  94. case 'm':
  95. if (spacetok(EARGF(usage()), tok, 3) || !tok[0] || !tok[1]) {
  96. usage();
  97. }
  98. if (!(srv.map = reallocarray(srv.map, ++srv.map_len,
  99. sizeof(struct map)))) {
  100. die("reallocarray:");
  101. }
  102. srv.map[srv.map_len - 1].from = tok[0];
  103. srv.map[srv.map_len - 1].to = tok[1];
  104. srv.map[srv.map_len - 1].chost = tok[2];
  105. break;
  106. case 's':
  107. err = NULL;
  108. nslots = strtonum(EARGF(usage()), 1, INT_MAX, &err);
  109. if (err) {
  110. die("strtonum '%s': %s", EARGF(usage()), err);
  111. }
  112. break;
  113. case 't':
  114. err = NULL;
  115. nthreads = strtonum(EARGF(usage()), 1, INT_MAX, &err);
  116. if (err) {
  117. die("strtonum '%s': %s", EARGF(usage()), err);
  118. }
  119. break;
  120. case 'p':
  121. srv.port = EARGF(usage());
  122. break;
  123. case 'U':
  124. udsname = EARGF(usage());
  125. break;
  126. case 'u':
  127. user = EARGF(usage());
  128. break;
  129. case 'v':
  130. if (spacetok(EARGF(usage()), tok, 4) || !tok[0] || !tok[1] ||
  131. !tok[2]) {
  132. usage();
  133. }
  134. if (!(srv.vhost = reallocarray(srv.vhost, ++srv.vhost_len,
  135. sizeof(*srv.vhost)))) {
  136. die("reallocarray:");
  137. }
  138. srv.vhost[srv.vhost_len - 1].chost = tok[0];
  139. srv.vhost[srv.vhost_len - 1].regex = tok[1];
  140. srv.vhost[srv.vhost_len - 1].dir = tok[2];
  141. srv.vhost[srv.vhost_len - 1].prefix = tok[3];
  142. break;
  143. default:
  144. usage();
  145. } ARGEND
  146. if (argc) {
  147. usage();
  148. }
  149. /* can't have both host and UDS but must have one of port or UDS*/
  150. if ((srv.host && udsname) || !(srv.port || udsname)) {
  151. usage();
  152. }
  153. if (udsname && (!access(udsname, F_OK) || errno != ENOENT)) {
  154. die("UNIX-domain socket '%s': %s", udsname, errno ?
  155. strerror(errno) : "File exists");
  156. }
  157. /* compile and check the supplied vhost regexes */
  158. for (i = 0; i < srv.vhost_len; i++) {
  159. if (regcomp(&srv.vhost[i].re, srv.vhost[i].regex,
  160. REG_EXTENDED | REG_ICASE | REG_NOSUB)) {
  161. die("regcomp '%s': invalid regex",
  162. srv.vhost[i].regex);
  163. }
  164. }
  165. /* validate user and group */
  166. errno = 0;
  167. if (!user || !(pwd = getpwnam(user))) {
  168. die("getpwnam '%s': %s", user ? user : "null",
  169. errno ? strerror(errno) : "Entry not found");
  170. }
  171. errno = 0;
  172. if (!group || !(grp = getgrnam(group))) {
  173. die("getgrnam '%s': %s", group ? group : "null",
  174. errno ? strerror(errno) : "Entry not found");
  175. }
  176. /* open a new process group */
  177. setpgid(0, 0);
  178. handlesignals(sigcleanup);
  179. /*
  180. * set the maximum number of open file descriptors as needed
  181. * - 3 initial fd's
  182. * - nthreads fd's for the listening socket
  183. * - (nthreads * nslots) fd's for the connection-fd
  184. * - (5 * nthreads) fd's for general purpose thread-use
  185. */
  186. rlim.rlim_cur = rlim.rlim_max = 3 + nthreads + nthreads * nslots +
  187. 5 * nthreads;
  188. if (setrlimit(RLIMIT_NOFILE, &rlim) < 0) {
  189. if (errno == EPERM) {
  190. die("You need to run as root or have "
  191. "CAP_SYS_RESOURCE set, or are asking for more "
  192. "file descriptors than the system can offer");
  193. } else {
  194. die("setrlimit:");
  195. }
  196. }
  197. /*
  198. * create the (non-blocking) listening socket
  199. *
  200. * we could use SO_REUSEPORT and create a listening socket for
  201. * each thread (for better load-balancing, given each thread
  202. * would get his own kernel-queue), but this increases latency
  203. * (as a thread might get stuck on a larger request, making all
  204. * other request wait in line behind it).
  205. *
  206. * socket contention with a single listening socket is a
  207. * non-issue and thread-load-balancing is better fixed in the
  208. * kernel by changing epoll-sheduling from a FIFO- to a
  209. * LIFO-model, especially as it doesn't affect performance
  210. */
  211. insock = udsname ? sock_get_uds(udsname, pwd->pw_uid, grp->gr_gid) :
  212. sock_get_ips(srv.host, srv.port);
  213. if (sock_set_nonblocking(insock)) {
  214. return 1;
  215. }
  216. /*
  217. * before dropping privileges, we fork, as we need to remove
  218. * the UNIX-domain socket when we shut down, which we need
  219. * privileges for
  220. */
  221. switch (fork()) {
  222. case -1:
  223. warn("fork:");
  224. break;
  225. case 0:
  226. /* restore default handlers */
  227. handlesignals(SIG_DFL);
  228. /* reap children automatically */
  229. if (signal(SIGCHLD, SIG_IGN) == SIG_ERR) {
  230. die("signal: Failed to set SIG_IGN on SIGCHLD");
  231. }
  232. if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) {
  233. die("signal: Failed to set SIG_IGN on SIGPIPE");
  234. }
  235. /*
  236. * try increasing the thread-limit by the number
  237. * of threads we need (which is the only reliable
  238. * workaround I know given the thread-limit is per user
  239. * rather than per process), but ignore EPERM errors,
  240. * because this most probably means the user has already
  241. * set the value to the kernel's limit, and there's not
  242. * much we can do in any other case.
  243. * There's also no danger of overflow as the value
  244. * returned by getrlimit() is way below the limits of the
  245. * rlim_t datatype.
  246. */
  247. if (getrlimit(RLIMIT_NPROC, &rlim) < 0) {
  248. die("getrlimit:");
  249. }
  250. if (rlim.rlim_max == RLIM_INFINITY) {
  251. if (rlim.rlim_cur != RLIM_INFINITY) {
  252. /* try increasing current limit by nthreads */
  253. rlim.rlim_cur += nthreads;
  254. }
  255. } else {
  256. /* try increasing current and hard limit by nthreads */
  257. rlim.rlim_cur = rlim.rlim_max += nthreads;
  258. }
  259. if (setrlimit(RLIMIT_NPROC, &rlim) < 0 && errno != EPERM) {
  260. die("setrlimit()");
  261. }
  262. /* limit ourselves to reading the servedir and block further unveils */
  263. eunveil(servedir, "r");
  264. eunveil(NULL, NULL);
  265. /* chroot */
  266. if (chdir(servedir) < 0) {
  267. die("chdir '%s':", servedir);
  268. }
  269. if (chroot(".") < 0) {
  270. if (errno == EPERM) {
  271. die("You need to run as root or have "
  272. "CAP_SYS_CHROOT set");
  273. } else {
  274. die("chroot:");
  275. }
  276. }
  277. /* drop root */
  278. if (pwd->pw_uid == 0 || grp->gr_gid == 0) {
  279. die("Won't run under root %s for hopefully obvious reasons",
  280. (pwd->pw_uid == 0) ? (grp->gr_gid == 0) ?
  281. "user and group" : "user" : "group");
  282. }
  283. if (setgroups(1, &(grp->gr_gid)) < 0) {
  284. if (errno == EPERM) {
  285. die("You need to run as root or have "
  286. "CAP_SETGID set");
  287. } else {
  288. die("setgroups:");
  289. }
  290. }
  291. if (setgid(grp->gr_gid) < 0) {
  292. if (errno == EPERM) {
  293. die("You need to run as root or have "
  294. "CAP_SETGID set");
  295. } else {
  296. die("setgid:");
  297. }
  298. }
  299. if (setuid(pwd->pw_uid) < 0) {
  300. if (errno == EPERM) {
  301. die("You need to run as root or have "
  302. "CAP_SETUID set");
  303. } else {
  304. die("setuid:");
  305. }
  306. }
  307. if (udsname) {
  308. epledge("stdio rpath proc unix", NULL);
  309. } else {
  310. epledge("stdio rpath proc inet", NULL);
  311. }
  312. /* accept incoming connections */
  313. server_init_thread_pool(insock, nthreads, nslots, &srv);
  314. exit(0);
  315. default:
  316. /* limit ourselves even further while we are waiting */
  317. if (udsname) {
  318. eunveil(udsname, "c");
  319. eunveil(NULL, NULL);
  320. epledge("stdio cpath", NULL);
  321. } else {
  322. eunveil("/", "");
  323. eunveil(NULL, NULL);
  324. epledge("stdio", NULL);
  325. }
  326. while (wait(&status) > 0)
  327. ;
  328. }
  329. cleanup();
  330. return status;
  331. }