/* * 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. */ #include "proxy.h" #include "privsep.h" #include "pxythrmgr.h" #include "pxyconn.h" #include "cachemgr.h" #include "opts.h" #include "log.h" #include "attrib.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Proxy engine, built around libevent 2.x. */ static int signals[] = { SIGQUIT, SIGHUP, SIGINT, SIGPIPE, SIGUSR1 }; struct proxy_ctx { pxy_thrmgr_ctx_t *thrmgr; struct event_base *evbase; struct event *sev[sizeof(signals)/sizeof(int)]; struct event *gcev; struct proxy_listener_ctx *lctx; opts_t *opts; }; static proxy_listener_ctx_t * proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec, opts_t *opts) MALLOC; static proxy_listener_ctx_t * proxy_listener_ctx_new(pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec, opts_t *opts) { proxy_listener_ctx_t *ctx = malloc(sizeof(proxy_listener_ctx_t)); if (!ctx) return NULL; memset(ctx, 0, sizeof(proxy_listener_ctx_t)); ctx->thrmgr = thrmgr; ctx->spec = spec; ctx->opts = opts; return ctx; } //static void //proxy_listener_ctx_free(proxy_listener_ctx_t *ctx) NONNULL(1); static void proxy_listener_ctx_free(proxy_listener_ctx_t *ctx) { if (ctx->evcl) { evconnlistener_free(ctx->evcl); } if (ctx->next) { proxy_listener_ctx_free(ctx->next); } free(ctx); } /* * Callback for error events on the socket listener bufferevent. */ static void proxy_listener_errorcb(struct evconnlistener *listener, UNUSED void *ctx) { proxy_conn_meta_ctx_t *mctx = ctx; int fd2 = -1; if (mctx) { fd2 = mctx->fd2; } log_dbg_level_printf(LOG_DBG_MODE_FINE, ">############################# proxy_listener_errorcb: ERROR, fd2=%d\n", fd2); struct event_base *evbase = evconnlistener_get_base(listener); int err = EVUTIL_SOCKET_ERROR(); log_err_printf("Error %d on listener: %s\n", err, evutil_socket_error_to_string(err)); event_base_loopbreak(evbase); } /* * Callback for accept events on the socket listener bufferevent. */ static void proxy_listener_acceptcb_e2(UNUSED struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *peeraddr, int peeraddrlen, void *arg) { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2() ENTER\n"); proxy_conn_meta_ctx_t *mctx = arg; // assert(mctx != NULL); if (!mctx) { log_dbg_level_printf(LOG_DBG_MODE_FINE, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2: NULL mctx <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< GONE\n"); return; } else { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2: ENTER 1 fd=%d, fd2=%d\n", mctx->fd, mctx->fd2); } // pthread_mutex_t *cmutex = &parent_ctx->thrmgr->mutex2; pthread_mutex_t *cmutex = &mctx->mutex; // @todo Enabling this lock causes ^C to fail?: Cannot quit the program on the command line using ^C int err = my_pthread_mutex_lock(cmutex); if (!mctx) { log_dbg_level_printf(LOG_DBG_MODE_FINE, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2: NULL mctx <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< GONE after lock\n"); goto leave; } else { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2: ENTER 2 fd=%d, fd2=%d\n", mctx->fd, mctx->fd2); } mctx->access_time = time(NULL); pxy_conn_ctx_t *parent_ctx = mctx->parent_ctx; evutil_socket_t pfd = -1; if (parent_ctx) { pfd = parent_ctx->fd; } log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2() lock err=%d\n", err); log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2(): child fd=%d, pfd=%d\n", fd, pfd); char *host, *port; if (sys_sockaddr_str(peeraddr, peeraddrlen, &host, &port) != 0) { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2(): PEER failed\n"); } else { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2(): PEER [%s]:%s <<<<< child fd=%d, pfd=%d\n", host, port, fd, pfd); free(host); free(port); } // pxy_conn_ctx_t *ctx = pxy_conn_setup_e2(fd, mctx); pxy_conn_setup_e2(fd, mctx); leave: log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>------------------------------------------------------------------------------------ proxy_listener_acceptcb_e2(): EXIT\n"); my_pthread_mutex_unlock(cmutex); } static proxy_conn_meta_ctx_t * pxy_conn_meta_ctx_new() { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>................... pxy_conn_meta_ctx_new(): ENTER <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); proxy_conn_meta_ctx_t *ctx = malloc(sizeof(proxy_conn_meta_ctx_t)); if (!ctx) return NULL; memset(ctx, 0, sizeof(proxy_conn_meta_ctx_t)); log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>................... pxy_conn_meta_ctx_new: sizeof(proxy_conn_meta_ctx_t)=%d <<<<<<\n", sizeof(proxy_conn_meta_ctx_t)); ctx->uuid = malloc(sizeof(uuid_t)); #ifdef OPENBSD uuid_create(ctx->uuid, NULL); char *uuid_str; uuid_to_string(ctx->uuid, &uuid_str, NULL); if (uuid_str) { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>................... pxy_conn_meta_ctx_new(): uuid = %s <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n", uuid_str); free(uuid_str); } #else uuid_generate(ctx->uuid); #endif /* OPENBSD */ ctx->access_time = time(NULL); ctx->next = NULL; pthread_mutex_init(&ctx->mutex, NULL); log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>>................... pxy_conn_meta_ctx_new(): EXIT <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<\n"); return ctx; } static void proxy_listener_acceptcb(UNUSED struct evconnlistener *listener, evutil_socket_t fd, struct sockaddr *peeraddr, int peeraddrlen, void *arg) { proxy_listener_ctx_t *lctx = arg; log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< LOCK\n"); pthread_mutex_lock(&lctx->thrmgr->mutex); time_t now = time(NULL); proxy_conn_meta_ctx_t *new_delete_list = NULL; pxy_thrmgr_get_elapsed_conns(lctx->thrmgr, &new_delete_list); pthread_mutex_unlock(&lctx->thrmgr->mutex); proxy_conn_meta_ctx_t *conn2del = new_delete_list; while (conn2del) { proxy_conn_meta_ctx_t *next = conn2del->delete; pthread_mutex_lock(&conn2del->mutex); log_dbg_level_printf(LOG_DBG_MODE_FINE, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: DELETE thr=%d, fd=%d, fd2=%d, time=%d <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< TIMED OUT\n", conn2del->thridx, conn2del->fd, conn2del->fd2, now - conn2del->access_time); pxy_all_conn_free(conn2del); // XXX: Releasing the lock causes callback functions to continue with a deleted mctx? //pthread_mutex_unlock(&conn2del->mutex); pthread_mutex_destroy(&conn2del->mutex); free(conn2del); conn2del = next; } // pthread_mutex_unlock(&lctx->thrmgr->mutex); // pthread_mutex_t *cmutex = &lctx->thrmgr->mutex2; proxy_conn_meta_ctx_t *mctx = pxy_conn_meta_ctx_new(); pthread_mutex_t *cmutex = &mctx->mutex; my_pthread_mutex_lock(cmutex); char *host, *port; if (sys_sockaddr_str(peeraddr, peeraddrlen, &host, &port) != 0) { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: PEER failed\n"); } else { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: PEER [%s]:%s <<<<< fd=%d\n", host, port, fd); free(host); free(port); } mctx->lctx = lctx; mctx->fd = fd; log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: SETTING UP E2, fd=%d, lctx->clisock=%d\n", fd, lctx->clisock); pxy_conn_ctx_t *parent_ctx = pxy_conn_setup(fd, peeraddr, peeraddrlen, mctx); mctx->parent_ctx = parent_ctx; evutil_socket_t fd2; struct evconnlistener *evcl2; log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: FIRST E2 setup <<<<<<\n"); if ((fd2 = privsep_client_opensock_e2(lctx->clisock, lctx->spec)) == -1) { log_err_printf("Error opening socket: %s (%i)\n", strerror(errno), errno); return; } mctx->fd2 = fd2; evcl2 = evconnlistener_new(evconnlistener_get_base(lctx->evcl), proxy_listener_acceptcb_e2, mctx, LEV_OPT_CLOSE_ON_FREE, 1024, fd2); if (!evcl2) { log_err_printf("Error creating evconnlistener e2: %s, fd=%d, fd2=%d <<<<<<\n", strerror(errno), fd, fd2); // proxy_listener_ctx_free(evcl2); evconnlistener_free(evcl2); evutil_closesocket(fd2); my_pthread_mutex_unlock(cmutex); return; } mctx->evcl2 = evcl2; evconnlistener_set_error_cb(evcl2, proxy_listener_errorcb); log_dbg_level_printf(LOG_DBG_MODE_FINER, ">>>>> !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! proxy_listener_acceptcb: FINISHED SETTING UP E2 SUCCESS, parent fd=%d, NEW fd2=%d\n", fd, fd2); my_pthread_mutex_unlock(cmutex); } /* * Dump a description of an evbase to debugging code. */ static void proxy_debug_base(const struct event_base *ev_base) { log_dbg_printf("Using libevent backend '%s'\n", event_base_get_method(ev_base)); enum event_method_feature f; f = event_base_get_features(ev_base); log_dbg_printf("Event base supports: edge %s, O(1) %s, anyfd %s\n", ((f & EV_FEATURE_ET) ? "yes" : "no"), ((f & EV_FEATURE_O1) ? "yes" : "no"), ((f & EV_FEATURE_FDS) ? "yes" : "no")); } /* * Set up the listener for a single proxyspec and add it to evbase. * Returns the proxy_listener_ctx_t pointer if successful, NULL otherwise. */ static proxy_listener_ctx_t * proxy_listener_setup(struct event_base *evbase, pxy_thrmgr_ctx_t *thrmgr, proxyspec_t *spec, opts_t *opts, int clisock) { log_dbg_level_printf(LOG_DBG_MODE_FINEST, ">>>>> proxy_listener_setup\n"); proxy_listener_ctx_t *lctx; int fd; if ((fd = privsep_client_opensock(clisock, spec)) == -1) { log_err_printf("Error opening socket: %s (%i)\n", strerror(errno), errno); return NULL; } lctx = proxy_listener_ctx_new(thrmgr, spec, opts); if (!lctx) { log_err_printf("Error creating listener context\n"); evutil_closesocket(fd); return NULL; } // pthread_mutex_init(&lctx->mutex, NULL); lctx->clisock = clisock; lctx->evcl = evconnlistener_new(evbase, proxy_listener_acceptcb, lctx, LEV_OPT_CLOSE_ON_FREE, 1024, fd); // @todo Should we enable threadsafe event structs? // lctx, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_THREADSAFE, 1024, fd); if (!lctx->evcl) { log_err_printf("Error creating evconnlistener: %s\n", strerror(errno)); proxy_listener_ctx_free(lctx); evutil_closesocket(fd); return NULL; } evconnlistener_set_error_cb(lctx->evcl, proxy_listener_errorcb); return lctx; } /* * Signal handler for SIGQUIT, SIGINT, SIGHUP, SIGPIPE and SIGUSR1. */ static void proxy_signal_cb(evutil_socket_t fd, UNUSED short what, void *arg) { proxy_ctx_t *ctx = arg; if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Received signal %i\n", fd); } switch(fd) { case SIGQUIT: case SIGINT: case SIGHUP: proxy_loopbreak(ctx); break; case SIGUSR1: if (log_reopen() == -1) { log_err_printf("Warning: Failed to reopen logs\n"); } else { log_dbg_printf("Reopened log files\n"); } break; case SIGPIPE: log_err_printf("Warning: Received SIGPIPE; ignoring.\n"); break; default: log_err_printf("Warning: Received unexpected signal %i\n", fd); break; } } /* * Garbage collection handler. */ static void proxy_gc_cb(UNUSED evutil_socket_t fd, UNUSED short what, void *arg) { proxy_ctx_t *ctx = arg; if (OPTS_DEBUG(ctx->opts)) log_dbg_printf("Garbage collecting caches started.\n"); cachemgr_gc(); if (OPTS_DEBUG(ctx->opts)) log_dbg_printf("Garbage collecting caches done.\n"); } /* * Set up the core event loop. * Socket clisock is the privsep client socket used for binding to ports. * Returns ctx on success, or NULL on error. */ proxy_ctx_t * proxy_new(opts_t *opts, int clisock) { proxy_listener_ctx_t *head; proxy_ctx_t *ctx; struct evdns_base *dnsbase; int rc; /* adds locking, only required if accessed from separate threads */ evthread_use_pthreads(); #ifndef PURIFY if (OPTS_DEBUG(opts)) { event_enable_debug_mode(); } #endif /* PURIFY */ ctx = malloc(sizeof(proxy_ctx_t)); if (!ctx) { log_err_printf("Error allocating memory\n"); goto leave0; } memset(ctx, 0, sizeof(proxy_ctx_t)); ctx->opts = opts; ctx->evbase = event_base_new(); if (!ctx->evbase) { log_err_printf("Error getting event base\n"); goto leave1; } if (opts_has_dns_spec(opts)) { /* create a dnsbase here purely for being able to test parsing * resolv.conf while we can still alert the user about it. */ dnsbase = evdns_base_new(ctx->evbase, 0); if (!dnsbase) { log_err_printf("Error creating dns event base\n"); goto leave1b; } rc = evdns_base_resolv_conf_parse(dnsbase, DNS_OPTIONS_ALL, "/etc/resolv.conf"); evdns_base_free(dnsbase, 0); if (rc != 0) { log_err_printf("evdns cannot parse resolv.conf: " "%s (%d)\n", rc == 1 ? "failed to open file" : rc == 2 ? "failed to stat file" : rc == 3 ? "file too large" : rc == 4 ? "out of memory" : rc == 5 ? "short read from file" : rc == 6 ? "no nameservers in file" : "unknown error", rc); goto leave1b; } } if (OPTS_DEBUG(opts)) { proxy_debug_base(ctx->evbase); } ctx->thrmgr = pxy_thrmgr_new(opts); if (!ctx->thrmgr) { log_err_printf("Error creating thread manager\n"); goto leave1b; } head = ctx->lctx = NULL; for (proxyspec_t *spec = opts->spec; spec; spec = spec->next) { head = proxy_listener_setup(ctx->evbase, ctx->thrmgr, spec, opts, clisock); if (!head) goto leave2; head->next = ctx->lctx; ctx->lctx = head; char *specstr = proxyspec_str(spec); if (!specstr) { fprintf(stderr, "out of memory\n"); exit(EXIT_FAILURE); } log_dbg_printf(">>>>> proxy_listener_setup - %s\n", specstr); free(specstr); } for (size_t i = 0; i < (sizeof(signals) / sizeof(int)); i++) { ctx->sev[i] = evsignal_new(ctx->evbase, signals[i], proxy_signal_cb, ctx); if (!ctx->sev[i]) goto leave3; evsignal_add(ctx->sev[i], NULL); } struct timeval gc_delay = {60, 0}; ctx->gcev = event_new(ctx->evbase, -1, EV_PERSIST, proxy_gc_cb, ctx); if (!ctx->gcev) goto leave4; evtimer_add(ctx->gcev, &gc_delay); // @attention Do not close privsep sock, the client binds to new sockets on the egress path //privsep_client_close(clisock); return ctx; leave4: if (ctx->gcev) { event_free(ctx->gcev); } leave3: for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) { if (ctx->sev[i]) { event_free(ctx->sev[i]); } } leave2: if (ctx->lctx) { proxy_listener_ctx_free(ctx->lctx); } pxy_thrmgr_free(ctx->thrmgr); leave1b: event_base_free(ctx->evbase); leave1: free(ctx); leave0: return NULL; } /* * Run the event loop. Returns when the event loop is cancelled by a signal * or on failure. */ void proxy_run(proxy_ctx_t *ctx) { if (ctx->opts->detach) { event_reinit(ctx->evbase); } #ifndef PURIFY if (OPTS_DEBUG(ctx->opts)) { event_base_dump_events(ctx->evbase, stderr); } #endif /* PURIFY */ if (pxy_thrmgr_run(ctx->thrmgr) == -1) { log_err_printf("Failed to start thread manager\n"); return; } if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Starting main event loop.\n"); } event_base_dispatch(ctx->evbase); if (OPTS_DEBUG(ctx->opts)) { log_dbg_printf("Main event loop stopped.\n"); } } /* * Break the loop of the proxy, causing the proxy_run to return. */ void proxy_loopbreak(proxy_ctx_t *ctx) { event_base_loopbreak(ctx->evbase); } /* * Free the proxy data structures. */ void proxy_free(proxy_ctx_t *ctx) { if (ctx->gcev) { event_free(ctx->gcev); } if (ctx->lctx) { proxy_listener_ctx_free(ctx->lctx); } for (size_t i = 0; i < (sizeof(ctx->sev) / sizeof(ctx->sev[0])); i++) { if (ctx->sev[i]) { event_free(ctx->sev[i]); } } if (ctx->thrmgr) { pxy_thrmgr_free(ctx->thrmgr); } if (ctx->evbase) { event_base_free(ctx->evbase); } free(ctx); } /* vim: set noet ft=c: */