summaryrefslogtreecommitdiff
path: root/src/main.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.cc')
-rw-r--r--src/main.cc1009
1 files changed, 1009 insertions, 0 deletions
diff --git a/src/main.cc b/src/main.cc
new file mode 100644
index 0000000..f1c1c54
--- /dev/null
+++ b/src/main.cc
@@ -0,0 +1,1009 @@
+/*-
+ * SSLsplit - transparent SSL/TLS interception
+ * https://www.roe.ch/SSLsplit
+ *
+ * Copyright (c) 2009-2018, Daniel Roethlisberger <[email protected]>.
+ * 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 COPYRIGHT HOLDER 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 COPYRIGHT HOLDER 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.
+ */
+
+/* silence daemon(3) deprecation warning on Mac OS X */
+#if __APPLE__
+#define daemon xdaemon
+#endif /* __APPLE__ */
+
+#include "opts.h"
+#include "proxy.h"
+#include "privsep.h"
+#include "ssl.h"
+#include "nat.h"
+#include "cachemgr.h"
+#include "sys.h"
+#include "log.h"
+#include "build.h"
+#include "defaults.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <signal.h>
+#include <string.h>
+
+#ifndef __BSD__
+#include <getopt.h>
+#endif /* !__BSD__ */
+
+#include <event2/event.h>
+
+#include <openssl/ssl.h>
+#include <openssl/x509.h>
+
+#if __APPLE__
+#undef daemon
+extern int daemon(int, int);
+#endif /* __APPLE__ */
+
+
+/*
+ * Print version information to stderr.
+ */
+static void
+main_version(void)
+{
+ fprintf(stderr, "%s %s (built %s)\n",
+ PKGLABEL, build_version, build_date);
+ if (strlen(build_version) < 5) {
+ /*
+ * Note to package maintainers: If you break the version
+ * string in your build, it will be impossible to provide
+ * proper upstream support to the users of the package,
+ * because it will be difficult or impossible to identify
+ * the exact codebase that is being used by the user
+ * reporting a bug. The version string is provided through
+ * different means depending on whether the code is a git
+ * checkout, a tarball downloaded from GitHub or a release.
+ * See GNUmakefile for the gory details.
+ */
+ fprintf(stderr, "---------------------------------------"
+ "---------------------------------------\n");
+ fprintf(stderr, "WARNING: Something is wrong with the "
+ "version compiled into sslsplit!\n");
+ fprintf(stderr, "The version should contain a release "
+ "number and/or a git commit reference.\n");
+ fprintf(stderr, "If using a package, please report a bug "
+ "to the distro package maintainer.\n");
+ fprintf(stderr, "---------------------------------------"
+ "---------------------------------------\n");
+ }
+ fprintf(stderr, "Copyright (c) 2009-2018, "
+ "Daniel Roethlisberger <[email protected]>\n");
+ fprintf(stderr, "https://www.roe.ch/SSLsplit\n");
+ if (build_info[0]) {
+ fprintf(stderr, "Build info: %s\n", build_info);
+ }
+ if (build_features[0]) {
+ fprintf(stderr, "Features: %s\n", build_features);
+ }
+ nat_version();
+ fprintf(stderr, "Local process info support: ");
+#ifdef HAVE_LOCAL_PROCINFO
+ fprintf(stderr, "yes (" LOCAL_PROCINFO_STR ")\n");
+#else /* !HAVE_LOCAL_PROCINFO */
+ fprintf(stderr, "no\n");
+#endif /* !HAVE_LOCAL_PROCINFO */
+ ssl_openssl_version();
+ fprintf(stderr, "compiled against libevent %s\n", LIBEVENT_VERSION);
+ fprintf(stderr, "rtlinked against libevent %s\n", event_get_version());
+ fprintf(stderr, "%d CPU cores detected\n", sys_get_cpu_cores());
+}
+
+/*
+ * Print usage to stderr.
+ */
+static void
+main_usage(void)
+{
+ const char *dflt, *warn;
+ const char *usagefmt =
+"Usage: %s [options...] [proxyspecs...]\n"
+" -c pemfile use CA cert (and key) from pemfile to sign forged certs\n"
+" -k pemfile use CA key (and cert) from pemfile to sign forged certs\n"
+" -C pemfile use CA chain from pemfile (intermediate and root CA certs)\n"
+" -K pemfile use key from pemfile for leaf certs (default: generate)\n"
+" -q crlurl use URL as CRL distribution point for all forged certs\n"
+" -t certdir use cert+chain+key PEM files from certdir to target all sites\n"
+" matching the common names (non-matching: generate if CA)\n"
+" -w gendir write leaf key and only generated certificates to gendir\n"
+" -W gendir write leaf key and all certificates to gendir\n"
+" -O deny all OCSP requests on all proxyspecs\n"
+" -P passthrough SSL connections if they cannot be split because of\n"
+" client cert auth or no matching cert and no CA (default: drop)\n"
+#ifndef OPENSSL_NO_DH
+" -g pemfile use DH group params from pemfile (default: keyfiles or auto)\n"
+#define OPT_g "g:"
+#else /* OPENSSL_NO_DH */
+#define OPT_g
+#endif /* !OPENSSL_NO_DH */
+#ifndef OPENSSL_NO_ECDH
+" -G curve use ECDH named curve (default: " DFLT_CURVE ")\n"
+#define OPT_G "G:"
+#else /* OPENSSL_NO_ECDH */
+#define OPT_G
+#endif /* OPENSSL_NO_ECDH */
+#ifdef SSL_OP_NO_COMPRESSION
+" -Z disable SSL/TLS compression on all connections\n"
+#define OPT_Z "Z"
+#else /* !SSL_OP_NO_COMPRESSION */
+#define OPT_Z
+#endif /* !SSL_OP_NO_COMPRESSION */
+" -r proto only support one of " SSL_PROTO_SUPPORT_S "(default: all)\n"
+" -R proto disable one of " SSL_PROTO_SUPPORT_S "(default: none)\n"
+" -s ciphers use the given OpenSSL cipher suite spec (default: " DFLT_CIPHERS ")\n"
+" -e engine specify default NAT engine to use (default: %s)\n"
+" -E list available NAT engines and exit\n"
+" -u user drop privileges to user (default if run as root: " DFLT_DROPUSER ")\n"
+" -m group when using -u, override group (default: primary group of user)\n"
+" -j jaildir chroot() to jaildir (impacts sni proxyspecs, see manual page)\n"
+" -p pidfile write pid to pidfile (default: no pid file)\n"
+" -l logfile connect log: log one line summary per connection to logfile\n"
+" -L logfile content log: full data to file or named pipe (excludes -S/-F)\n"
+" -S logdir content log: full data to separate files in dir (excludes -L/-F)\n"
+" -F pathspec content log: full data to sep files with %% subst (excl. -L/-S):\n"
+" %%T - initial connection time as an ISO 8601 UTC timestamp\n"
+" %%d - destination host and port\n"
+" %%D - destination host\n"
+" %%p - destination port\n"
+" %%s - source host and port\n"
+" %%S - source host\n"
+" %%q - source port\n"
+#ifdef HAVE_LOCAL_PROCINFO
+" %%x - base name of local process (requires -i)\n"
+" %%X - full path to local process (requires -i)\n"
+" %%u - user name or id of local process (requires -i)\n"
+" %%g - group name or id of local process (requires -i)\n"
+#endif /* HAVE_LOCAL_PROCINFO */
+" %%%% - literal '%%'\n"
+#ifdef HAVE_LOCAL_PROCINFO
+" e.g. \"/var/log/sslsplit/%%X/%%u-%%s-%%d-%%T.log\"\n"
+" -i look up local process owning each connection for logging\n"
+#define OPT_i "i"
+#else /* !HAVE_LOCAL_PROCINFO */
+" e.g. \"/var/log/sslsplit/%%T-%%s-%%d.log\"\n"
+#define OPT_i
+#endif /* HAVE_LOCAL_PROCINFO */
+" -M logfile log master keys to logfile in SSLKEYLOGFILE format\n"
+" -d daemon mode: run in background, log error messages to syslog\n"
+" -D debug mode: run in foreground, log debug messages on stderr\n"
+" -V print version information and exit\n"
+" -h print usage information and exit\n"
+" proxyspec = type listenaddr+port [natengine|targetaddr+port|\"sni\"+port]\n"
+" e.g. http 0.0.0.0 8080 www.roe.ch 80 # http/4; static hostname dst\n"
+" https ::1 8443 2001:db8::1 443 # https/6; static address dst\n"
+" https 127.0.0.1 9443 sni 443 # https/4; SNI DNS lookups\n"
+" tcp 127.0.0.1 10025 # tcp/4; default NAT engine\n"
+" ssl 2001:db8::2 9999 pf # ssl/6; NAT engine 'pf'\n"
+" autossl ::1 10025 # autossl/6; STARTTLS et al\n"
+"Example:\n"
+" %s -k ca.key -c ca.pem -P https 127.0.0.1 8443 https ::1 8443\n"
+"%s";
+
+ if (!(dflt = nat_getdefaultname())) {
+ dflt = "n/a";
+ warn = "\nWarning: no supported NAT engine on this platform!\n"
+ "Only static and SNI proxyspecs are supported.\n";
+ } else {
+ warn = "";
+ }
+
+ fprintf(stderr, usagefmt, build_pkgname, dflt, build_pkgname, warn);
+}
+
+/*
+ * Callback to load a cert/chain/key combo from a single PEM file.
+ * A return value of -1 indicates a fatal error to the file walker.
+ */
+static int
+main_loadtgcrt(const char *filename, void *arg)
+{
+ tfe_config *opts = arg;
+ cert_t *cert;
+ char **names;
+
+ cert = cert_new_load(filename);
+ if (!cert) {
+ log_err_printf("Failed to load cert and key from PEM file "
+ "'%s'\n", filename);
+ return -1;
+ }
+ if (X509_check_private_key(cert->crt, cert->key) != 1) {
+ log_err_printf("Cert does not match key in PEM file "
+ "'%s':\n", filename);
+ ERR_print_errors_fp(stderr);
+ return -1;
+ }
+
+#ifdef DEBUG_CERTIFICATE
+ log_dbg_printf("Loaded '%s':\n", filename);
+ log_dbg_print_free(ssl_x509_to_str(cert->crt));
+ log_dbg_print_free(ssl_x509_to_pem(cert->crt));
+#endif /* DEBUG_CERTIFICATE */
+
+ if (OPTS_DEBUG(opts)) {
+ log_dbg_printf("Targets for '%s':", filename);
+ }
+ names = ssl_x509_names(cert->crt);
+ for (char **p = names; *p; p++) {
+ /* be deliberately vulnerable to NULL prefix attacks */
+ char *sep;
+ if ((sep = strchr(*p, '!'))) {
+ *sep = '\0';
+ }
+ if (OPTS_DEBUG(opts)) {
+ log_dbg_printf(" '%s'", *p);
+ }
+ cachemgr_tgcrt_set(*p, cert);
+ free(*p);
+ }
+ if (OPTS_DEBUG(opts)) {
+ log_dbg_printf("\n");
+ }
+ free(names);
+ cert_free(cert);
+ return 0;
+}
+
+/*
+ * Handle out of memory conditions in early stages of main().
+ * Print error message and exit with failure status code.
+ * Does not return.
+ */
+void NORET
+oom_die(const char *argv0)
+{
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * Main entry point.
+ */
+int
+main(int argc, char *argv[])
+{
+ const char *argv0;
+ int ch;
+ tfe_config *opts;
+ proxy_ctx_t *proxy;
+
+ char *natengine;
+ int pidfd = -1;
+ int rv = EXIT_FAILURE;
+
+ argv0 = argv[0];
+ opts = tfe_config_new();
+ if (nat_getdefaultname()) {
+ natengine = strdup(nat_getdefaultname());
+ if (!natengine)
+ oom_die(argv0);
+ } else {
+ natengine = NULL;
+ }
+
+ while ((ch = getopt(argc, argv, OPT_g OPT_G OPT_Z OPT_i "k:c:C:K:t:"
+ "OPs:r:R:e:Eu:m:j:p:l:L:S:F:M:dDVhW:w:q:")) != -1) {
+ switch (ch) {
+ case 'c':
+ if (opts->cacrt)
+ X509_free(opts->cacrt);
+ opts->cacrt = ssl_x509_load(optarg);
+ if (!opts->cacrt) {
+ fprintf(stderr, "%s: error loading CA "
+ "cert from '%s':\n",
+ argv0, optarg);
+ if (errno) {
+ fprintf(stderr, "%s\n",
+ strerror(errno));
+ } else {
+ ERR_print_errors_fp(stderr);
+ }
+ exit(EXIT_FAILURE);
+ }
+ ssl_x509_refcount_inc(opts->cacrt);
+ sk_X509_insert(opts->chain, opts->cacrt, 0);
+ if (!opts->cakey) {
+ opts->cakey = ssl_key_load(optarg);
+ }
+#ifndef OPENSSL_NO_DH
+ if (!opts->dh) {
+ opts->dh = ssl_dh_load(optarg);
+ }
+#endif /* !OPENSSL_NO_DH */
+ break;
+ case 'k':
+ if (opts->cakey)
+ EVP_PKEY_free(opts->cakey);
+ opts->cakey = ssl_key_load(optarg);
+ if (!opts->cakey) {
+ fprintf(stderr, "%s: error loading CA "
+ "key from '%s':\n",
+ argv0, optarg);
+ if (errno) {
+ fprintf(stderr, "%s\n",
+ strerror(errno));
+ } else {
+ ERR_print_errors_fp(stderr);
+ }
+ exit(EXIT_FAILURE);
+ }
+ if (!opts->cacrt) {
+ opts->cacrt = ssl_x509_load(optarg);
+ if (opts->cacrt) {
+ ssl_x509_refcount_inc(
+ opts->cacrt);
+ sk_X509_insert(opts->chain,
+ opts->cacrt, 0);
+ }
+ }
+#ifndef OPENSSL_NO_DH
+ if (!opts->dh) {
+ opts->dh = ssl_dh_load(optarg);
+ }
+#endif /* !OPENSSL_NO_DH */
+ break;
+ case 'C':
+ if (ssl_x509chain_load(NULL, &opts->chain,
+ optarg) == -1) {
+ fprintf(stderr, "%s: error loading "
+ "chain from '%s':\n",
+ argv0, optarg);
+ if (errno) {
+ fprintf(stderr, "%s\n",
+ strerror(errno));
+ } else {
+ ERR_print_errors_fp(stderr);
+ }
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'K':
+ if (opts->key)
+ EVP_PKEY_free(opts->key);
+ opts->key = ssl_key_load(optarg);
+ if (!opts->key) {
+ fprintf(stderr, "%s: error loading lea"
+ "f key from '%s':\n",
+ argv0, optarg);
+ if (errno) {
+ fprintf(stderr, "%s\n",
+ strerror(errno));
+ } else {
+ ERR_print_errors_fp(stderr);
+ }
+ exit(EXIT_FAILURE);
+ }
+#ifndef OPENSSL_NO_DH
+ if (!opts->dh) {
+ opts->dh = ssl_dh_load(optarg);
+ }
+#endif /* !OPENSSL_NO_DH */
+ break;
+ case 't':
+ if (!sys_isdir(optarg)) {
+ fprintf(stderr, "%s: '%s' is not a "
+ "directory\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->tgcrtdir)
+ free(opts->tgcrtdir);
+ opts->tgcrtdir = strdup(optarg);
+ if (!opts->tgcrtdir)
+ oom_die(argv0);
+ break;
+ case 'q':
+ if (opts->crlurl)
+ free(opts->crlurl);
+ opts->crlurl = strdup(optarg);
+ break;
+ case 'O':
+ opts->deny_ocsp = 1;
+ break;
+ case 'P':
+ opts->passthrough = 1;
+ break;
+#ifndef OPENSSL_NO_DH
+ case 'g':
+ if (opts->dh)
+ DH_free(opts->dh);
+ opts->dh = ssl_dh_load(optarg);
+ if (!opts->dh) {
+ fprintf(stderr, "%s: error loading DH "
+ "params from '%s':\n",
+ argv0, optarg);
+ if (errno) {
+ fprintf(stderr, "%s\n",
+ strerror(errno));
+ } else {
+ ERR_print_errors_fp(stderr);
+ }
+ exit(EXIT_FAILURE);
+ }
+ break;
+#endif /* !OPENSSL_NO_DH */
+#ifndef OPENSSL_NO_ECDH
+ case 'G':
+ {
+ EC_KEY *ec;
+ if (opts->ecdhcurve)
+ free(opts->ecdhcurve);
+ if (!(ec = ssl_ec_by_name(optarg))) {
+ fprintf(stderr, "%s: unknown curve "
+ "'%s'\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ EC_KEY_free(ec);
+ opts->ecdhcurve = strdup(optarg);
+ if (!opts->ecdhcurve)
+ oom_die(argv0);
+ break;
+ }
+#endif /* !OPENSSL_NO_ECDH */
+#ifdef SSL_OP_NO_COMPRESSION
+ case 'Z':
+ opts->sslcomp = 0;
+ break;
+#endif /* SSL_OP_NO_COMPRESSION */
+ case 's':
+ if (opts->ciphers)
+ free(opts->ciphers);
+ opts->ciphers = strdup(optarg);
+ if (!opts->ciphers)
+ oom_die(argv0);
+ break;
+ case 'r': tfe_config_proto_force(opts, optarg, argv0);
+ break;
+ case 'R': tfe_config_proto_disable(opts, optarg, argv0);
+ break;
+ case 'e':
+ free(natengine);
+ natengine = strdup(optarg);
+ if (!natengine)
+ oom_die(argv0);
+ break;
+ case 'E':
+ nat_list_engines();
+ exit(EXIT_SUCCESS);
+ break;
+ case 'u':
+ if (!sys_isuser(optarg)) {
+ fprintf(stderr, "%s: '%s' is not an "
+ "existing user\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->dropuser)
+ free(opts->dropuser);
+ opts->dropuser = strdup(optarg);
+ if (!opts->dropuser)
+ oom_die(argv0);
+ break;
+ case 'm':
+ if (!sys_isgroup(optarg)) {
+ fprintf(stderr, "%s: '%s' is not an "
+ "existing group\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->dropgroup)
+ free(opts->dropgroup);
+ opts->dropgroup = strdup(optarg);
+ if (!opts->dropgroup)
+ oom_die(argv0);
+ break;
+ case 'p':
+ if (opts->pidfile)
+ free(opts->pidfile);
+ opts->pidfile = strdup(optarg);
+ if (!opts->pidfile)
+ oom_die(argv0);
+ break;
+ case 'j':
+ if (!sys_isdir(optarg)) {
+ fprintf(stderr, "%s: '%s' is not a "
+ "directory\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->jaildir)
+ free(opts->jaildir);
+ opts->jaildir = realpath(optarg, NULL);
+ if (!opts->jaildir) {
+ fprintf(stderr, "%s: Failed to "
+ "canonicalize '%s': "
+ "%s (%i)\n",
+ argv0, optarg,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'l':
+ if (opts->connectlog)
+ free(opts->connectlog);
+ opts->connectlog = strdup(optarg);
+ if (!opts->connectlog)
+ oom_die(argv0);
+ break;
+ case 'L':
+ if (opts->contentlog)
+ free(opts->contentlog);
+ opts->contentlog = strdup(optarg);
+ if (!opts->contentlog)
+ oom_die(argv0);
+ opts->contentlog_isdir = 0;
+ opts->contentlog_isspec = 0;
+ break;
+ case 'S':
+ if (!sys_isdir(optarg)) {
+ fprintf(stderr, "%s: '%s' is not a "
+ "directory\n",
+ argv0, optarg);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->contentlog)
+ free(opts->contentlog);
+ opts->contentlog = realpath(optarg, NULL);
+ if (!opts->contentlog) {
+ fprintf(stderr, "%s: Failed to "
+ "canonicalize '%s': "
+ "%s (%i)\n",
+ argv0, optarg,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ opts->contentlog_isdir = 1;
+ opts->contentlog_isspec = 0;
+ break;
+ case 'F': {
+ char *lhs, *rhs, *p, *q;
+ size_t n;
+ if (opts->contentlog_basedir)
+ free(opts->contentlog_basedir);
+ if (opts->contentlog)
+ free(opts->contentlog);
+ if (log_content_split_pathspec(optarg, &lhs,
+ &rhs) == -1) {
+ fprintf(stderr, "%s: Failed to split "
+ "'%s' in lhs/rhs: "
+ "%s (%i)\n",
+ argv0, optarg,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ /* eliminate %% from lhs */
+ for (p = q = lhs; *p; p++, q++) {
+ if (q < p)
+ *q = *p;
+ if (*p == '%' && *(p+1) == '%')
+ p++;
+ }
+ *q = '\0';
+ /* all %% in lhs resolved to % */
+ if (sys_mkpath(lhs, 0777) == -1) {
+ fprintf(stderr, "%s: Failed to create "
+ "'%s': %s (%i)\n",
+ argv0, lhs,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ opts->contentlog_basedir = realpath(lhs, NULL);
+ if (!opts->contentlog_basedir) {
+ fprintf(stderr, "%s: Failed to "
+ "canonicalize '%s': "
+ "%s (%i)\n",
+ argv0, lhs,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ /* count '%' in opts->contentlog_basedir */
+ for (n = 0, p = opts->contentlog_basedir;
+ *p;
+ p++) {
+ if (*p == '%')
+ n++;
+ }
+ free(lhs);
+ n += strlen(opts->contentlog_basedir);
+ if (!(lhs = malloc(n + 1)))
+ oom_die(argv0);
+ /* re-encoding % to %%, copying basedir to lhs */
+ for (p = opts->contentlog_basedir, q = lhs;
+ *p;
+ p++, q++) {
+ *q = *p;
+ if (*q == '%')
+ *(++q) = '%';
+ }
+ *q = '\0';
+ /* lhs contains encoded realpathed basedir */
+ if (asprintf(&opts->contentlog,
+ "%s/%s", lhs, rhs) < 0)
+ oom_die(argv0);
+ opts->contentlog_isdir = 0;
+ opts->contentlog_isspec = 1;
+ free(lhs);
+ free(rhs);
+ break;
+ case 'W':
+ opts->certgen_writeall = 1;
+ if (opts->certgendir)
+ free(opts->certgendir);
+ opts->certgendir = strdup(optarg);
+ if (!opts->certgendir)
+ oom_die(argv0);
+ break;
+ case 'w':
+ opts->certgen_writeall = 0;
+ if (opts->certgendir)
+ free(opts->certgendir);
+ opts->certgendir = strdup(optarg);
+ if (!opts->certgendir)
+ oom_die(argv0);
+ break;
+ }
+#ifdef HAVE_LOCAL_PROCINFO
+ case 'i':
+ opts->lprocinfo = 1;
+ break;
+#endif /* HAVE_LOCAL_PROCINFO */
+ case 'M':
+ if (opts->masterkeylog)
+ free(opts->masterkeylog);
+ opts->masterkeylog = strdup(optarg);
+ if (!opts->masterkeylog)
+ oom_die(argv0);
+ break;
+ case 'd':
+ opts->detach = 1;
+ break;
+ case 'D':
+ log_dbg_mode(LOG_DBG_MODE_ERRLOG);
+ opts->debug = 1;
+ break;
+ case 'V':
+ main_version();
+ exit(EXIT_SUCCESS);
+ case 'h':
+ main_usage();
+ exit(EXIT_SUCCESS);
+ case '?':
+ exit(EXIT_FAILURE);
+ default:
+ main_usage();
+ exit(EXIT_FAILURE);
+ }
+ }
+ argc -= optind;
+ argv += optind;
+
+ tfe_config_load_from_file(opts, "./config.ini");
+
+ /* usage checks before defaults */
+ if (opts->detach && OPTS_DEBUG(opts)) {
+ fprintf(stderr, "%s: -d and -D are mutually exclusive.\n",
+ argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (!opts->spec) {
+ fprintf(stderr, "%s: no proxyspec specified.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+ for (proxyspec *spec = opts->spec; spec; spec = spec->next) {
+ if (spec->connect_addrlen || spec->sni_port)
+ continue;
+ if (!spec->natengine) {
+ fprintf(stderr, "%s: no supported NAT engines "
+ "on this platform.\n"
+ "Only static addr and SNI proxyspecs "
+ "supported.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (spec->listen_addr.ss_family == AF_INET6 &&
+ !nat_ipv6ready(spec->natengine)) {
+ fprintf(stderr, "%s: IPv6 not supported by '%s'\n",
+ argv0, spec->natengine);
+ exit(EXIT_FAILURE);
+ }
+ spec->natlookup = nat_getlookupcb(spec->natengine);
+ spec->natsocket = nat_getsocketcb(spec->natengine);
+ }
+ if (tfe_config_has_ssl_spec(opts)) {
+ if (ssl_init() == -1) {
+ fprintf(stderr, "%s: failed to initialize OpenSSL.\n",
+ argv0);
+ exit(EXIT_FAILURE);
+ }
+ if ((opts->cacrt || !opts->tgcrtdir) && !opts->cakey) {
+ fprintf(stderr, "%s: no CA key specified (-k).\n",
+ argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->cakey && !opts->cacrt) {
+ fprintf(stderr, "%s: no CA cert specified (-c).\n",
+ argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (opts->cakey && opts->cacrt &&
+ (X509_check_private_key(opts->cacrt, opts->cakey) != 1)) {
+ fprintf(stderr, "%s: CA cert does not match key.\n",
+ argv0);
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+#ifdef __APPLE__
+ if (opts->dropuser && !!strcmp(opts->dropuser, "root") &&
+ nat_used("pf")) {
+ fprintf(stderr, "%s: cannot use 'pf' proxyspec with -u due "
+ "to Apple bug\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+#endif /* __APPLE__ */
+
+ /* prevent multiple instances running */
+ if (opts->pidfile) {
+ pidfd = sys_pidf_open(opts->pidfile);
+ if (pidfd == -1) {
+ fprintf(stderr, "%s: cannot open PID file '%s' "
+ "- process already running?\n",
+ argv0, opts->pidfile);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* dynamic defaults */
+ if (!opts->ciphers) {
+ opts->ciphers = strdup(DFLT_CIPHERS);
+ if (!opts->ciphers)
+ oom_die(argv0);
+ }
+ if (!opts->dropuser && !geteuid() && !getuid() &&
+ sys_isuser(DFLT_DROPUSER)) {
+#ifdef __APPLE__
+ /* Apple broke ioctl(/dev/pf) for EUID != 0 so we do not
+ * want to automatically drop privileges to nobody there
+ * if pf has been used in any proxyspec */
+ if (!nat_used("pf")) {
+#endif /* __APPLE__ */
+ opts->dropuser = strdup(DFLT_DROPUSER);
+ if (!opts->dropuser)
+ oom_die(argv0);
+#ifdef __APPLE__
+ }
+#endif /* __APPLE__ */
+ }
+ if (tfe_config_has_ssl_spec(opts) && opts->cakey && !opts->key) {
+ /*
+ * While browsers still generally accept it, use a leaf key
+ * size of 1024 bit for leaf keys. When browsers start to
+ * sunset 1024 bit RSA in leaf keys, we will need to make this
+ * value bigger, and/or configurable. Until then, users who
+ * want a different size can always use their own pre-generated
+ * leaf key instead of generating one.
+ */
+ opts->key = ssl_key_genrsa(1024);
+ if (!opts->key) {
+ fprintf(stderr, "%s: error generating RSA key:\n",
+ argv0);
+ ERR_print_errors_fp(stderr);
+ exit(EXIT_FAILURE);
+ }
+ if (OPTS_DEBUG(opts)) {
+ log_dbg_printf("Generated RSA key for leaf certs.\n");
+ }
+ }
+
+ if (opts->certgendir) {
+ char *keyid, *keyfn;
+ int prv;
+ FILE *keyf;
+
+ keyid = ssl_key_identifier(opts->key, 0);
+ if (!keyid) {
+ fprintf(stderr, "%s: error generating key id\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+
+ prv = asprintf(&keyfn, "%s/%s.key", opts->certgendir, keyid);
+ if (prv == -1) {
+ fprintf(stderr, "%s: %s (%i)\n", argv0,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!(keyf = fopen(keyfn, "w"))) {
+ fprintf(stderr, "%s: Failed to open '%s' for writing: "
+ "%s (%i)\n", argv0, keyfn,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ if (!PEM_write_PrivateKey(keyf, opts->key, NULL, 0, 0,
+ NULL, NULL)) {
+ fprintf(stderr, "%s: Failed to write key to '%s': "
+ "%s (%i)\n", argv0, keyfn,
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ fclose(keyf);
+ }
+
+ /* usage checks after defaults */
+ if (opts->dropgroup && !opts->dropuser) {
+ fprintf(stderr, "%s: -m depends on -u.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+
+ /* debugging */
+ if (OPTS_DEBUG(opts)) {
+ main_version();
+ tfe_config_proto_dbg_dump(opts);
+ log_dbg_printf("proxyspecs:\n");
+ for (proxyspec *spec = opts->spec; spec; spec = spec->next) {
+ char *specstr = proxyspec_str(spec);
+ if (!specstr) {
+ fprintf(stderr, "%s: out of memory\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+ log_dbg_printf("- %s\n", specstr);
+ free(specstr);
+ }
+ if (opts->cacrt) {
+ char *subj = ssl_x509_subject(opts->cacrt);
+ log_dbg_printf("Loaded CA: '%s'\n", subj);
+ free(subj);
+#ifdef DEBUG_CERTIFICATE
+ log_dbg_print_free(ssl_x509_to_str(opts->cacrt));
+ log_dbg_print_free(ssl_x509_to_pem(opts->cacrt));
+#endif /* DEBUG_CERTIFICATE */
+ } else {
+ log_dbg_printf("No CA loaded.\n");
+ }
+ }
+
+ /*
+ * Initialize as much as possible before daemon() in order to be
+ * able to provide direct feedback to the user when failing.
+ */
+ if (cachemgr_preinit() == -1) {
+ fprintf(stderr, "%s: failed to preinit cachemgr.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (log_preinit(opts) == -1) {
+ fprintf(stderr, "%s: failed to preinit logging.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+ if (nat_preinit() == -1) {
+ fprintf(stderr, "%s: failed to preinit NAT lookup.\n", argv0);
+ exit(EXIT_FAILURE);
+ }
+
+ /* Load certs before dropping privs but after cachemgr_preinit() */
+ if (opts->tgcrtdir) {
+ if (sys_dir_eachfile(opts->tgcrtdir,
+ main_loadtgcrt, opts) == -1) {
+ fprintf(stderr, "%s: failed to load certs from %s\n",
+ argv0, opts->tgcrtdir);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Detach from tty; from this point on, only canonicalized absolute
+ * paths should be used (-j, -F, -S). */
+ if (opts->detach) {
+ if (OPTS_DEBUG(opts)) {
+ log_dbg_printf("Detaching from TTY, see syslog for "
+ "errors after this point\n");
+ }
+ if (daemon(0, 0) == -1) {
+ fprintf(stderr, "%s: failed to detach from TTY: %s\n",
+ argv0, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ log_err_mode(LOG_ERR_MODE_SYSLOG);
+ }
+
+ if (opts->pidfile && (sys_pidf_write(pidfd) == -1)) {
+ log_err_printf("Failed to write PID to PID file '%s': %s (%i)"
+ "\n", opts->pidfile, strerror(errno), errno);
+ return -1;
+ }
+
+ /* Fork into parent monitor process and (potentially unprivileged)
+ * child process doing the actual work. We request 3 privsep client
+ * sockets: content logger thread, cert writer thread, and the child
+ * process main thread (main proxy thread) */
+ int clisock[3];
+ if (privsep_fork(opts, clisock, 3) != 0) {
+ /* parent has exited the monitor loop after waiting for child,
+ * or an error occured */
+ if (opts->pidfile) {
+ sys_pidf_close(pidfd, opts->pidfile);
+ }
+ goto out_parent;
+ }
+ /* child */
+
+ /* close pidfile in child */
+ if (opts->pidfile)
+ close(pidfd);
+
+ /* Initialize proxy before dropping privs */
+ proxy = proxy_new(opts, clisock[0]);
+ if (!proxy) {
+ log_err_printf("Failed to initialize proxy.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Drop privs, chroot */
+ if (sys_privdrop(opts->dropuser, opts->dropgroup,
+ opts->jaildir) == -1) {
+ log_err_printf("Failed to drop privileges: %s (%i)\n",
+ strerror(errno), errno);
+ exit(EXIT_FAILURE);
+ }
+ if (ssl_reinit() == -1) {
+ fprintf(stderr, "%s: failed to reinit SSL\n", argv0);
+ goto out_sslreinit_failed;
+ }
+
+ /* Post-privdrop/chroot/detach initialization, thread spawning */
+ if (log_init(opts, proxy, clisock[1], clisock[2]) == -1) {
+ fprintf(stderr, "%s: failed to init log facility: %s\n",
+ argv0, strerror(errno));
+ goto out_log_failed;
+ }
+ if (cachemgr_init() == -1) {
+ log_err_printf("Failed to init cache manager.\n");
+ goto out_cachemgr_failed;
+ }
+ if (nat_init() == -1) {
+ log_err_printf("Failed to init NAT state table lookup.\n");
+ goto out_nat_failed;
+ }
+ rv = EXIT_SUCCESS;
+
+ proxy_run(proxy);
+ proxy_free(proxy);
+ nat_fini();
+out_nat_failed:
+ cachemgr_fini();
+out_cachemgr_failed:
+ log_fini();
+out_sslreinit_failed:
+out_log_failed:
+out_parent:
+ tfe_config_free(opts);
+ ssl_fini();
+ return rv;
+}
+
+/* vim: set noet ft=c: */