/*- * 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 "attribute.h" #include "explicit_memset.h" #include "freadline.h" #include "progname.h" #include "read_block.h" #include "strlcat.h" #include "strlcpy.h" #include "strprefix.h" /* Forward declarations. */ static void ignoresignal(int); static void parseargs(int, char **); static void usage(void) attr_noreturn; static void readcontrolcookie(void); static void opencontrol(void); static void closecontrol(void); static void addonion(void); static void writefile(const char *, const char *, mode_t); const char default_keytype[] = "BEST"; const char default_keytype_v2[] = "RSA1024"; const char default_keytype_v3[] = "ED25519-V3"; /* Parameters. */ const char * keytype; const char * path_control; const char * path_controlcookie; const char * path_onionsecret; const char * path_onion; /* State. */ unsigned char controlcookie[512]; size_t controlcookielen; int controlfd = -1; FILE * control = NULL; char onionsecret[1024]; char onion[512]; char buf[1024]; /* * Main. */ int main(int argc, char **argv) { setprogname(argv[0]); if (signal(SIGPIPE, &ignoresignal) == SIG_ERR) err(1, "ignore SIGPIPE"); parseargs(argc, argv); readcontrolcookie(); opencontrol(); addonion(); closecontrol(); writefile(onionsecret, path_onionsecret, 0600); writefile(onion, path_onion, 0644); return 0; } /* * ignoresignal(signo) * * Do nothing. 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) { } /* * 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, "t:V:")) != -1) { switch (ch) { case 't': if (strcmp(optarg, "BEST") != 0 && strcmp(optarg, "RSA1024") != 0 && strcmp(optarg, "ED25519-V3") != 0) warnx("unknown key type"); keytype = optarg; break; case 'V': if (keytype != NULL) { warnx("key type overridden, ignoring version"); break; } if (strcmp(optarg, "2") == 0) keytype = default_keytype_v2; else if (strcmp(optarg, "3") == 0) keytype = default_keytype_v3; else errx(1, "unknown onion version"); break; case '?': default: usage(); } } argc -= optind; argv += optind; if (argc != 4) usage(); argc--, path_control = *argv++; argc--, path_controlcookie = *argv++; argc--, path_onionsecret = *argv++; argc--, path_onion = *argv++; assert(argc == 0); assert(*argv == NULL); /* Apply default values. */ if (keytype == NULL) keytype = default_keytype; } /* * usage() * * Print usage message and exit. */ static void attr_noreturn usage(void) { fprintf(stderr, "Usage: %s", getprogname()); fprintf(stderr, " \n"); exit(1); } /* * 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 ((fd = open(path_controlcookie, O_RDONLY)) == -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) warn("close control cookie"); } /* * 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; /* 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. */ 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 (%zu): %s", len, buf); if (strprefix(buf, len, "250 ") != 0) errx(1, "failed to authenticate"); explicit_memset(buf, 0, sizeof buf); } /* * closecontrol() * * Close the control socket. */ static void closecontrol(void) { if (fclose(control) == EOF) warn("close control"); } /* * addonion() * * Ask tor to add our onion. */ static void addonion(void) { size_t len; /* Send ADD_ONION request. */ fprintf(control, "ADD_ONION NEW:%s", keytype); /* XXX client authentication */ fprintf(control, " Port=65535,unix:/dev/null"); 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 (%zu): %s", len, buf); 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: Record private key. */ (void)strlcpy(onionsecret, buf + strlen("250-PrivateKey="), sizeof onionsecret); } else if (strprefix(buf, len, "250-ClientAuth=") == 0) { /* ClientAuth: nothing */ /* XXX client authentication */ } else if (strprefix(buf, len, "250-") == 0) { /* Unknown answer. Ignore noisily. */ warnx("unknown ADD_ONION response: %s", buf + 4); } else if (strprefix(buf, len, "250 ") == 0) { /* End of answer. Make sure we got address & key. */ if (onion[0] == '\0') warnx("no onion"); if (onionsecret[0] == '\0') warnx("no onion secret"); break; } else { /* Invalid answer. Barf. */ errx(1, "ADD_ONION: %s", buf); } } /* * No need to delete onion -- control connection, to which this * onion is pegged, will die soon. */ } /* * writefile(s, path, perms) * * Write the NUL-terminated string s, followed by a newline (but * excluding the NUL terminator), to the file path, with the * specified permission bits. */ static void writefile(const char *s, const char *path, mode_t perms) { struct iovec iov[2] = { { .iov_base = UNCONST(s), .iov_len = strlen(s) }, { .iov_base = "\n", .iov_len = 1 }, }; int fd; ssize_t nwrit; if ((fd = open(path, O_WRONLY|O_CREAT|O_EXCL, perms)) == -1) err(1, "open"); if ((nwrit = writev(fd, iov, 2)) == -1) err(1, "write"); if ((size_t)nwrit != strlen(s) + 1) err(1, "bad write"); if (close(fd) == -1) warn("close"); }