/*- * Copyright (c) 2017, 2018 Taylor R. Campbell * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "attribute.h" #include "explicit_memset.h" #include "freadline.h" #include "onion_closefrom.h" #include "onion_setuidgid.h" #include "progname.h" #include "read_block.h" #include "strlcat.h" #include "strlcpy.h" #include "strprefix.h" /* XXX Apparently Linux doesn't have these...? */ #ifndef UID_MAX #define UID_MAX (~((uid_t)1 << (CHAR_BIT*sizeof(uid_t) - 1))) #endif #ifndef GID_MAX #define GID_MAX (~((gid_t)1 << (CHAR_BIT*sizeof(gid_t) - 1))) #endif /* Forward declarations. */ static void signalhandlers(void); static void ignoresignal(int); static void interruptsignal(int); static void exitsignal(int); static void parseargs(int, char **); static void usage(void) attr_noreturn; static uintmax_t parseu(unsigned, const char *, uintmax_t, uintmax_t, const char *); static void readonionsecret(void); static void readcontrolcookie(void); static void makesocket(void); static void opencontrol(void); static void addonion(void); static void privdrop(void); static void printonion(void); static void serve(void) attr_noreturn; static void printstatus(unsigned); static void childerr(int, const char *) attr_noreturn; /* XXX make this useful */ enum verbosity { V_QUIET = 0, V_ERROR, V_VERBOSE, V_DEFAULT = V_ERROR }; /* Options. */ enum verbosity verbosity = V_DEFAULT; unsigned maxchildren = 100000; uid_t uid = -1; gid_t gid = -1; uid_t owner = -1; gid_t group = -1; mode_t mode = 0700; unsigned backlog = 5; /* XXX why? */ bool do_printonion = false; bool nonanonymous = false; /* `Address', in UCSPIese. */ const char * path_control; const char * path_controlcookie; const char * path_onionsecret; unsigned port; const char * path_socket; char *const * application; /* State. */ int pipehack[2]; char onionsecret[1024]; size_t onionsecretlen; unsigned char controlcookie[512]; size_t controlcookielen; int controlfd = -1; FILE * control = NULL; int socketfd = -1; sig_atomic_t unlink_socket = 0; char buf[1024]; char onion[512]; /* * Main */ int main(int argc, char **argv) { setprogname(argv[0]); signalhandlers(); parseargs(argc, argv); readonionsecret(); readcontrolcookie(); makesocket(); opencontrol(); addonion(); privdrop(); printonion(); serve(); notreached(); } /* * signalhandlers() * * Set up signal handlers. * - Initialize a pipe to reliably wake on SIGCHLD. * - Ignore SIGPIPE. * - Write to pipe on SIGCHLD. * - Exit gracefully on SIGTERM. */ static void signalhandlers(void) { int flags; unsigned i; /* * Create a nonblocking close-on-exec pipe for delivering * SIGCHLD notifications while waiting for other I/O via * select. */ if (pipe(pipehack) == -1) err(1, "pipehack"); for (i = 0; i < 2; i++) { if ((flags = fcntl(pipehack[i], F_GETFL)) == -1) err(1, "get flags"); flags |= O_NONBLOCK; if (fcntl(pipehack[i], F_SETFL, flags) == -1) err(1, "set flags"); if ((flags = fcntl(pipehack[i], F_GETFD)) == -1) err(1, "get cloexec flag"); flags |= FD_CLOEXEC; if (fcntl(pipehack[i], F_SETFD, flags) == -1) err(1, "set cloexec flag"); } /* Ignore SIGPIPE. */ if (signal(SIGPIPE, &ignoresignal) == SIG_ERR) err(1, "ignore SIGPIPE"); /* * Interrupt system calls waiting in I/O on SIGCHLD so we have * a chance to reap children before restarting them. */ if (signal(SIGCHLD, &interruptsignal) == SIG_ERR) err(1, "handle SIGCHLD"); /* Exit gracefully on SIGTERM. */ if (signal(SIGTERM, &exitsignal) == SIG_ERR) err(1, "handle SIGTERM"); } /* * ignoresignal(signo) * * Do nothing but interrupt a system call. We use this rather * than setting SIG_IGN to avoid perturbing the child's * environment -- SIG_IGN is inherited but a signal handler is * reset to the default action. */ static void ignoresignal(int signo attr_unused) { } /* * interruptsignal(signo) * * Do nothing but interrupt a system call waiting for I/O. */ static void interruptsignal(int signo attr_unused) { int error = errno; /* save errno */ char ch = 0; (void)write(pipehack[1], &ch, 1); errno = error; /* restore errno */ } /* * exitsignal(signo) * * Exit gracefully on a signal. Can use only async-signal-safe * library routines here. */ static void exitsignal(int signo attr_unused) { const char msg[] = ": exiting\n"; const struct iovec iov[2] = { { .iov_base = UNCONST(getprogname()), .iov_len = strlen(getprogname()) }, { .iov_base = UNCONST(msg), .iov_len = strlen(msg) }, }; /* Say farewell. */ (void)writev(2, iov, 2); /* Unlink the socket, if we had created it. If this fails, tough. */ if (unlink_socket) (void)unlink(path_socket); /* Exit with status 0. */ _exit(0); } /* * parseargs(argc, argv) * * Parse command-line arguments argc/argv. */ static void parseargs(int argc, char **argv) { extern char *optarg; extern int optind; int ch; while ((ch = getopt(argc, argv, "1b:c:g:G:m:nNO:qQu:Uv")) != -1) { switch (ch) { case '1': do_printonion = true; break; case 'b': backlog = parseu(10, optarg, 0, INT_MAX, "backlog"); break; case 'c': maxchildren = parseu(10, optarg, 0, UINT_MAX, "max connections"); break; case 'g': gid = parseu(10, optarg, 0, GID_MAX, "gid"); break; case 'G': group = parseu(10, optarg, 0, GID_MAX, "group"); break; case 'm': mode = parseu(8, optarg, 0, 0777, "mode"); break; case 'n': nonanonymous = true; break; case 'N': nonanonymous = false; break; case 'O': owner = parseu(10, optarg, 0, UID_MAX, "owner"); break; case 'q': verbosity = V_QUIET; break; case 'Q': verbosity = V_ERROR; break; case 'u': uid = parseu(10, optarg, 0, UID_MAX, "uid"); break; case 'U': { const char *uids = getenv("UID"); const char *gids = getenv("GID"); if (uids != NULL) uid = parseu(10, uids, 0, UID_MAX, "$UID"); if (gids != NULL) gid = parseu(10, gids, 0, GID_MAX, "$GID"); break; } case 'v': verbosity = V_VERBOSE; break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc < 6) usage(); argc--, path_control = *argv++; argc--, path_controlcookie = *argv++; argc--, path_onionsecret = *argv++; argc--, port = parseu(10, *argv++, 0, 65535, "port"); argc--, path_socket = *argv++; assert(0 < argc); assert(*argv != NULL); application = argv; } /* * usage() * * Print usage message and exit. */ static void attr_noreturn usage(void) { fprintf(stderr, "Usage: %s [-1nNqQUv]\n", getprogname()); fprintf(stderr, " [-b ] [-g ] [-G ]"); fprintf(stderr, " [-m ] [-O ] [-u ]\n"); fprintf(stderr, " "); fprintf(stderr, " ...\n"); exit(1); } /* * parseu(radix, arg, minimum, maximum, name) * * Parse the unsigned integer in the given radix from the string * arg called name, requiring it to be no less than minimum and no * greater than maximum. Exit on failure, using name to report an * error message. */ static uintmax_t parseu(unsigned radix, const char *arg, uintmax_t minimum, uintmax_t maximum, const char *name) { char *end; uintmax_t v; errno = 0; v = strtoumax(arg, &end, radix); if (end == arg) errx(1, "invalid %s: `%s'", name, arg); if (*end != '\0') errx(1, "invalid %s -- trailing garbage: `%s'", name, arg); if (errno == 0 && (v < minimum || maximum < v)) errno = ERANGE; if (errno) err(1, "invalid %s -- out of range: `%s'", name, arg); return v; } /* * readonionsecret() * * Read the onion secret from the file at path_onionsecret into * the buffer onionsecret. Ensure onionsecret is NUL-terminated * with no trailing newline, and set onionsecretlen to its length * in bytes. */ static void readonionsecret(void) { int fd; ssize_t nread; if (verbosity >= V_VERBOSE) warnx("read onion secret"); if ((fd = open(path_onionsecret, O_RDONLY|O_CLOEXEC)) == -1) err(1, "open onion secret"); nread = read_block(fd, onionsecret, sizeof onionsecret); if (nread == -1) err(1, "read onion secret"); onionsecretlen = (size_t)nread; if (onionsecretlen < 1) errx(1, "invalid onion secret -- too short"); if (onionsecretlen == sizeof onionsecret) errx(1, "invalid onion secret -- too long"); onionsecretlen--; if (onionsecret[onionsecretlen] != '\n') errx(1, "invalid onion secret -- no trailing newline"); onionsecret[onionsecretlen] = '\0'; if (close(fd) == -1) { if (verbosity >= V_ERROR) warn("close onion secret"); } } /* * readcontrolcookie() * * Read the control cookie from the file at path_controlcookie * into the buffer controlcookie. The control cookie is an * arbitrary sequence of bytes with no trailing newline or NUL * terminator. */ static void readcontrolcookie(void) { int fd; ssize_t nread; if (verbosity >= V_VERBOSE) warnx("read control cookie"); if ((fd = open(path_controlcookie, O_RDONLY|O_CLOEXEC)) == -1) err(1, "open control cookie"); nread = read_block(fd, controlcookie, sizeof controlcookie); if (nread == -1) err(1, "read control cookie"); controlcookielen = (size_t)nread; if (controlcookielen == sizeof controlcookie) err(1, "control cookie too long"); if (close(fd) == -1) { if (verbosity >= V_ERROR) warn("close control cookie"); } } /* * makesocket() * * Create and bind the socket, and set its ownership and mode. */ static void makesocket(void) { static const struct sockaddr_un zero_sun; struct sockaddr_un addr = zero_sun; struct stat st; mode_t old; if (verbosity >= V_VERBOSE) warnx("make socket"); /* Make sure the socket path isn't too long. */ if (strlen(path_socket) >= sizeof(addr.sun_path)) errx(1, "socket path too long"); /* Warn if it's not an absolute path -- tor is probably elsewhere. */ if (path_socket[0] != '/') warnx("socket path is not absolute, can tor connect to it?"); /* Format a Unix socket address. */ addr.sun_family = AF_LOCAL; (void)strlcpy(addr.sun_path, path_socket, sizeof addr.sun_path); /* * If there's already a socket there, assume it's stale. * Anything else, and we assume the caller made a mistake, so * we avoid clobbering it. Caller has responsibility for * excluding access to the directory. */ if (stat(path_socket, &st) == -1) { if (errno != ENOENT) err(1, "stat"); } else { if (!S_ISSOCK(st.st_mode)) errx(1, "socket is not a socket"); /* TOCTOU */ if (unlink(path_socket) == -1) err(1, "unlink socket"); } /* * Confirmed there was nothing there that we might be * inadvertently clobbering, so it is now safe for the signal * handler to unlink the socket. */ unlink_socket = 1; /* Create a socket. */ if ((socketfd = socket(AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0)) == -1) err(1, "create socket"); /* * Bind the socket with exactly the specified permission bits. * No way to do this explicitly in bind, so mask all bits * first; then make sure ownership is right and chmod. */ old = umask(0777); if (bind(socketfd, (const struct sockaddr *)&addr, sizeof addr) == -1) err(1, "bind socket"); if (owner != (uid_t)-1 || group != (gid_t)-1) { if (chown(path_socket, owner, group) == -1) err(1, "chown socket"); } if (chmod(path_socket, mode) == -1) err(1, "chmod socket"); (void)umask(old); /* Listen with the requested backlog. */ if (listen(socketfd, backlog) == -1) err(1, "listen socket"); } /* * opencontrol() * * Open the control socket and file, and authenticate to them. */ static void opencontrol(void) { static const struct sockaddr_un zero_sun; struct sockaddr_un addr = zero_sun; size_t len; unsigned i; if (verbosity >= V_VERBOSE) warnx("open tor control"); /* Make sure the socket path isn't too long. */ if (strlen(path_control) >= sizeof addr.sun_path) errx(1, "control path too long"); /* Format a Unix socket address. */ addr.sun_family = AF_LOCAL; (void)strlcpy(addr.sun_path, path_control, sizeof addr.sun_path); /* Create a socket and connect it to the control socket. */ if ((controlfd = socket(AF_LOCAL, SOCK_STREAM|SOCK_CLOEXEC, 0)) == -1) err(1, "create control socket"); if (connect(controlfd, (const struct sockaddr *)&addr, sizeof addr) == -1) err(1, "connect control socket"); /* Open a stdio stream; hand off responsibility for controlfd. */ if ((control = fdopen(controlfd, "r+")) == NULL) err(1, "open control stream"); controlfd = -1; /* Authenticate. */ if (verbosity >= V_VERBOSE) warnx("authenticate tor control"); fprintf(control, "AUTHENTICATE "); for (i = 0; i < controlcookielen; i++) fprintf(control, "%02hhx", controlcookie[i]); fprintf(control, "\n"); fflush(control); if (ferror(control)) errx(1, "write control stream"); /* Check authentication results. */ if (freadline(buf, sizeof buf, &len, control) == EOF) err(1, "read control stream"); if (len < 4) errx(1, "short control read"); if (strncmp(&buf[len - 2], "\r\n", 2) != 0) errx(1, "truncated control read"); if (strprefix(buf, len, "250 ") != 0) errx(1, "failed to authenticate"); explicit_memset(buf, 0, sizeof buf); } /* * addonion() * * Ask tor to add our onion. */ static void addonion(void) { size_t len; if (verbosity >= V_VERBOSE) warnx("add onion"); /* Send ADD_ONION request. */ fprintf(control, "ADD_ONION %s Flags=DiscardPK", onionsecret); if (nonanonymous) fprintf(control, ",NonAnonymous"); /* XXX client authentication */ fprintf(control, " Port=%d,unix:%s", port, path_socket); fprintf(control, "\n"); fflush(control); if (ferror(control)) err(1, "write control stream"); /* Process multi-line reply. */ for (;;) { /* Read line and truncate CRLF. */ if (freadline(buf, sizeof buf, &len, control) == EOF) err(1, "read control stream"); if (len < 4) errx(1, "short control read"); if (strncmp(&buf[len - 2], "\r\n", 2) != 0) errx(1, "truncated control read"); buf[len - 2] = '\0'; /* Dispatch on answer. */ if (strprefix(buf, len, "250-ServiceID=") == 0) { /* ServiceID: Record .onion address. */ (void)strlcpy(onion, buf + strlen("250-ServiceID="), sizeof onion); (void)strlcat(onion, ".onion", sizeof onion); } else if (strprefix(buf, len, "250-PrivateKey=") == 0) { /* PrivateKey: Didn't ask for it. */ if (verbosity >= V_ERROR) warnx("tor gave us private key" " against our wishes"); } else if (strprefix(buf, len, "250-ClientAuth=") == 0) { /* ClientAuth: nothing */ /* XXX client authentication */ } else if (strprefix(buf, len, "250-") == 0) { /* Unknown answer. Ignore noisily. */ if (verbosity >= V_ERROR) warnx("unknown ADD_ONION response: %s", buf + 4); } else if (strprefix(buf, len, "250 ") == 0) { /* End of answer. Make sure we got .onion address. */ if (onion[0] == '\0') warnx("no onion"); break; } else { /* Invalid answer. Barf. */ errx(1, "ADD_ONION: %s", buf); } } } /* * privdrop() * * Drop privileges and erase secrets. */ static void privdrop(void) { if (verbosity >= V_VERBOSE) warnx("drop privileges"); /* Clear needless secrets. */ explicit_memset(controlcookie, 0, sizeof controlcookie); explicit_memset(onionsecret, 0, sizeof onionsecret); /* Drop privileges. */ if (onion_setuidgid(uid, gid) == -1) err(1, "set uid and gid"); } /* * printonion() * * Print the onion, including `.onion' suffix, if requested. */ static void printonion(void) { if (!do_printonion) return; printf("%s\n", onion); fflush(stdout); if (ferror(stdout)) errx(1, "print onion"); } /* * serve() * * Accept connections and handle them forever. */ static void attr_noreturn serve(void) { volatile unsigned childbudget = maxchildren; fd_set readfds; pid_t pid; int maxfd, nfds, fd, flags, status; /* Set up the child's environment. */ if (setenv("PROTO", "ONION", 1) == -1) err(1, "setenv(PROTO)"); if (setenv("ONIONLOCAL", onion, 1) == -1) err(1, "setenv(ONIONLOCAL)"); if (verbosity >= V_VERBOSE) warnx("serve forever"); /* Find the maximum file descriptor. */ maxfd = socketfd; if (maxfd < pipehack[0]) maxfd = pipehack[0]; if (maxfd < fileno(control)) maxfd = fileno(control); /* Print initial status. */ printstatus(childbudget); for (;;) { /* Reap children. Hang if we're outta juice. */ while ((pid = waitpid(-1, &status, childbudget? WNOHANG : 0)) != 0) { if (pid == -1) { /* If interrupted, try again. */ if (errno == EINTR) continue; /* * If we still have a budget, and there * are no children, just move on. * Otherwise failure here is fatal. */ if (childbudget && errno == ECHILD) break; err(1, "waitpid"); } if (verbosity >= V_VERBOSE) warnx("reaped %"PRIdMAX" status %x", (intmax_t)pid, status); if (WIFSTOPPED(status)) /* paranoia */ continue; /* Credit terminated child to the budget. */ assert(childbudget < maxchildren); childbudget++; printstatus(childbudget); } /* Wait for control activity, connection, or SIGCHLD. */ FD_ZERO(&readfds); FD_SET(fileno(control), &readfds); FD_SET(socketfd, &readfds); FD_SET(pipehack[0], &readfds); nfds = select(maxfd + 1, &readfds, NULL, NULL, NULL); if (nfds == -1) { /* If interrupted, try again. */ if (errno == EINTR) continue; err(1, "select"); } /* Check for activity on control connection. */ if (FD_ISSET(fileno(control), &readfds)) { const ssize_t nread = fread(buf, 1, 1, control); /* * Bail on any activity from the control: * * - We didn't subscribe to any events, and * we're not sending commands, so we don't * expect to read anything; if we do, bail. * * - If we didn't read anything, and ferror * indicates an error, bail. * * - If we didn't read anything, and feof * indicates EOF, bail so caller of * onionserver can try restarting, e.g. when * the tor dameon restarts. * * - If we didn't read anything and neither * ferror nor feof admits fault, something is * fishy, so bail. */ if (nread != 0) { errx(1, "unexpected data from control socket"); } else if (ferror(control)) { err(1, "error on control socket"); } else if (feof(control)) { errx(1, "control socket closed"); } else { errx(1, "stdio stream busted"); } } /* Check for SIGCHLD. */ if (FD_ISSET(pipehack[0], &readfds)) { /* * Empty the nonblocking pipe hack buffer and * start over to reap children. If we're * interrupted, no matter. */ while (read(pipehack[0], buf, sizeof buf) > 0) continue; continue; } /* Check for a connection. */ if (!FD_ISSET(socketfd, &readfds)) { /* If no connection, start over. */ continue; } /* Get the connection. */ fd = accept(socketfd, NULL, NULL); if (fd == -1) { /* Disregard aborted connections or interruption. */ if (errno == ECONNABORTED || errno == EINTR) continue; err(1, "paccept"); } /* * Make it close-on-exec. Only what we dup2 it to -- * namely 0 and 1 -- will be inherited by children. */ if ((flags = fcntl(fd, F_GETFD)) == -1) { warn("get flags"); goto next; } flags |= FD_CLOEXEC; if (fcntl(fd, F_SETFD, flags) == -1) { warn("set flags"); goto next; } /* Debit one child from the budget. */ assert(0 < childbudget); childbudget--; printstatus(childbudget); /* Run the application. */ pid = fork(); switch (pid) { case -1: /* error */ warn("fork"); /* Credit the child back to the budget. */ assert(childbudget < maxchildren); childbudget++; printstatus(childbudget); break; case 0: /* child */ if (dup2(fd, 0) == -1) childerr(255, "dup2"); if (dup2(fd, 1) == -1) childerr(255, "dup2"); if (onion_closefrom(3) == -1) /* paranoia */ childerr(255, "closefrom(3)"); if (execvp(application[0], application) == -1) childerr(255, "execvp"); _exit(255); break; default: /* parent */ if (verbosity >= V_VERBOSE) warnx("fork %"PRIdMAX, (intmax_t)pid); break; } next: /* Close the connection to the client in the parent. */ if (close(fd) == -1) { if (verbosity >= V_ERROR) warn("close fd"); } } } /* * printstatus(childbudget) * * Print status: number of children and maximum number allowed. */ static void printstatus(unsigned childbudget) { unsigned numchildren = maxchildren - childbudget; if (verbosity < V_VERBOSE) return; warnx("status: %u/%u", numchildren, maxchildren); } /* * childerr(status, msg) * * Report an error to stderr from a child process. Can use only * async-signal-safe library routines here. */ static void attr_noreturn childerr(int status, const char *msg) { const struct iovec iov[4] = { { .iov_base = UNCONST(getprogname()), .iov_len = strlen(getprogname()) }, { .iov_base = ": ", .iov_len = 2 }, { .iov_base = UNCONST(msg), .iov_len = strlen(msg) }, { .iov_base = "\n", .iov_len = 1 }, }; (void)writev(2, iov, 4); _exit(status); }