/* * SSLsplit - transparent SSL/TLS interception * Copyright (c) 2009-2016, Daniel Roethlisberger * All rights reserved. * http://www.roe.ch/SSLsplit * * 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 ``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 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 "proc.h" #include "cachemgr.h" #include "sys.h" #include "log.h" #include "version.h" #include "defaults.h" #include #include #include #include #include #ifndef __BSD__ #include #endif /* !__BSD__ */ #include #include #include #if __APPLE__ #undef daemon extern int daemon(int, int); #endif /* __APPLE__ */ int descriptor_table_size = 0; /* * Print version information to stderr. */ static void main_version(void) { fprintf(stderr, "%s %s (built %s)\n", PNAME, version, build_date); if (strlen(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 sslproxy!\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-2016, " "Daniel Roethlisberger \n"); fprintf(stderr, "http://www.roe.ch/SSLsplit\n"); if (build_info[0]) { fprintf(stderr, "Build info: %s\n", build_info); } if (features[0]) { fprintf(stderr, "Features: %s\n", 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; 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, "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" " -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/sslproxy/%%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/sslproxy/%%T-%%s-%%d.log\"\n" #define OPT_i #endif /* HAVE_LOCAL_PROCINFO */ " -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", BNAME, dflt, BNAME, 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) { opts_t *opts = arg; cert_t *cert; char **names; cert = cert_new_load(filename); if (!cert) { log_err_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "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); } static void set_cacrt(opts_t *opts, const char *argv0, char *optarg) { 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 */ fprintf(stderr, "cacrt: %s\n", optarg); } static void set_cakey(opts_t *opts, const char *argv0, char *optarg) { 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 */ fprintf(stderr, "cakey: %s\n", optarg); } static void set_user(opts_t *opts, const char *argv0, char *optarg) { 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); fprintf(stderr, "dropuser: %s\n", opts->dropuser); } static void set_group(opts_t *opts, const char *argv0, char *optarg) { 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); fprintf(stderr, "dropgroup: %s\n", opts->dropgroup); } static void set_pidfile(opts_t *opts, const char *argv0, char *optarg) { if (opts->pidfile) free(opts->pidfile); opts->pidfile = strdup(optarg); if (!opts->pidfile) oom_die(argv0); fprintf(stderr, "pidfile: %s\n", opts->pidfile); } static int load_conffile(opts_t *opts, const char *argv0, const char *natengine) { FILE *f; int rv, line_num, found; size_t line_len; char *n, *value, *v, *value_end; char *line, *name; f = fopen(opts->conffile, "r"); if (!f) { fprintf(stderr, "Error opening conf file %s: %s\n", opts->conffile, strerror(errno)); return -1; } line = NULL; line_num = 0; while (!feof(f)) { rv = getline(&line, &line_len, f); if (rv == -1) { break; } if (line == NULL) { fprintf(stderr, "getline() buf=NULL"); return -1; } line_num++; // skip white space for (name = line; *name == ' ' || *name == '\t'; name++); // skip comments and empty lines if ((name[0] == '\0') || (name[0] == '#') || (name[0] == ';') || (name[0] == '\r') || (name[0] == '\n')) { continue; } // skip to the end of option name and terminate it with '\0' for (n = name;; n++) { if (*n == ' ' || *n == '\t') { *n = '\0'; n++; break; } if (*n == '\0') { n = NULL; break; } } // no value if (n == NULL) { fprintf(stderr, "Conf error at line %d\n", line_num); fclose(f); if (line) { free(line); } return -1; } // skip white space before value while (*n == ' ' || *n == '\t') { n++; } value = n; // find end of value and terminate it with '\0' // find first occurrence of trailing white space value_end = NULL; for (v = value;; v++) { if (*v == '\0') { break; } if (*v == '\r' || *v == '\n') { *v = '\0'; break; } if (*v == ' ' || *v == '\t') { if (!value_end) { value_end = v; } } else { value_end = NULL; } } if (value_end) { *value_end = '\0'; } found = 0; if (!strncasecmp(name, "CACert", 6)) { set_cacrt(opts, argv0, value); found = 1; } else if (!strncasecmp(name, "CAKey", 5)) { set_cakey(opts, argv0, value); found = 1; } else if (!strncasecmp(name, "ProxySpec", 9)) { char **argv = malloc(strlen(value) + 1); char **save_argv = argv; int argc = 0; char *p, *last; for ((p = strtok_r(value, " ", &last)); p; (p = strtok_r(NULL, " ", &last))) { // Limit max # token if (argc < 10) { argv[argc++] = p; } } proxyspec_parse(&argc, &argv, natengine, opts); free(save_argv); found = 1; } else if (!strncasecmp(name, "ConnIdleTimeout", 15)) { unsigned int rv = atoi(value); if (rv >= 10 && rv <= 3600) { opts->conn_idle_timeout = rv; } else { fprintf(stderr, "Invalid ConnIdleTimeout %s at line %d, use 10-3600\n", value, line_num); } fprintf(stderr, "ConnIdleTimeout: %u\n", opts->conn_idle_timeout); found = 1; } else if (!strncasecmp(name, "ExpiredConnCheckPeriod", 22)) { unsigned int rv = atoi(value); if (rv >= 10 && rv <= 60) { opts->expired_conn_check_period = rv; } else { fprintf(stderr, "Invalid ExpiredConnCheckPeriod %s at line %d, use 10-60\n", value, line_num); } fprintf(stderr, "ExpiredConnCheckPeriod: %u\n", opts->expired_conn_check_period); found = 1; } else if (!strncasecmp(name, "SSLShutdownRetryDelay", 21)) { unsigned int rv = atoi(value); if (rv >= 100 && rv <= 10000) { opts->ssl_shutdown_retry_delay = rv; } else { fprintf(stderr, "Invalid SSLShutdownRetryDelay %s at line %d, use 100-10000\n", value, line_num); } fprintf(stderr, "SSLShutdownRetryDelay: %u\n", opts->ssl_shutdown_retry_delay); found = 1; } else if (!strncasecmp(name, "PidFile", 7)) { set_pidfile(opts, argv0, value); found = 1; } else if (!strncasecmp(name, "LogStats", 8)) { if (!strncasecmp(value, "yes", 3)) { opts->statslog = 1; } else if (!strncasecmp(value, "no", 3)) { opts->statslog = 0; } else { fprintf(stderr, "Invalid LogStats %s at line %d, use yes|no\n", value, line_num); } fprintf(stderr, "LogStats: %u\n", opts->statslog); found = 1; } else if (!strncasecmp(name, "StatsPeriod", 11)) { unsigned int rv = atoi(value); if (rv >= 1 && rv <= 10) { opts->stats_period = rv; } else { fprintf(stderr, "Invalid StatsPeriod %s at line %d, use 1-10\n", value, line_num); } fprintf(stderr, "StatsPeriod: %u\n", opts->stats_period); found = 1; } else if (!strncasecmp(name, "User", 4)) { set_user(opts, argv0, value); found = 1; } else if (!strncasecmp(name, "Group", 5)) { set_group(opts, argv0, value); found = 1; } else if (!strncasecmp(name, "RemoveHTTPAcceptEncoding", 24)) { if (!strncasecmp(value, "yes", 3)) { opts->remove_http_accept_encoding = 1; } else if (!strncasecmp(value, "no", 3)) { opts->remove_http_accept_encoding = 0; } else { fprintf(stderr, "Invalid RemoveHTTPAcceptEncoding %s at line %d, use yes|no\n", value, line_num); } fprintf(stderr, "RemoveHTTPAcceptEncoding: %u\n", opts->remove_http_accept_encoding); found = 1; } else if (!strncasecmp(name, "RemoveHTTPReferer", 17)) { if (!strncasecmp(value, "yes", 3)) { opts->remove_http_referer = 1; } else if (!strncasecmp(value, "no", 3)) { opts->remove_http_referer = 0; } else { fprintf(stderr, "Invalid RemoveHTTPReferer %s at line %d, use yes|no\n", value, line_num); } fprintf(stderr, "RemoveHTTPReferer: %u\n", opts->remove_http_referer); found = 1; } if (found) { continue; } fprintf(stderr, "Unknown option '%s' at %s line %d\n", name, opts->conffile, line_num); fclose(f); if (line) { free(line); } return -1; } fclose(f); if (line) { free(line); } return 0; } /* * Main entry point. */ int main(int argc, char *argv[]) { const char *argv0; int ch; opts_t *opts; char *natengine; int pidfd = -1; int rv = EXIT_FAILURE; argv0 = argv[0]; opts = opts_new(); if (nat_getdefaultname()) { natengine = strdup(nat_getdefaultname()); if (!natengine) oom_die(argv0); } else { natengine = NULL; } // Set defaults opts->conn_idle_timeout = 120; opts->expired_conn_check_period = 10; opts->ssl_shutdown_retry_delay = 100; opts->log_stats = 0; opts->stats_period = 1; opts->remove_http_accept_encoding = 1; opts->remove_http_referer = 1; 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:dD::VhW:w:If:")) != -1) { switch (ch) { case 'f': if (opts->conffile) free(opts->conffile); opts->conffile = strdup(optarg); if (!opts->conffile) oom_die(argv0); fprintf(stderr, "Conf file: %s\n", opts->conffile); break; case 'c': set_cacrt(opts, argv0, optarg); break; case 'k': set_cakey(opts, argv0, optarg); 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 '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': opts_proto_force(opts, optarg, argv0); break; case 'R': opts_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': set_user(opts, argv0, optarg); break; case 'm': set_group(opts, argv0, optarg); break; case 'p': set_pidfile(opts, argv0, optarg); 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 'I': opts->statslog = 1; 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 'd': opts->detach = 1; break; case 'D': opts->debug = 1; if (optarg && strncmp(optarg, "2", 1) == 0) { log_dbg_mode(LOG_DBG_MODE_FINE); } else if (optarg && strncmp(optarg, "3", 1) == 0) { log_dbg_mode(LOG_DBG_MODE_FINER); } else if (optarg && strncmp(optarg, "4", 1) == 0) { log_dbg_mode(LOG_DBG_MODE_FINEST); } else { log_dbg_mode(LOG_DBG_MODE_ERRLOG); } 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; proxyspec_parse(&argc, &argv, natengine, opts); if (opts->conffile) { if (load_conffile(opts, argv0, natengine) == -1) { exit(EXIT_FAILURE); } } /* 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_t *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 (opts_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 (opts_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(); opts_proto_dbg_dump(opts); log_dbg_printf("proxyspecs:\n"); for (proxyspec_t *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_level_printf(LOG_CRIT, "Failed to write PID to PID file '%s': %s (%i)" "\n", opts->pidfile, strerror(errno), errno); return -1; } descriptor_table_size = getdtablesize(); /* 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_ctx_t *proxy = proxy_new(opts, clisock[0]); if (!proxy) { log_err_level_printf(LOG_CRIT, "Failed to initialize proxy.\n"); exit(EXIT_FAILURE); } /* Drop privs, chroot */ if (sys_privdrop(opts->dropuser, opts->dropgroup, opts->jaildir) == -1) { log_err_level_printf(LOG_CRIT, "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_level_printf(LOG_CRIT, "Failed to init cache manager.\n"); goto out_cachemgr_failed; } if (nat_init() == -1) { log_err_level_printf(LOG_CRIT, "Failed to init NAT state table lookup.\n"); goto out_nat_failed; } rv = EXIT_SUCCESS; proxy_run(proxy); #ifdef DEBUG_PROXY log_dbg_level_printf(LOG_DBG_MODE_FINEST, "main: EXIT closing privsep clisock=%d\n", clisock[0]); #endif /* DEBUG_PROXY */ privsep_client_close(clisock[0]); proxy_free(proxy); nat_fini(); out_nat_failed: cachemgr_fini(); out_cachemgr_failed: log_fini(); out_sslreinit_failed: out_log_failed: out_parent: opts_free(opts); ssl_fini(); return rv; } /* vim: set noet ft=c: */