/*- * SSLsplit - transparent SSL/TLS interception * https://www.roe.ch/SSLsplit * * Copyright (c) 2009-2019, Daniel Roethlisberger . * Copyright (c) 2017-2024, Soner Tari . * 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. */ #include "protopassthrough.h" #include "prototcp.h" #include #ifdef HAVE_LOCAL_PROCINFO static int NONNULL(1) protopassthrough_prepare_logging(pxy_conn_ctx_t *ctx) { /* prepare logging, part 2 */ if (WANT_CONNECT_LOG(ctx)) { return pxy_prepare_logging_local_procinfo(ctx); } return 0; } #endif /* HAVE_LOCAL_PROCINFO */ static void NONNULL(1) protopassthrough_log_dbg_connect_type(pxy_conn_ctx_t *ctx) { if (OPTS_DEBUG(ctx->global)) { /* for TCP, we get only a dst connect event, * since src was already connected from the * beginning */ log_dbg_printf("PASSTHROUGH connected to [%s]:%s\n", STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str)); log_dbg_printf("PASSTHROUGH connected from [%s]:%s\n", STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str)); } } static void NONNULL(1) protopassthrough_log_connect(pxy_conn_ctx_t *ctx) { if (WANT_CONNECT_LOG(ctx)) { pxy_log_connect_nonhttp(ctx); } protopassthrough_log_dbg_connect_type(ctx); } /* * We cannot redirect failed ssl connections to login page while switching * to passthrough mode, because redirect message should be sent over ssl, * but it has failed (that's why we are engaging the passthrough mode). */ void protopassthrough_engage(pxy_conn_ctx_t *ctx) { log_fine("ENTER"); // Free any children of the previous proto pxy_conn_free_children(ctx); // In split mode, srvdst is used as dst, so it should be freed as dst below // If srvdst has been xferred to the first child conn, the child should free it, not the parent if (ctx->srvdst.bev) { ctx->srvdst.free(ctx->srvdst.bev, ctx); ctx->srvdst.bev = NULL; } ctx->srvdst.ssl = NULL; ctx->connected = 0; // Make sure bev is not NULL, as dst may not have been initialized yet if (ctx->dst.bev) { ctx->dst.free(ctx->dst.bev, ctx); ctx->dst.bev = NULL; ctx->dst_fd = 0; ctx->dst.closed = 1; } // Free any/all data of the previous proto if (ctx->protoctx->proto_free) { ctx->protoctx->proto_free(ctx); // Disable proto_free callback of the previous proto, otherwise it is called while passthrough is closing too ctx->protoctx->proto_free = NULL; } ctx->proto = protopassthrough_setup(ctx); pxy_conn_connect(ctx); } static int NONNULL(1) WUNRES protopassthrough_conn_connect(pxy_conn_ctx_t *ctx) { log_finest("ENTER"); if (prototcp_setup_srvdst(ctx) == -1) { return -1; } bufferevent_setcb(ctx->srvdst.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx); return 0; } static void NONNULL(1) protopassthrough_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_finest_va("ENTER, size=%zu", evbuffer_get_length(bufferevent_get_input(bev))); // Passthrough packets are transferred between src and srvdst if (ctx->srvdst.closed) { ctx->protoctx->discard_inbufcb(bev); return; } #ifndef WITHOUT_USERAUTH if (prototcp_try_send_userauth_msg(bev, ctx)) { return; } #endif /* !WITHOUT_USERAUTH */ if (pxy_conn_apply_deferred_block_action(ctx)) { return; } evbuffer_add_buffer(bufferevent_get_output(ctx->srvdst.bev), bufferevent_get_input(bev)); ctx->protoctx->set_watermarkcb(bev, ctx, ctx->srvdst.bev); } static void NONNULL(1) protopassthrough_bev_readcb_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_finest_va("ENTER, size=%zu", evbuffer_get_length(bufferevent_get_input(bev))); // Passthrough packets are transferred between src and srvdst if (ctx->src.closed) { ctx->protoctx->discard_inbufcb(bev); return; } evbuffer_add_buffer(bufferevent_get_output(ctx->src.bev), bufferevent_get_input(bev)); ctx->protoctx->set_watermarkcb(bev, ctx, ctx->src.bev); } static void NONNULL(1) protopassthrough_bev_writecb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_finest("ENTER"); #ifndef WITHOUT_USERAUTH if (prototcp_try_close_unauth_conn(bev, ctx)) { return; } #endif /* !WITHOUT_USERAUTH */ // @attention srvdst.bev may be NULL if (ctx->srvdst.closed) { if (pxy_try_close_conn_end(&ctx->src, ctx)) { log_finest("srvdst.closed, terminate conn"); pxy_conn_term(ctx, 1); } return; } ctx->protoctx->unset_watermarkcb(bev, ctx, &ctx->srvdst); } static void NONNULL(1) protopassthrough_bev_writecb_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_finest("ENTER"); if (ctx->src.closed) { if (pxy_try_close_conn_end(&ctx->srvdst, ctx) == 1) { log_finest("src.closed, terminate conn"); pxy_conn_term(ctx, 0); } return; } ctx->protoctx->unset_watermarkcb(bev, ctx, &ctx->src); } static void NONNULL(1,2) protopassthrough_bev_eventcb_connected_src(UNUSED struct bufferevent *bev, UNUSED pxy_conn_ctx_t *ctx) { log_finest("ENTER"); } static int NONNULL(1) protopassthrough_enable_src(pxy_conn_ctx_t *ctx) { log_finest("ENTER"); if (prototcp_setup_src(ctx) == -1) { return -1; } bufferevent_setcb(ctx->src.bev, pxy_bev_readcb, pxy_bev_writecb, pxy_bev_eventcb, ctx); log_finer("Enabling src"); // Now open the gates bufferevent_enable(ctx->src.bev, EV_READ|EV_WRITE); return 0; } #ifndef WITHOUT_USERAUTH static void NONNULL(1) protopassthrough_classify_user(pxy_conn_ctx_t *ctx) { // Do not re-engage passthrough mode in passthrough mode if (ctx->spec->opts->passusers && !pxy_is_listuser(ctx->spec->opts->passusers, ctx->user #ifdef DEBUG_PROXY , ctx, "PassUsers" #endif /* DEBUG_PROXY */ ) && ctx->spec->opts->divertusers && !pxy_is_listuser(ctx->spec->opts->divertusers, ctx->user #ifdef DEBUG_PROXY , ctx, "DivertUsers" #endif /* DEBUG_PROXY */ )) { log_fine_va("User %s not in PassUsers or DivertUsers; terminating connection", ctx->user); pxy_conn_term(ctx, 1); } } #endif /* !WITHOUT_USERAUTH */ static void NONNULL(1,2) protopassthrough_bev_eventcb_connected_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_finest("ENTER"); #ifndef WITHOUT_USERAUTH pxy_userauth(ctx); if (ctx->term || ctx->enomem) { return; } #endif /* !WITHOUT_USERAUTH */ ctx->connected = 1; bufferevent_enable(bev, EV_READ|EV_WRITE); // Do not re-enable src if it is already enabled, e.g. in autossl if (!ctx->src.bev && protopassthrough_enable_src(ctx) == -1) { return; } } static void NONNULL(1,2) protopassthrough_bev_eventcb_eof_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_finest("ENTER"); ctx->protoctx->log_dbg_evbuf_infocb(ctx, &ctx->src, &ctx->srvdst); #endif /* DEBUG_PROXY */ if (!ctx->connected) { log_err_level(LOG_WARNING, "EOF on outbound connection before connection establishment"); ctx->srvdst.closed = 1; } else if (!ctx->srvdst.closed) { log_finest("!srvdst.closed, terminate conn"); if (pxy_try_consume_last_input(bev, ctx) == -1) { return; } pxy_try_close_conn_end(&ctx->srvdst, ctx); } pxy_try_disconnect(ctx, &ctx->src, &ctx->srvdst, 1); } static void NONNULL(1,2) protopassthrough_bev_eventcb_eof_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx) { #ifdef DEBUG_PROXY log_finest("ENTER"); ctx->protoctx->log_dbg_evbuf_infocb(ctx, &ctx->srvdst, &ctx->src); #endif /* DEBUG_PROXY */ if (!ctx->connected) { log_err_level(LOG_WARNING, "EOF on outbound connection before connection establishment"); ctx->src.closed = 1; } else if (!ctx->src.closed) { log_finest("!src.closed, terminate conn"); if (pxy_try_consume_last_input(bev, ctx) == -1) { return; } pxy_try_close_conn_end(&ctx->src, ctx); } pxy_try_disconnect(ctx, &ctx->srvdst, &ctx->src, 0); } static void NONNULL(1,2) protopassthrough_bev_eventcb_error_src(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_fine("ENTER"); // Passthrough packets are transferred between src and srvdst if (!ctx->connected) { ctx->srvdst.closed = 1; } else if (!ctx->srvdst.closed) { pxy_try_close_conn_end(&ctx->srvdst, ctx); } pxy_try_disconnect(ctx, &ctx->src, &ctx->srvdst, 1); } static void NONNULL(1,2) protopassthrough_bev_eventcb_error_srvdst(UNUSED struct bufferevent *bev, pxy_conn_ctx_t *ctx) { log_fine("ENTER"); // Passthrough packets are transferred between src and srvdst if (!ctx->connected) { ctx->src.closed = 1; } else if (!ctx->src.closed) { pxy_try_close_conn_end(&ctx->src, ctx); } pxy_try_disconnect(ctx, &ctx->srvdst, &ctx->src, 0); } static void NONNULL(1) protopassthrough_bev_readcb(struct bufferevent *bev, void *arg) { pxy_conn_ctx_t *ctx = arg; if (bev == ctx->src.bev) { protopassthrough_bev_readcb_src(bev, ctx); } else if (bev == ctx->srvdst.bev) { protopassthrough_bev_readcb_srvdst(bev, ctx); } else { log_err_printf("protopassthrough_bev_readcb: UNKWN conn end\n"); } } static void NONNULL(1) protopassthrough_bev_writecb(struct bufferevent *bev, void *arg) { pxy_conn_ctx_t *ctx = arg; if (bev == ctx->src.bev) { protopassthrough_bev_writecb_src(bev, ctx); } else if (bev == ctx->srvdst.bev) { protopassthrough_bev_writecb_srvdst(bev, ctx); } else { log_err_printf("protopassthrough_bev_writecb: UNKWN conn end\n"); } } static void NONNULL(1) protopassthrough_bev_eventcb_src(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx) { if (events & BEV_EVENT_CONNECTED) { protopassthrough_bev_eventcb_connected_src(bev, ctx); } else if (events & BEV_EVENT_EOF) { protopassthrough_bev_eventcb_eof_src(bev, ctx); } else if (events & BEV_EVENT_ERROR) { protopassthrough_bev_eventcb_error_src(bev, ctx); } } static void NONNULL(1) protopassthrough_bev_eventcb_srvdst(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx) { if (events & BEV_EVENT_CONNECTED) { protopassthrough_bev_eventcb_connected_srvdst(bev, ctx); } else if (events & BEV_EVENT_EOF) { protopassthrough_bev_eventcb_eof_srvdst(bev, ctx); } else if (events & BEV_EVENT_ERROR) { protopassthrough_bev_eventcb_error_srvdst(bev, ctx); } } static void NONNULL(1) protopassthrough_bev_eventcb(struct bufferevent *bev, short events, void *arg) { pxy_conn_ctx_t *ctx = arg; if (bev == ctx->src.bev) { protopassthrough_bev_eventcb_src(bev, events, ctx); } else if (bev == ctx->srvdst.bev) { protopassthrough_bev_eventcb_srvdst(bev, events, ctx); } else { log_err_printf("protopassthrough_bev_eventcb: UNKWN conn end\n"); return; } // The topmost eventcb handles the term and enomem flags, frees the conn if (ctx->term || ctx->enomem) { return; } if (events & BEV_EVENT_CONNECTED) { if (ctx->connected) { #ifdef HAVE_LOCAL_PROCINFO if (protopassthrough_prepare_logging(ctx) == -1) { return; } #endif /* HAVE_LOCAL_PROCINFO */ protopassthrough_log_connect(ctx); } } } protocol_t protopassthrough_setup(pxy_conn_ctx_t *ctx) { // @attention Reset all callbacks while switching to passthrough mode, because we should override any/all protocol settings of the previous protocol. // This is different from initial protocol setup, which may choose to keep the default tcp settings. ctx->protoctx->proto = PROTO_PASSTHROUGH; ctx->protoctx->connectcb = protopassthrough_conn_connect; // Never used, but set it to the correct callback anyway ctx->protoctx->init_conn = prototcp_init_conn; ctx->protoctx->bev_readcb = protopassthrough_bev_readcb; ctx->protoctx->bev_writecb = protopassthrough_bev_writecb; ctx->protoctx->bev_eventcb = protopassthrough_bev_eventcb; #ifndef WITHOUT_USERAUTH ctx->protoctx->classify_usercb = protopassthrough_classify_user; #endif /* !WITHOUT_USERAUTH */ return PROTO_PASSTHROUGH; } /* vim: set noet ft=c: */