/*- * SSLsplit - transparent SSL/TLS interception * https://www.roe.ch/SSLsplit * * Copyright (c) 2009-2018, Daniel Roethlisberger . * 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 "sys.h" #include "log.h" #include "build.h" #include "defaults.h" #include #include #include #include #include #ifndef __BSD__ #include #endif /* !__BSD__ */ #include #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 lp!\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) 2017-2019, Soner Tari \n"); fprintf(stderr, "https://github.com/sonertari/SSLproxy\n"); fprintf(stderr, "Copyright (c) 2009-2018, " "Daniel Roethlisberger \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); } 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 *usagefmt = "Usage: %s [-D] [-f conffile] [-o opt=val] [options...] [proxyspecs...]\n" " -f conffile use conffile to load configuration from\n" " -o opt=val override conffile option opt with value val\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" " -J enable connection statistics logging\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" " %%%% - literal '%%'\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 = listenaddr+port\n" " e.g. 127.0.0.1 8080 # tcp/4; static\n" " # et al\n" "Example:\n" " %s 127.0.0.1 8080\n"; fprintf(stderr, usagefmt, build_pkgname, build_pkgname); } /* * Main entry point. */ int main(int argc, char *argv[]) { const char *argv0; int ch; opts_t *opts; int pidfd = -1; int rv = EXIT_FAILURE; argv0 = argv[0]; opts = opts_new(); while ((ch = getopt(argc, argv, "u:m:j:p:l:L:S:F:dD::Vhf:o:J")) != -1) { switch (ch) { case 'f': if (opts->conffile) free(opts->conffile); opts->conffile = strdup(optarg); if (!opts->conffile) oom_die(argv0); if (opts_load_conffile(opts, argv0) == -1) { exit(EXIT_FAILURE); } #ifdef DEBUG_OPTS log_dbg_printf("Conf file: %s\n", opts->conffile); #endif /* DEBUG_OPTS */ break; case 'o': if (opts_set_option(opts, argv0, optarg) == -1) { exit(EXIT_FAILURE); } break; case 'u': opts_set_user(opts, argv0, optarg); break; case 'm': opts_set_group(opts, argv0, optarg); break; case 'p': opts_set_pidfile(opts, argv0, optarg); break; case 'j': opts_set_jaildir(opts, argv0, optarg); break; case 'l': opts_set_connectlog(opts, argv0, optarg); break; case 'J': opts_set_statslog(opts); break; case 'L': opts_set_contentlog(opts, argv0, optarg); break; case 'S': opts_set_contentlogdir(opts, argv0, optarg); break; case 'F': opts_set_contentlogpathspec(opts, argv0, optarg); break; case 'd': opts_set_daemon(opts); break; case 'D': opts_set_debug(opts); if (optarg) { opts_set_debug_level(optarg); } 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, &opts->spec); /* 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) continue; } #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); } } 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->dropuser && sys_isgeteuid(opts->dropuser)) { if (opts->dropgroup) { fprintf(stderr, "%s: cannot use -m when -u is " "current user\n", argv0); exit(EXIT_FAILURE); } free(opts->dropuser); opts->dropuser = NULL; } /* usage checks after defaults */ if (opts->dropgroup && !opts->dropuser) { fprintf(stderr, "%s: -m depends on -u\n", argv0); exit(EXIT_FAILURE); } /* Warn about options that require per-connection privileged operations * to be executed through privsep, but only if dropuser is set and is * not root, because privsep will fastpath in that situation, skipping * the latency-incurring overhead. */ int privsep_warn = 0; if (opts->dropuser) { if (opts->contentlog_isdir) { log_dbg_printf("| Warning: -F requires a privileged " "operation for each connection!\n"); privsep_warn = 1; } if (opts->contentlog_isspec) { log_dbg_printf("| Warning: -S requires a privileged " "operation for each connection!\n"); privsep_warn = 1; } } if (privsep_warn) { log_dbg_printf("| Privileged operations require communication " "between parent and child process\n" "| and will negatively impact latency and " "performance on each connection.\n"); } /* debug log, part 1 */ if (OPTS_DEBUG(opts)) { main_version(); } /* debug log, part 2 */ if (OPTS_DEBUG(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); } } /* * Initialize as much as possible before daemon() in order to be * able to provide direct feedback to the user when failing. */ if (log_preinit(opts) == -1) { fprintf(stderr, "%s: failed to preinit logging.\n", argv0); 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 6 privsep client * sockets: five logger threads, and the child process main thread, * which will become the main proxy thread. First slot is main thread, * remaining slots are passed down to log subsystem. */ int clisock[6]; if (privsep_fork(opts, clisock, sizeof(clisock)/sizeof(clisock[0])) != 0) { /* parent has exited the monitor loop after waiting for child, * or an error occurred */ 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); } log_dbg_printf("Dropped privs to user %s group %s chroot %s\n", opts->dropuser ? opts->dropuser : "-", opts->dropgroup ? opts->dropgroup : "-", opts->jaildir ? opts->jaildir : "-"); /* Post-privdrop/chroot/detach initialization, thread spawning */ if (log_init(opts, proxy, &clisock[1]) == -1) { fprintf(stderr, "%s: failed to init log facility: %s\n", argv0, strerror(errno)); goto out_log_failed; } rv = EXIT_SUCCESS; proxy_run(proxy); proxy_free(proxy); log_fini(); out_log_failed: out_parent: opts_free(opts); return rv; } /* vim: set noet ft=c: */