mirror of
https://github.com/sonertari/SSLproxy
synced 2024-11-02 15:40:19 +00:00
844e68116a
Lock conn thread instead of thrmgr thread while adding conns (giant thrmgr lock versus conn thread level locks), so add conn thread mutex and remove thrmgr mutex Offload thrmgr thread by moving many conn related setup to conn handling threads Fix signal 6 crash caused by calling pxy_thrmgr_timer_cb() while failed conn is being freed, so use conn thread mutexes and defer adding conn to thr conn list until conn setup succeeds Other fixes, improvements, and clean-up
2040 lines
60 KiB
C
2040 lines
60 KiB
C
/*-
|
|
* SSLsplit - transparent SSL/TLS interception
|
|
* https://www.roe.ch/SSLsplit
|
|
*
|
|
* Copyright (c) 2009-2018, Daniel Roethlisberger <daniel@roe.ch>.
|
|
* Copyright (c) 2017-2019, Soner Tari <sonertari@gmail.com>.
|
|
* 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 "pxyconn.h"
|
|
|
|
#include "prototcp.h"
|
|
#include "protossl.h"
|
|
#include "protohttp.h"
|
|
#include "protopop3.h"
|
|
#include "protosmtp.h"
|
|
#include "protoautossl.h"
|
|
#include "protopassthrough.h"
|
|
|
|
#include "privsep.h"
|
|
#include "sys.h"
|
|
#include "log.h"
|
|
#include "attrib.h"
|
|
#include "proc.h"
|
|
|
|
#include <string.h>
|
|
#include <arpa/inet.h>
|
|
#include <sys/param.h>
|
|
#include <assert.h>
|
|
|
|
#include <event2/listener.h>
|
|
|
|
#ifdef __linux__
|
|
#include <glob.h>
|
|
#endif /* __linux__ */
|
|
|
|
#include <net/if_arp.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <sys/sysctl.h>
|
|
#include <net/route.h>
|
|
#include <netinet/if_ether.h>
|
|
#ifdef __OpenBSD__
|
|
#include <net/if_dl.h>
|
|
#endif /* __OpenBSD__ */
|
|
|
|
/*
|
|
* Maximum size of data to buffer per connection direction before
|
|
* temporarily stopping to read data from the other end.
|
|
*/
|
|
#define OUTBUF_LIMIT (128*1024)
|
|
|
|
int descriptor_table_size = 0;
|
|
|
|
// @attention The order of names should match the order in protocol enum
|
|
char *protocol_names[] = {
|
|
// ERROR = -1
|
|
"PASSTHROUGH", // = 0
|
|
"HTTP",
|
|
"HTTPS",
|
|
"POP3",
|
|
"POP3S",
|
|
"SMTP",
|
|
"SMTPS",
|
|
"AUTOSSL",
|
|
"TCP",
|
|
"SSL",
|
|
};
|
|
|
|
static protocol_t NONNULL(1)
|
|
pxy_setup_proto(pxy_conn_ctx_t *ctx)
|
|
{
|
|
ctx->protoctx = malloc(sizeof(proto_ctx_t));
|
|
if (!ctx->protoctx) {
|
|
return PROTO_ERROR;
|
|
}
|
|
memset(ctx->protoctx, 0, sizeof(proto_ctx_t));
|
|
|
|
// Default to tcp
|
|
prototcp_setup(ctx);
|
|
|
|
protocol_t proto;
|
|
if (ctx->spec->upgrade) {
|
|
proto = protoautossl_setup(ctx);
|
|
} else if (ctx->spec->http) {
|
|
if (ctx->spec->ssl) {
|
|
proto = protohttps_setup(ctx);
|
|
} else {
|
|
proto = protohttp_setup(ctx);
|
|
}
|
|
} else if (ctx->spec->pop3) {
|
|
if (ctx->spec->ssl) {
|
|
proto = protopop3s_setup(ctx);
|
|
} else {
|
|
proto = protopop3_setup(ctx);
|
|
}
|
|
} else if (ctx->spec->smtp) {
|
|
if (ctx->spec->ssl) {
|
|
proto = protosmtps_setup(ctx);
|
|
} else {
|
|
proto = protosmtp_setup(ctx);
|
|
}
|
|
} else if (ctx->spec->ssl) {
|
|
proto = protossl_setup(ctx);
|
|
} else {
|
|
proto = PROTO_TCP;
|
|
}
|
|
|
|
if (proto == PROTO_ERROR) {
|
|
free(ctx->protoctx);
|
|
}
|
|
return proto;
|
|
}
|
|
|
|
static protocol_t NONNULL(1)
|
|
pxy_setup_proto_child(pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
ctx->protoctx = malloc(sizeof(proto_child_ctx_t));
|
|
if (!ctx->protoctx) {
|
|
return PROTO_ERROR;
|
|
}
|
|
memset(ctx->protoctx, 0, sizeof(proto_child_ctx_t));
|
|
|
|
// Default to tcp
|
|
prototcp_setup_child(ctx);
|
|
|
|
protocol_t proto;
|
|
if (ctx->conn->spec->upgrade) {
|
|
proto = protoautossl_setup_child(ctx);
|
|
} else if (ctx->conn->spec->http) {
|
|
if (ctx->conn->spec->ssl) {
|
|
proto = protohttps_setup_child(ctx);
|
|
} else {
|
|
proto = protohttp_setup_child(ctx);
|
|
}
|
|
} else if (ctx->conn->spec->pop3) {
|
|
if (ctx->conn->spec->ssl) {
|
|
proto = (protossl_setup_child(ctx) != PROTO_ERROR) ? PROTO_POP3S : PROTO_ERROR;
|
|
} else {
|
|
proto = PROTO_POP3;
|
|
}
|
|
} else if (ctx->conn->spec->smtp) {
|
|
if (ctx->conn->spec->ssl) {
|
|
proto = (protossl_setup_child(ctx) != PROTO_ERROR) ? PROTO_SMTPS : PROTO_ERROR;
|
|
} else {
|
|
proto = PROTO_SMTP;
|
|
}
|
|
} else if (ctx->conn->spec->ssl) {
|
|
proto = protossl_setup_child(ctx);
|
|
} else {
|
|
proto = PROTO_TCP;
|
|
}
|
|
|
|
if (proto == PROTO_ERROR) {
|
|
free(ctx->protoctx);
|
|
}
|
|
return proto;
|
|
}
|
|
|
|
static pxy_conn_ctx_t * MALLOC NONNULL(2,3,4)
|
|
pxy_conn_ctx_new(evutil_socket_t fd,
|
|
pxy_thrmgr_ctx_t *thrmgr,
|
|
proxyspec_t *spec, opts_t *opts,
|
|
evutil_socket_t clisock)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_new: ENTER, fd=%d\n", fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
pxy_conn_ctx_t *ctx = malloc(sizeof(pxy_conn_ctx_t));
|
|
if (!ctx) {
|
|
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
|
|
evutil_closesocket(fd);
|
|
return NULL;
|
|
}
|
|
memset(ctx, 0, sizeof(pxy_conn_ctx_t));
|
|
|
|
ctx->id = thrmgr->conn_count++;
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_new: id=%llu, fd=%d\n", ctx->id, fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->type = CONN_TYPE_PARENT;
|
|
ctx->fd = fd;
|
|
ctx->conn = ctx;
|
|
ctx->thrmgr = thrmgr;
|
|
ctx->spec = spec;
|
|
|
|
ctx->proto = pxy_setup_proto(ctx);
|
|
if (ctx->proto == PROTO_ERROR) {
|
|
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
|
|
evutil_closesocket(fd);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
ctx->opts = opts;
|
|
ctx->clisock = clisock;
|
|
|
|
ctx->ctime = time(NULL);
|
|
ctx->atime = ctx->ctime;
|
|
|
|
ctx->next = NULL;
|
|
|
|
pxy_thrmgr_attach(ctx);
|
|
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
ctx->lproc.pid = -1;
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
return ctx;
|
|
}
|
|
|
|
static pxy_conn_child_ctx_t * MALLOC NONNULL(2)
|
|
pxy_conn_ctx_new_child(evutil_socket_t fd, pxy_conn_ctx_t *conn)
|
|
{
|
|
assert(conn != NULL);
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_new_child: ENTER, child fd=%d, fd=%d\n", fd, conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
pxy_conn_child_ctx_t *ctx = malloc(sizeof(pxy_conn_child_ctx_t));
|
|
if (!ctx) {
|
|
return NULL;
|
|
}
|
|
memset(ctx, 0, sizeof(pxy_conn_child_ctx_t));
|
|
|
|
ctx->type = CONN_TYPE_CHILD;
|
|
ctx->fd = fd;
|
|
ctx->conn = conn;
|
|
|
|
ctx->proto = pxy_setup_proto_child(ctx);
|
|
if (ctx->proto == PROTO_ERROR) {
|
|
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
|
|
evutil_closesocket(fd);
|
|
free(ctx);
|
|
return NULL;
|
|
}
|
|
|
|
// @attention Child connections use the parent's event bases, otherwise we would get multithreading issues
|
|
pxy_thrmgr_attach_child(conn);
|
|
return ctx;
|
|
}
|
|
|
|
static void NONNULL(1)
|
|
pxy_conn_ctx_free_child(pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free_child: ENTER, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
pxy_thrmgr_detach_child(ctx->conn);
|
|
|
|
// If the proto doesn't have special args, proto_free() callback is NULL
|
|
if (ctx->protoctx->proto_free) {
|
|
ctx->protoctx->proto_free(ctx);
|
|
}
|
|
free(ctx->protoctx);
|
|
free(ctx);
|
|
}
|
|
|
|
static void NONNULL(1,2)
|
|
pxy_conn_remove_child(pxy_conn_child_ctx_t *child, pxy_conn_child_ctx_t **head)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_remove_child: ENTER, child fd=%d, fd=%d\n", child->fd, child->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (child->fd == (*head)->fd) {
|
|
*head = (*head)->next;
|
|
return;
|
|
}
|
|
|
|
pxy_conn_child_ctx_t *current = (*head)->next;
|
|
pxy_conn_child_ctx_t *previous = *head;
|
|
while (current != NULL && previous != NULL) {
|
|
if (child->fd == current->fd) {
|
|
previous->next = current->next;
|
|
return;
|
|
}
|
|
previous = current;
|
|
current = current->next;
|
|
}
|
|
return;
|
|
}
|
|
|
|
static void
|
|
pxy_conn_free_child(pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
assert(ctx->conn != NULL);
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_free_child: ENTER, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (ctx->dst.bev) {
|
|
ctx->dst.free(ctx->dst.bev, ctx->conn);
|
|
ctx->dst.bev = NULL;
|
|
}
|
|
|
|
if (ctx->src.bev) {
|
|
ctx->src.free(ctx->src.bev, ctx->conn);
|
|
ctx->src.bev = NULL;
|
|
}
|
|
|
|
pxy_conn_remove_child(ctx, &ctx->conn->children);
|
|
pxy_conn_ctx_free_child(ctx);
|
|
}
|
|
|
|
void
|
|
pxy_conn_term_child(pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_term_child: ENTER, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->term = 1;
|
|
}
|
|
|
|
void
|
|
pxy_conn_free_children(pxy_conn_ctx_t *ctx)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_free_children: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @attention Free the child ctxs asap, we need their fds
|
|
while (ctx->children) {
|
|
pxy_conn_free_child(ctx->children);
|
|
}
|
|
|
|
// @attention Parent may be closing before there was any child at all nor was child_evcl ever created
|
|
if (ctx->child_evcl) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINER, "pxy_conn_free_children: Freeing child_evcl, child fd=%d, children->fd=%d, fd=%d\n",
|
|
ctx->child_fd, ctx->children ? ctx->children->fd : -1, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @attention child_evcl was created with LEV_OPT_CLOSE_ON_FREE, so do not close ctx->child_fd
|
|
evconnlistener_free(ctx->child_evcl);
|
|
ctx->child_evcl = NULL;
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_conn_ctx_free(pxy_conn_ctx_t *ctx, int by_requestor)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (WANT_CONTENT_LOG(ctx)) {
|
|
if (log_content_close(&ctx->logctx, by_requestor) == -1) {
|
|
log_err_level_printf(LOG_WARNING, "Content log close failed\n");
|
|
}
|
|
}
|
|
|
|
if (ctx->opts->user_auth && ctx->srchost_str && ctx->user && ctx->ether) {
|
|
// Update userdb atime if idle time is more than 50% of user timeout, which is expected to reduce update frequency
|
|
unsigned int idletime = ctx->idletime + (time(NULL) - ctx->ctime);
|
|
if (idletime > (ctx->opts->user_timeout / 2)) {
|
|
struct userdbkeys *keys = malloc(sizeof(userdbkeys_t));
|
|
if (keys) {
|
|
// Zero out for NULL termination
|
|
memset(keys, 0, sizeof(userdbkeys_t));
|
|
// Leave room for NULL to make sure the strings are always NULL terminated
|
|
strncpy(keys->ip, ctx->srchost_str, sizeof(keys->ip) - 1);
|
|
strncpy(keys->user, ctx->user, sizeof(keys->user) - 1);
|
|
strncpy(keys->ether, ctx->ether, sizeof(keys->ether) - 1);
|
|
|
|
if (privsep_client_update_atime(ctx->clisock, keys) == -1) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free: Error updating user atime: %s, ctx->fd=%d\n", sqlite3_errmsg(ctx->opts->userdb), ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
} else {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free: Successfully updated user atime, ctx->fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
}
|
|
free(keys);
|
|
}
|
|
} else {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_ctx_free: Will not update user atime, idletime=%u, ctx->fd=%d\n", idletime, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
}
|
|
}
|
|
|
|
pxy_thrmgr_detach(ctx);
|
|
if (ctx->srchost_str) {
|
|
free(ctx->srchost_str);
|
|
}
|
|
if (ctx->srcport_str) {
|
|
free(ctx->srcport_str);
|
|
}
|
|
if (ctx->dsthost_str) {
|
|
free(ctx->dsthost_str);
|
|
}
|
|
if (ctx->dstport_str) {
|
|
free(ctx->dstport_str);
|
|
}
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
if (ctx->lproc.exec_path) {
|
|
free(ctx->lproc.exec_path);
|
|
}
|
|
if (ctx->lproc.user) {
|
|
free(ctx->lproc.user);
|
|
}
|
|
if (ctx->lproc.group) {
|
|
free(ctx->lproc.group);
|
|
}
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
if (ctx->ev) {
|
|
event_free(ctx->ev);
|
|
}
|
|
if (ctx->sslproxy_header) {
|
|
free(ctx->sslproxy_header);
|
|
}
|
|
// If the proto doesn't have special args, proto_free() callback is NULL
|
|
if (ctx->protoctx->proto_free) {
|
|
ctx->protoctx->proto_free(ctx);
|
|
}
|
|
free(ctx->protoctx);
|
|
|
|
if (ctx->user) {
|
|
free(ctx->user);
|
|
}
|
|
if (ctx->ether) {
|
|
free(ctx->ether);
|
|
}
|
|
free(ctx);
|
|
}
|
|
|
|
void
|
|
pxy_conn_free(pxy_conn_ctx_t *ctx, int by_requestor)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_free: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (!ctx->src.closed) {
|
|
if (ctx->src.bev) {
|
|
ctx->src.free(ctx->src.bev, ctx);
|
|
ctx->src.bev = NULL;
|
|
} else {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_conn_free: evutil_closesocket on NULL src->bev, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @todo src fd may be open, although src.bev is NULL, where do we close the src fd?
|
|
evutil_closesocket(ctx->fd);
|
|
}
|
|
}
|
|
|
|
if (ctx->srvdst.bev) {
|
|
ctx->srvdst.free(ctx->srvdst.bev, ctx);
|
|
ctx->srvdst.bev = NULL;
|
|
}
|
|
|
|
if (ctx->dst.bev) {
|
|
ctx->dst.free(ctx->dst.bev, ctx);
|
|
ctx->dst.bev = NULL;
|
|
}
|
|
|
|
pxy_conn_free_children(ctx);
|
|
pxy_conn_ctx_free(ctx, by_requestor);
|
|
}
|
|
|
|
void
|
|
pxy_conn_term(pxy_conn_ctx_t *ctx, int by_requestor)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_term: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->term = 1;
|
|
ctx->term_requestor = by_requestor;
|
|
}
|
|
|
|
void
|
|
pxy_log_connect_nonhttp(pxy_conn_ctx_t *ctx)
|
|
{
|
|
char *msg;
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
char *lpi = NULL;
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
int rv;
|
|
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
if (ctx->opts->lprocinfo) {
|
|
rv = asprintf(&lpi, "lproc:%i:%s:%s:%s",
|
|
ctx->lproc.pid,
|
|
STRORDASH(ctx->lproc.user),
|
|
STRORDASH(ctx->lproc.group),
|
|
STRORDASH(ctx->lproc.exec_path));
|
|
if ((rv < 0) || !lpi) {
|
|
ctx->enomem = 1;
|
|
goto out;
|
|
}
|
|
} else {
|
|
lpi = "";
|
|
}
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
|
|
/*
|
|
* The following ifdef's within asprintf arguments list generates
|
|
* warnings with -Wembedded-directive on some compilers.
|
|
* Not fixing the code in order to avoid more code duplication.
|
|
*/
|
|
|
|
if (!ctx->src.ssl) {
|
|
rv = asprintf(&msg, "CONN: %s %s %s %s %s"
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
" %s"
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
" user:%s\n",
|
|
ctx->proto == PROTO_PASSTHROUGH ? "passthrough" : (ctx->proto == PROTO_POP3 ? "pop3" : (ctx->proto == PROTO_SMTP ? "smtp" : "tcp")),
|
|
STRORDASH(ctx->srchost_str),
|
|
STRORDASH(ctx->srcport_str),
|
|
STRORDASH(ctx->dsthost_str),
|
|
STRORDASH(ctx->dstport_str),
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
lpi,
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
STRORDASH(ctx->user));
|
|
} else {
|
|
rv = asprintf(&msg, "CONN: %s %s %s %s %s "
|
|
"sni:%s names:%s "
|
|
"sproto:%s:%s dproto:%s:%s "
|
|
"origcrt:%s usedcrt:%s"
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
" %s"
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
" user:%s\n",
|
|
ctx->proto == PROTO_AUTOSSL ? "autossl" : (ctx->proto == PROTO_POP3S ? "pop3s" : (ctx->proto == PROTO_SMTPS ? "smtps" : "ssl")),
|
|
STRORDASH(ctx->srchost_str),
|
|
STRORDASH(ctx->srcport_str),
|
|
STRORDASH(ctx->dsthost_str),
|
|
STRORDASH(ctx->dstport_str),
|
|
STRORDASH(ctx->sslctx->sni),
|
|
STRORDASH(ctx->sslctx->ssl_names),
|
|
SSL_get_version(ctx->src.ssl),
|
|
SSL_get_cipher(ctx->src.ssl),
|
|
!ctx->srvdst.closed && ctx->srvdst.ssl ? SSL_get_version(ctx->srvdst.ssl):ctx->sslctx->srvdst_ssl_version,
|
|
!ctx->srvdst.closed && ctx->srvdst.ssl ? SSL_get_cipher(ctx->srvdst.ssl):ctx->sslctx->srvdst_ssl_cipher,
|
|
STRORDASH(ctx->sslctx->origcrtfpr),
|
|
STRORDASH(ctx->sslctx->usedcrtfpr),
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
lpi,
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
STRORDASH(ctx->user));
|
|
}
|
|
if ((rv < 0) || !msg) {
|
|
ctx->enomem = 1;
|
|
goto out;
|
|
}
|
|
if (!ctx->opts->detach) {
|
|
log_err_printf("%s", msg);
|
|
} else if (ctx->opts->statslog) {
|
|
if (log_conn(msg) == -1) {
|
|
log_err_level_printf(LOG_WARNING, "Conn logging failed\n");
|
|
}
|
|
}
|
|
if (ctx->opts->connectlog) {
|
|
if (log_connect_print_free(msg) == -1) {
|
|
free(msg);
|
|
log_err_level_printf(LOG_WARNING, "Connection logging failed\n");
|
|
}
|
|
} else {
|
|
free(msg);
|
|
}
|
|
out:
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
if (lpi && ctx->opts->lprocinfo) {
|
|
free(lpi);
|
|
}
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
return;
|
|
}
|
|
|
|
int
|
|
pxy_log_content_inbuf(pxy_conn_ctx_t *ctx, struct evbuffer *inbuf, int req)
|
|
{
|
|
size_t sz = evbuffer_get_length(inbuf);
|
|
unsigned char *buf = malloc(sz);
|
|
if (!buf) {
|
|
ctx->conn->enomem = 1;
|
|
return -1;
|
|
}
|
|
if (evbuffer_copyout(inbuf, buf, sz) == -1) {
|
|
free(buf);
|
|
return -1;
|
|
}
|
|
logbuf_t *lb = logbuf_new_alloc(sz, NULL);
|
|
if (!lb) {
|
|
free(buf);
|
|
ctx->conn->enomem = 1;
|
|
return -1;
|
|
}
|
|
memcpy(lb->buf, buf, lb->sz);
|
|
free(buf);
|
|
if (log_content_submit(&ctx->conn->logctx, lb, req) == -1) {
|
|
logbuf_free(lb);
|
|
log_err_level_printf(LOG_WARNING, "Content log submission failed\n");
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
int
|
|
pxy_prepare_logging_local_procinfo(pxy_conn_ctx_t *ctx)
|
|
{
|
|
if (ctx->opts->lprocinfo) {
|
|
/* fetch process info */
|
|
if (proc_pid_for_addr(&ctx->lproc.pid,
|
|
(struct sockaddr*)&ctx->lproc.srcaddr,
|
|
ctx->lproc.srcaddrlen) == 0 &&
|
|
ctx->lproc.pid != -1 &&
|
|
proc_get_info(ctx->lproc.pid,
|
|
&ctx->lproc.exec_path,
|
|
&ctx->lproc.uid,
|
|
&ctx->lproc.gid) == 0) {
|
|
/* fetch user/group names */
|
|
ctx->lproc.user = sys_user_str(
|
|
ctx->lproc.uid);
|
|
ctx->lproc.group = sys_group_str(
|
|
ctx->lproc.gid);
|
|
if (!ctx->lproc.user ||
|
|
!ctx->lproc.group) {
|
|
ctx->enomem = 1;
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
|
|
static int
|
|
pxy_prepare_logging(pxy_conn_ctx_t *ctx)
|
|
{
|
|
/* prepare logging, part 2 */
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
if (WANT_CONNECT_LOG(ctx) || WANT_CONTENT_LOG(ctx)) {
|
|
if (pxy_prepare_logging_local_procinfo(ctx) == -1) {
|
|
return -1;
|
|
}
|
|
}
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
if (WANT_CONTENT_LOG(ctx)) {
|
|
if (log_content_open(&ctx->logctx, ctx->opts,
|
|
(struct sockaddr *)&ctx->srcaddr,
|
|
ctx->srcaddrlen,
|
|
(struct sockaddr *)&ctx->dstaddr,
|
|
ctx->dstaddrlen,
|
|
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str),
|
|
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str),
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
ctx->lproc.exec_path,
|
|
ctx->lproc.user,
|
|
ctx->lproc.group
|
|
#else /* HAVE_LOCAL_PROCINFO */
|
|
NULL, NULL, NULL
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
) == -1) {
|
|
if (errno == ENOMEM)
|
|
ctx->enomem = 1;
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void NONNULL(1,2)
|
|
pxy_log_dbg_connect_type(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this)
|
|
{
|
|
if (OPTS_DEBUG(ctx->opts)) {
|
|
if (this->ssl) {
|
|
char *keystr;
|
|
/* for SSL, we get two connect events */
|
|
log_dbg_printf("%s connected to [%s]:%s %s %s\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str),
|
|
SSL_get_version(this->ssl), SSL_get_cipher(this->ssl));
|
|
keystr = ssl_ssl_masterkey_to_str(this->ssl);
|
|
if (keystr) {
|
|
log_dbg_print_free(keystr);
|
|
}
|
|
} else {
|
|
/* for TCP, we get only a dst connect event,
|
|
* since src was already connected from the
|
|
* beginning; mirror SSL debug output anyway
|
|
* in order not to confuse anyone who might be
|
|
* looking closely at the output */
|
|
log_dbg_printf("%s connected to [%s]:%s\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str));
|
|
log_dbg_printf("%s connected from [%s]:%s\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_log_connect_src(pxy_conn_ctx_t *ctx)
|
|
{
|
|
/* log connection if we don't analyze any headers */
|
|
if (!ctx->spec->http && (WANT_CONNECT_LOG(ctx) || ctx->opts->statslog)) {
|
|
pxy_log_connect_nonhttp(ctx);
|
|
}
|
|
|
|
if (ctx->src.ssl && ctx->opts->certgendir) {
|
|
/* write SSL certificates to gendir */
|
|
protossl_srccert_write(ctx);
|
|
}
|
|
|
|
if (protossl_log_masterkey(ctx, &ctx->src) == -1) {
|
|
return;
|
|
}
|
|
|
|
pxy_log_dbg_connect_type(ctx, &ctx->src);
|
|
}
|
|
|
|
void
|
|
pxy_log_connect_srvdst(pxy_conn_ctx_t *ctx)
|
|
{
|
|
// @attention srvdst.bev may be NULL, if its writecb fires first
|
|
if (ctx->srvdst.bev) {
|
|
/* log connection if we don't analyze any headers */
|
|
if (!ctx->srvdst.ssl && !ctx->spec->http && (WANT_CONNECT_LOG(ctx) || ctx->opts->statslog)) {
|
|
pxy_log_connect_nonhttp(ctx);
|
|
}
|
|
|
|
if (protossl_log_masterkey(ctx, &ctx->srvdst) == -1) {
|
|
return;
|
|
}
|
|
|
|
pxy_log_dbg_connect_type(ctx, &ctx->srvdst);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pxy_log_dbg_disconnect(pxy_conn_ctx_t *ctx)
|
|
{
|
|
/* we only get a single disconnect event here for both connections */
|
|
if (OPTS_DEBUG(ctx->opts)) {
|
|
log_dbg_printf("%s disconnected to [%s]:%s, fd=%d\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->dsthost_str), STRORDASH(ctx->dstport_str), ctx->fd);
|
|
log_dbg_printf("%s disconnected from [%s]:%s, fd=%d\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->srchost_str), STRORDASH(ctx->srcport_str), ctx->fd);
|
|
}
|
|
}
|
|
|
|
static void
|
|
pxy_log_dbg_disconnect_child(pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
/* we only get a single disconnect event here for both connections */
|
|
if (OPTS_DEBUG(ctx->conn->opts)) {
|
|
log_dbg_printf("Child %s disconnected to [%s]:%s, child fd=%d, fd=%d\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->conn->dsthost_str), STRORDASH(ctx->conn->dstport_str), ctx->fd, ctx->conn->fd);
|
|
log_dbg_printf("Child %s disconnected from [%s]:%s, child fd=%d, fd=%d\n",
|
|
protocol_names[ctx->proto],
|
|
STRORDASH(ctx->conn->srchost_str), STRORDASH(ctx->conn->srcport_str), ctx->fd, ctx->conn->fd);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
void
|
|
pxy_log_dbg_evbuf_info(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this, pxy_conn_desc_t *other)
|
|
{
|
|
// Use ctx->conn, because this function is used by child conns too
|
|
if (OPTS_DEBUG(ctx->conn->opts)) {
|
|
log_dbg_printf("evbuffer size at EOF: i:%zu o:%zu i:%zu o:%zu\n",
|
|
evbuffer_get_length(bufferevent_get_input(this->bev)),
|
|
evbuffer_get_length(bufferevent_get_output(this->bev)),
|
|
other->closed ? 0 : evbuffer_get_length(bufferevent_get_input(other->bev)),
|
|
other->closed ? 0 : evbuffer_get_length(bufferevent_get_output(other->bev)));
|
|
}
|
|
}
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
unsigned char *
|
|
pxy_malloc_packet(size_t sz, pxy_conn_ctx_t *ctx)
|
|
{
|
|
unsigned char *packet = malloc(sz);
|
|
if (!packet) {
|
|
ctx->enomem = 1;
|
|
return NULL;
|
|
}
|
|
return packet;
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
char *bev_names[] = {
|
|
"src",
|
|
"dst",
|
|
"srvdst",
|
|
"NULL",
|
|
"UNKWN"
|
|
};
|
|
|
|
static char *
|
|
pxy_get_event_name(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
|
|
{
|
|
// XXX: Used by watermark functions only, remove
|
|
if (bev == ctx->src.bev) {
|
|
return bev_names[0];
|
|
} else if (bev == ctx->dst.bev) {
|
|
return bev_names[1];
|
|
} else if (bev == ctx->srvdst.bev) {
|
|
return bev_names[2];
|
|
} else if (bev == NULL) {
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_get_event_name: event_name=NULL\n");
|
|
return bev_names[3];
|
|
} else {
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_get_event_name: event_name=UNKWN\n");
|
|
return bev_names[4];
|
|
}
|
|
}
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
void
|
|
pxy_try_set_watermark(struct bufferevent *bev, pxy_conn_ctx_t *ctx, struct bufferevent *other)
|
|
{
|
|
if (evbuffer_get_length(bufferevent_get_output(other)) >= OUTBUF_LIMIT) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_set_watermark: %s, fd=%d\n", pxy_get_event_name(bev, ctx), ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
/* temporarily disable data source;
|
|
* set an appropriate watermark. */
|
|
bufferevent_setwatermark(other, EV_WRITE, OUTBUF_LIMIT/2, OUTBUF_LIMIT);
|
|
bufferevent_disable(bev, EV_READ);
|
|
ctx->thr->set_watermarks++;
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_try_unset_watermark(struct bufferevent *bev, pxy_conn_ctx_t *ctx, pxy_conn_desc_t *other)
|
|
{
|
|
if (other->bev && !(bufferevent_get_enabled(other->bev) & EV_READ)) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_unset_watermark: %s, fd=%d\n", pxy_get_event_name(bev, ctx), ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
/* data source temporarily disabled;
|
|
* re-enable and reset watermark to 0. */
|
|
bufferevent_setwatermark(bev, EV_WRITE, 0, 0);
|
|
bufferevent_enable(other->bev, EV_READ);
|
|
ctx->thr->unset_watermarks++;
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_discard_inbuf(struct bufferevent *bev)
|
|
{
|
|
struct evbuffer *inbuf = bufferevent_get_input(bev);
|
|
size_t inbuf_size = evbuffer_get_length(inbuf);
|
|
|
|
log_dbg_printf("Warning: Drained %zu bytes (conn closed)\n", inbuf_size);
|
|
evbuffer_drain(inbuf, inbuf_size);
|
|
}
|
|
|
|
void
|
|
pxy_insert_sslproxy_header(pxy_conn_ctx_t *ctx, unsigned char *packet, size_t *packet_size)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINER, "pxy_insert_sslproxy_header: INSERT, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @attention Cannot use string manipulation functions; we are dealing with binary arrays here, not NULL-terminated strings
|
|
memmove(packet + ctx->sslproxy_header_len + 2, packet, *packet_size);
|
|
memcpy(packet, ctx->sslproxy_header, ctx->sslproxy_header_len);
|
|
memcpy(packet + ctx->sslproxy_header_len, "\r\n", 2);
|
|
*packet_size += ctx->sslproxy_header_len + 2;
|
|
ctx->sent_sslproxy_header = 1;
|
|
}
|
|
|
|
void
|
|
pxy_try_remove_sslproxy_header(pxy_conn_child_ctx_t *ctx, unsigned char *packet, size_t *packet_size)
|
|
{
|
|
// @attention Cannot use string manipulation functions; we are dealing with binary arrays here, not NULL-terminated strings
|
|
unsigned char *pos = memmem(packet, *packet_size, ctx->conn->sslproxy_header, ctx->conn->sslproxy_header_len);
|
|
if (pos) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINER, "pxy_try_remove_sslproxy_header: REMOVE, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
memmove(pos, pos + ctx->conn->sslproxy_header_len + 2, *packet_size - (pos - packet) - (ctx->conn->sslproxy_header_len + 2));
|
|
*packet_size -= ctx->conn->sslproxy_header_len + 2;
|
|
ctx->removed_sslproxy_header = 1;
|
|
}
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
#define getdtablecount() 0
|
|
|
|
/*
|
|
* Copied from:
|
|
* opensmtpd-201801101641p1/openbsd-compat/imsg.c
|
|
*
|
|
* Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
static int
|
|
available_fds(unsigned int n)
|
|
{
|
|
unsigned int i;
|
|
int ret, fds[256];
|
|
|
|
if (n > (sizeof(fds)/sizeof(fds[0])))
|
|
return -1;
|
|
|
|
ret = 0;
|
|
for (i = 0; i < n; i++) {
|
|
fds[i] = -1;
|
|
if ((fds[i] = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
|
|
ret = -1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (i = 0; i < n && fds[i] >= 0; i++)
|
|
close(fds[i]);
|
|
|
|
return ret;
|
|
}
|
|
#endif /* __APPLE__ */
|
|
|
|
#ifdef __linux__
|
|
/*
|
|
* Copied from:
|
|
* https://github.com/tmux/tmux/blob/master/compat/getdtablecount.c
|
|
*
|
|
* Copyright (c) 2017 Nicholas Marriott <nicholas.marriott@gmail.com>
|
|
*
|
|
* Permission to use, copy, modify, and distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
* WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
|
|
* IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
|
|
* OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
int
|
|
getdtablecount()
|
|
{
|
|
char path[PATH_MAX];
|
|
glob_t g;
|
|
int n = 0;
|
|
|
|
if (snprintf(path, sizeof path, "/proc/%ld/fd/*", (long)getpid()) < 0) {
|
|
log_err_level_printf(LOG_CRIT, "snprintf overflow\n");
|
|
return 0;
|
|
}
|
|
if (glob(path, 0, NULL, &g) == 0)
|
|
n = g.gl_pathc;
|
|
globfree(&g);
|
|
return n;
|
|
}
|
|
#endif /* __linux__ */
|
|
|
|
/*
|
|
* Check if we are out of file descriptors to close the conn, or else libevent will crash us
|
|
* @attention We cannot guess the number of children in a connection at conn setup time. So, FD_RESERVE is just a ball park figure.
|
|
* But what if a connection passes the check below, but eventually tries to create more children than FD_RESERVE allows for? This will crash us the same.
|
|
* Beware, this applies to all current conns, not just the last connection setup.
|
|
* For example, 20x conns pass the check below before creating any children, at which point we reach at the last FD_RESERVE fds,
|
|
* then they all start creating children, which crashes us again.
|
|
* So, no matter how large an FD_RESERVE we choose, there will always be a risk of running out of fds, if we check the number of fds here only.
|
|
* If we are left with less than FD_RESERVE fds, we should not create more children than FD_RESERVE allows for either.
|
|
* Therefore, we check if we are out of fds in proxy_listener_acceptcb_child() and close the conn there too.
|
|
* @attention These checks are expected to slow us further down, but it is critical to avoid a crash in case we run out of fds.
|
|
*/
|
|
static int
|
|
check_fd_usage(UNUSED evutil_socket_t fd)
|
|
{
|
|
int dtable_count = getdtablecount();
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINER, "check_fd_usage: descriptor_table_size=%d, dtablecount=%d, reserve=%d, fd=%d\n",
|
|
descriptor_table_size, dtable_count, FD_RESERVE, fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (dtable_count + FD_RESERVE >= descriptor_table_size) {
|
|
goto out;
|
|
}
|
|
|
|
#ifdef __APPLE__
|
|
if (available_fds(FD_RESERVE) == -1) {
|
|
goto out;
|
|
}
|
|
#endif /* __APPLE__ */
|
|
|
|
return 0;
|
|
out:
|
|
errno = EMFILE;
|
|
log_err_level_printf(LOG_CRIT, "Out of file descriptors\n");
|
|
return -1;
|
|
}
|
|
|
|
/*
|
|
* Callback for accept events on the socket listener bufferevent.
|
|
*/
|
|
static void
|
|
pxy_listener_acceptcb_child(UNUSED struct evconnlistener *listener, evutil_socket_t fd,
|
|
UNUSED struct sockaddr *peeraddr, UNUSED int peeraddrlen, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *conn = arg;
|
|
|
|
conn->atime = time(NULL);
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_listener_acceptcb_child: ENTER, child fd=%d, conn->child_fd=%d, fd=%d\n", fd, conn->child_fd, conn->fd);
|
|
|
|
char *host, *port;
|
|
if (sys_sockaddr_str(peeraddr, peeraddrlen, &host, &port) == 0) {
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_listener_acceptcb_child: peer addr=[%s]:%s, child fd=%d, fd=%d\n", host, port, fd, conn->fd);
|
|
free(host);
|
|
free(port);
|
|
}
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (!conn->dstaddrlen) {
|
|
log_err_level_printf(LOG_CRIT, "Child no target address; aborting connection\n");
|
|
evutil_closesocket(fd);
|
|
pxy_conn_term(conn, 1);
|
|
goto out;
|
|
}
|
|
|
|
if (check_fd_usage(conn->fd) == -1) {
|
|
evutil_closesocket(fd);
|
|
pxy_conn_term(conn, 1);
|
|
goto out;
|
|
}
|
|
|
|
pxy_conn_child_ctx_t *ctx = pxy_conn_ctx_new_child(fd, conn);
|
|
if (!ctx) {
|
|
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
|
|
evutil_closesocket(fd);
|
|
pxy_conn_term(conn, 1);
|
|
goto out;
|
|
}
|
|
|
|
// Prepend child ctx to conn ctx child list
|
|
// @attention If the last child is deleted, the children list may become null again
|
|
ctx->next = conn->children;
|
|
conn->children = ctx;
|
|
|
|
conn->child_count++;
|
|
ctx->idx = conn->child_count;
|
|
|
|
// @attention Do not enable src events here yet, they will be enabled after dst connects
|
|
if (prototcp_setup_src_child(ctx) == -1) {
|
|
goto out;
|
|
}
|
|
|
|
// src_fd is different from fd
|
|
ctx->src_fd = bufferevent_getfd(ctx->src.bev);
|
|
ctx->conn->child_src_fd = ctx->src_fd;
|
|
ctx->conn->thr->max_fd = MAX(ctx->conn->thr->max_fd, ctx->src_fd);
|
|
|
|
/* create server-side socket and eventbuffer */
|
|
// Children rely on the findings of parent
|
|
ctx->protoctx->connectcb(ctx);
|
|
|
|
if (ctx->conn->term || ctx->conn->enomem) {
|
|
goto out;
|
|
}
|
|
|
|
bufferevent_enable(ctx->dst.bev, EV_READ|EV_WRITE);
|
|
|
|
if (OPTS_DEBUG(ctx->conn->opts)) {
|
|
char *host, *port;
|
|
if (sys_sockaddr_str((struct sockaddr *)&ctx->conn->dstaddr, ctx->conn->dstaddrlen, &host, &port) == 0) {
|
|
log_dbg_printf("Child connecting to [%s]:%s\n", host, port);
|
|
free(host);
|
|
free(port);
|
|
} else {
|
|
log_dbg_printf("Child connecting to [?]:?\n");
|
|
}
|
|
}
|
|
|
|
/* initiate connection */
|
|
if (bufferevent_socket_connect(ctx->dst.bev, (struct sockaddr *)&ctx->conn->dstaddr, ctx->conn->dstaddrlen) == -1) {
|
|
pxy_conn_term(conn, 1);
|
|
goto out;
|
|
}
|
|
|
|
ctx->dst_fd = bufferevent_getfd(ctx->dst.bev);
|
|
ctx->conn->child_dst_fd = ctx->dst_fd;
|
|
ctx->conn->thr->max_fd = MAX(ctx->conn->thr->max_fd, ctx->dst_fd);
|
|
|
|
out:
|
|
// @attention Do not use ctx->conn here, ctx may be uninitialized
|
|
// @attention Call pxy_conn_free() directly, not term functions here
|
|
// This is our last chance to close and free the conn
|
|
if (conn->term || conn->enomem) {
|
|
pxy_conn_free(conn, conn->term ? conn->term_requestor : 1);
|
|
}
|
|
}
|
|
|
|
int
|
|
pxy_setup_child_listener(pxy_conn_ctx_t *ctx)
|
|
{
|
|
// @attention Defer child setup and evcl creation until after parent init is complete, otherwise (1) causes multithreading issues (proxy_listener_acceptcb is
|
|
// running on a different thread from the conn, and we only have thrmgr mutex), and (2) we need to clean up less upon errors.
|
|
// Child evcls use the evbase of the parent thread, otherwise we would get multithreading issues.
|
|
if ((ctx->child_fd = privsep_client_opensock_child(ctx->clisock, ctx->spec)) == -1) {
|
|
log_err_level_printf(LOG_CRIT, "Error opening child socket: %s (%i)\n", strerror(errno), errno);
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
ctx->thr->max_fd = MAX(ctx->thr->max_fd, ctx->child_fd);
|
|
|
|
// @attention Do not pass NULL as user-supplied pointer
|
|
struct evconnlistener *child_evcl = evconnlistener_new(ctx->thr->evbase, pxy_listener_acceptcb_child, ctx, LEV_OPT_CLOSE_ON_FREE, 1024, ctx->child_fd);
|
|
if (!child_evcl) {
|
|
log_err_level_printf(LOG_CRIT, "Error creating child evconnlistener: %s\n", strerror(errno));
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_setup_child_listener: Error creating child evconnlistener: %s, fd=%d, child_fd=%d\n", strerror(errno), ctx->fd, ctx->child_fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @attention Cannot call proxy_listener_ctx_free() on child_evcl, child_evcl does not have any ctx with next listener
|
|
// @attention Close child fd separately, because child evcl does not exist yet, hence fd would not be closed by calling pxy_conn_free()
|
|
evutil_closesocket(ctx->child_fd);
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
ctx->child_evcl = child_evcl;
|
|
|
|
evconnlistener_set_error_cb(child_evcl, proxy_listener_errorcb);
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINER, "pxy_setup_child_listener: Finished setting up child, NEW child_fd=%d, fd=%d\n", ctx->child_fd, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
struct sockaddr_in child_listener_addr;
|
|
socklen_t child_listener_len = sizeof(child_listener_addr);
|
|
|
|
if (getsockname(ctx->child_fd, (struct sockaddr *)&child_listener_addr, &child_listener_len) < 0) {
|
|
log_err_level_printf(LOG_CRIT, "Error in getsockname: %s\n", strerror(errno));
|
|
// @todo If getsockname() fails, should we really terminate the connection?
|
|
// @attention Do not close the child fd here, because child evcl exists now, hence pxy_conn_free() will close it while freeing child_evcl
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
|
|
// @attention Children are assumed to be listening on an IPv4 address
|
|
// @todo IPv6?
|
|
char addr[INET_ADDRSTRLEN];
|
|
if (!inet_ntop(AF_INET, &child_listener_addr.sin_addr, addr, INET_ADDRSTRLEN)) {
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
|
|
if (pxy_set_dstaddr(ctx) == -1) {
|
|
return -1;
|
|
}
|
|
|
|
// SSLproxy: [127.0.0.1]:34649,[192.168.3.24]:47286,[74.125.206.108]:465,s
|
|
// @todo Port may be less than 5 chars
|
|
// SSLproxy: + + [ + addr + ] + : + p + , + [ + srchost_str + ] + : + srcport_str + , + [ + dsthost_str + ] + : + dstport_str + , + s
|
|
// SSLPROXY_KEY_LEN + 1 + 1 + strlen(addr) + 1 + 1 + 5 + 1 + 1 + strlen(ctx->srchost_str) + 1 + 1 + strlen(ctx->srcport_str) + 1 + 1 + strlen(ctx->dsthost_str) + 1 + 1 + strlen(ctx->dstport_str) + 1 + 1
|
|
ctx->sslproxy_header_len = SSLPROXY_KEY_LEN + strlen(addr) + strlen(ctx->srchost_str) + strlen(ctx->srcport_str) + strlen(ctx->dsthost_str) + strlen(ctx->dstport_str) + 19;
|
|
|
|
// @todo Always check malloc retvals. Should we close the conn if malloc fails?
|
|
// +1 for NULL
|
|
ctx->sslproxy_header = malloc(ctx->sslproxy_header_len + 1);
|
|
if (!ctx->sslproxy_header) {
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
|
|
// printf(3): "snprintf() will write at most size-1 of the characters (the size'th character then gets the terminating NULL)"
|
|
// So, +1 for NULL
|
|
snprintf(ctx->sslproxy_header, ctx->sslproxy_header_len + 1, "%s [%s]:%u,[%s]:%s,[%s]:%s,%s",
|
|
SSLPROXY_KEY, addr, ntohs(child_listener_addr.sin_port), STRORNONE(ctx->srchost_str), STRORNONE(ctx->srcport_str),
|
|
STRORNONE(ctx->dsthost_str), STRORNONE(ctx->dstport_str), ctx->spec->ssl ? "s":"p");
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pxy_try_close_conn_end(pxy_conn_desc_t *conn_end, pxy_conn_ctx_t *ctx)
|
|
{
|
|
/* if the other end is still open and doesn't have data
|
|
* to send, close it, otherwise its writecb will close
|
|
* it after writing what's left in the output buffer */
|
|
if (evbuffer_get_length(bufferevent_get_output(conn_end->bev)) == 0) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_try_close_conn_end: evbuffer_get_length(outbuf) == 0, terminate conn, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
conn_end->free(conn_end->bev, ctx);
|
|
conn_end->bev = NULL;
|
|
conn_end->closed = 1;
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pxy_connect_srvdst(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_connect_srvdst: writecb before connected, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// @attention Sometimes dst write cb fires but not event cb, especially if the listener cb is not finished yet, so the conn stalls.
|
|
// This is a workaround for this error condition, nothing else seems to work.
|
|
// @attention Do not try to free the conn here, since the listener cb may not be finished yet, which causes multithreading issues
|
|
// XXX: Workaround, should find the real cause: BEV_OPT_DEFER_CALLBACKS?
|
|
ctx->protoctx->bev_eventcb(bev, BEV_EVENT_CONNECTED, ctx);
|
|
|
|
return pxy_bev_eventcb_postexec_logging_and_stats(bev, BEV_EVENT_CONNECTED, ctx);
|
|
}
|
|
|
|
void
|
|
pxy_try_disconnect(pxy_conn_ctx_t *ctx, pxy_conn_desc_t *this, pxy_conn_desc_t *other, int is_requestor)
|
|
{
|
|
// @attention srvdst should never reach here unless in passthrough mode, its bev may be NULL
|
|
this->closed = 1;
|
|
this->free(this->bev, ctx);
|
|
this->bev = NULL;
|
|
if (other->closed) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_try_disconnect: other->closed, terminate conn, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// Uses only ctx to log disconnect, never any of the bevs
|
|
pxy_log_dbg_disconnect(ctx);
|
|
pxy_conn_term(ctx, is_requestor);
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_try_disconnect_child(pxy_conn_child_ctx_t *ctx, pxy_conn_desc_t *this, pxy_conn_desc_t *other)
|
|
{
|
|
this->closed = 1;
|
|
this->free(this->bev, ctx->conn);
|
|
this->bev = NULL;
|
|
if (other->closed) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_try_disconnect_child: other->closed, terminate conn, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// Uses only ctx to log disconnect, never any of the bevs
|
|
pxy_log_dbg_disconnect_child(ctx);
|
|
pxy_conn_term_child(ctx);
|
|
}
|
|
}
|
|
|
|
int
|
|
pxy_try_consume_last_input(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
|
|
{
|
|
/* if there is data pending in the closed connection,
|
|
* handle it here, otherwise it will be lost. */
|
|
if (evbuffer_get_length(bufferevent_get_input(bev))) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_consume_last_input: evbuffer_get_length(inbuf) > 0, terminate conn, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (pxy_bev_readcb_preexec_logging_and_stats(bev, ctx) == -1) {
|
|
return -1;
|
|
}
|
|
ctx->protoctx->bev_readcb(bev, ctx);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pxy_try_consume_last_input_child(struct bufferevent *bev, pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
/* if there is data pending in the closed connection,
|
|
* handle it here, otherwise it will be lost. */
|
|
if (evbuffer_get_length(bufferevent_get_input(bev))) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINE, "pxy_try_consume_last_input_child: evbuffer_get_length(inbuf) > 0, terminate conn, child fd=%d, fd=%d\n", ctx->fd, ctx->conn->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (pxy_bev_readcb_preexec_logging_and_stats_child(bev, ctx) == -1) {
|
|
return -1;
|
|
}
|
|
ctx->protoctx->bev_readcb(bev, ctx);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pxy_set_dstaddr(pxy_conn_ctx_t *ctx)
|
|
{
|
|
if (sys_sockaddr_str((struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen, &ctx->dsthost_str, &ctx->dstport_str) != 0) {
|
|
// sys_sockaddr_str() may fail due to either malloc() or getnameinfo()
|
|
ctx->enomem = 1;
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
pxy_bev_readcb_preexec_logging_and_stats(struct bufferevent *bev, pxy_conn_ctx_t *ctx)
|
|
{
|
|
if (bev == ctx->src.bev || bev == ctx->dst.bev) {
|
|
struct evbuffer *inbuf = bufferevent_get_input(bev);
|
|
size_t inbuf_size = evbuffer_get_length(inbuf);
|
|
|
|
if (bev == ctx->src.bev) {
|
|
ctx->thr->intif_in_bytes += inbuf_size;
|
|
} else {
|
|
ctx->thr->intif_out_bytes += inbuf_size;
|
|
}
|
|
|
|
if (WANT_CONTENT_LOG(ctx->conn)) {
|
|
if (ctx->proto != PROTO_PASSTHROUGH) {
|
|
// HTTP content logging at this point may record certain header lines twice, if we have not seen all headers yet
|
|
return pxy_log_content_inbuf(ctx, inbuf, (bev == ctx->src.bev));
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Callback for read events on the up- and downstream connection bufferevents.
|
|
* Called when there is data ready in the input evbuffer.
|
|
*/
|
|
void
|
|
pxy_bev_readcb(struct bufferevent *bev, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *ctx = arg;
|
|
|
|
if (pxy_bev_readcb_preexec_logging_and_stats(bev, ctx) == -1) {
|
|
goto out;
|
|
}
|
|
|
|
if (!ctx->connected) {
|
|
log_err_level_printf(LOG_CRIT, "pxy_bev_readcb: readcb called when not connected - aborting.\n");
|
|
log_exceptcb();
|
|
return;
|
|
}
|
|
|
|
ctx->atime = time(NULL);
|
|
ctx->protoctx->bev_readcb(bev, ctx);
|
|
|
|
out:
|
|
if (ctx->term || ctx->enomem) {
|
|
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
|
|
}
|
|
}
|
|
|
|
int
|
|
pxy_bev_readcb_preexec_logging_and_stats_child(struct bufferevent *bev, pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
struct evbuffer *inbuf = bufferevent_get_input(bev);
|
|
size_t inbuf_size = evbuffer_get_length(inbuf);
|
|
|
|
if (bev == ctx->src.bev) {
|
|
ctx->conn->thr->extif_out_bytes += inbuf_size;
|
|
} else {
|
|
ctx->conn->thr->extif_in_bytes += inbuf_size;
|
|
}
|
|
|
|
if (WANT_CONTENT_LOG(ctx->conn)) {
|
|
if (ctx->proto != PROTO_PASSTHROUGH) {
|
|
return pxy_log_content_inbuf((pxy_conn_ctx_t *)ctx, inbuf, (bev == ctx->src.bev));
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
pxy_bev_readcb_child(struct bufferevent *bev, void *arg)
|
|
{
|
|
pxy_conn_child_ctx_t *ctx = arg;
|
|
|
|
if (pxy_bev_readcb_preexec_logging_and_stats_child(bev, ctx) == -1) {
|
|
goto out;
|
|
}
|
|
|
|
if (!ctx->connected) {
|
|
log_err_level_printf(LOG_CRIT, "pxy_bev_readcb_child: readcb called when not connected - aborting.\n");
|
|
log_exceptcb();
|
|
return;
|
|
}
|
|
|
|
ctx->conn->atime = time(NULL);
|
|
ctx->protoctx->bev_readcb(bev, ctx);
|
|
|
|
out:
|
|
if (ctx->conn->term || ctx->conn->enomem) {
|
|
pxy_conn_free(ctx->conn, ctx->conn->term ? ctx->conn->term_requestor : (bev == ctx->src.bev));
|
|
return;
|
|
}
|
|
|
|
if (ctx->term) {
|
|
pxy_conn_free_child(ctx);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Callback for write events on the up- and downstream connection bufferevents.
|
|
* Called when either all data from the output evbuffer has been written,
|
|
* or if the outbuf is only half full again after having been full.
|
|
*/
|
|
void
|
|
pxy_bev_writecb(struct bufferevent *bev, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *ctx = arg;
|
|
|
|
ctx->atime = time(NULL);
|
|
ctx->protoctx->bev_writecb(bev, ctx);
|
|
|
|
if (ctx->term || ctx->enomem) {
|
|
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_bev_writecb_child(struct bufferevent *bev, void *arg)
|
|
{
|
|
pxy_conn_child_ctx_t *ctx = arg;
|
|
|
|
ctx->conn->atime = time(NULL);
|
|
ctx->protoctx->bev_writecb(bev, ctx);
|
|
|
|
if (ctx->conn->term || ctx->conn->enomem) {
|
|
pxy_conn_free(ctx->conn, ctx->conn->term ? ctx->conn->term_requestor : (bev == ctx->src.bev));
|
|
return;
|
|
}
|
|
|
|
if (ctx->term) {
|
|
pxy_conn_free_child(ctx);
|
|
}
|
|
}
|
|
|
|
int
|
|
pxy_bev_eventcb_postexec_logging_and_stats(struct bufferevent *bev, short events, pxy_conn_ctx_t *ctx)
|
|
{
|
|
if (ctx->term || ctx->enomem) {
|
|
return -1;
|
|
}
|
|
|
|
if (events & BEV_EVENT_CONNECTED) {
|
|
// Passthrough proto does its own connect logging
|
|
if (ctx->proto != PROTO_PASSTHROUGH) {
|
|
if (bev == ctx->src.bev) {
|
|
// @todo When do we reach here? If proto is autossl? Otherwise, src is connected in acceptcb.
|
|
pxy_log_connect_src(ctx);
|
|
} else if (ctx->connected) {
|
|
if (pxy_prepare_logging(ctx) == -1) {
|
|
return -1;
|
|
}
|
|
// Doesn't log connect if proto is http, http proto does its own connect logging
|
|
pxy_log_connect_srvdst(ctx);
|
|
}
|
|
}
|
|
|
|
if (bev == ctx->srvdst.bev) {
|
|
// src and other fd stats are collected in acceptcb functions
|
|
ctx->srvdst_fd = bufferevent_getfd(ctx->srvdst.bev);
|
|
ctx->thr->max_fd = MAX(ctx->thr->max_fd, ctx->srvdst_fd);
|
|
|
|
// Passthrough proto may have a NULL dst.bev
|
|
if (ctx->dst.bev) {
|
|
ctx->dst_fd = bufferevent_getfd(ctx->dst.bev);
|
|
ctx->thr->max_fd = MAX(ctx->thr->max_fd, ctx->dst_fd);
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Callback for meta events on the up- and downstream connection bufferevents.
|
|
* Called when EOF has been reached, a connection has been made, and on errors.
|
|
*/
|
|
void
|
|
pxy_bev_eventcb(struct bufferevent *bev, short events, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *ctx = arg;
|
|
|
|
ctx->atime = time(NULL);
|
|
|
|
if (events & BEV_EVENT_ERROR) {
|
|
log_err_printf("Client-side BEV_EVENT_ERROR\n");
|
|
ctx->thr->errors++;
|
|
}
|
|
|
|
ctx->protoctx->bev_eventcb(bev, events, arg);
|
|
|
|
pxy_bev_eventcb_postexec_logging_and_stats(bev, events, ctx);
|
|
|
|
// Logging functions may set term or enomem too
|
|
// EOF eventcb may call readcb possibly causing enomem
|
|
if (ctx->term || ctx->enomem) {
|
|
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : (bev == ctx->src.bev));
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_bev_eventcb_postexec_stats_child(short events, pxy_conn_child_ctx_t *ctx)
|
|
{
|
|
if (events & BEV_EVENT_CONNECTED) {
|
|
ctx->conn->thr->max_fd = MAX(ctx->conn->thr->max_fd, MAX(bufferevent_getfd(ctx->src.bev), bufferevent_getfd(ctx->dst.bev)));
|
|
}
|
|
}
|
|
|
|
void
|
|
pxy_bev_eventcb_child(struct bufferevent *bev, short events, void *arg)
|
|
{
|
|
pxy_conn_child_ctx_t *ctx = arg;
|
|
|
|
ctx->conn->atime = time(NULL);
|
|
|
|
if (events & BEV_EVENT_ERROR) {
|
|
log_err_printf("Server-side BEV_EVENT_ERROR\n");
|
|
ctx->conn->thr->errors++;
|
|
}
|
|
|
|
ctx->protoctx->bev_eventcb(bev, events, arg);
|
|
|
|
// EOF eventcb may call readcb possibly causing enomem
|
|
if (ctx->conn->term || ctx->conn->enomem) {
|
|
pxy_conn_free(ctx->conn, ctx->conn->term ? ctx->conn->term_requestor : (bev == ctx->src.bev));
|
|
return;
|
|
}
|
|
|
|
if (ctx->term) {
|
|
pxy_conn_free_child(ctx);
|
|
return;
|
|
}
|
|
|
|
pxy_bev_eventcb_postexec_stats_child(events, ctx);
|
|
}
|
|
|
|
/*
|
|
* Complete the connection. This gets called after finding out where to
|
|
* connect to.
|
|
*/
|
|
void
|
|
pxy_conn_connect(pxy_conn_ctx_t *ctx)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_connect: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (!ctx->dstaddrlen) {
|
|
log_err_level_printf(LOG_CRIT, "No target address; aborting connection\n");
|
|
evutil_closesocket(ctx->fd);
|
|
pxy_conn_ctx_free(ctx, 1);
|
|
return;
|
|
}
|
|
|
|
if (OPTS_DEBUG(ctx->opts)) {
|
|
char *host, *port;
|
|
if (sys_sockaddr_str((struct sockaddr *)&ctx->dstaddr, ctx->dstaddrlen, &host, &port) == 0) {
|
|
log_dbg_printf("Connecting to [%s]:%s\n", host, port);
|
|
free(host);
|
|
free(port);
|
|
} else {
|
|
log_dbg_printf("Connecting to [?]:?\n");
|
|
}
|
|
}
|
|
|
|
if (ctx->protoctx->connectcb(ctx) == -1) {
|
|
// @attention Do not try to close conns on the thrmgr thread after setting event callbacks and/or socket connect.
|
|
// The return value of -1 from connectcb indicates that there was a fatal error before event callbacks were set, so we can terminate the connection.
|
|
// Otherwise, it is up to the event callbacks to terminate the connection. This is necessary to avoid multithreading issues.
|
|
if (ctx->term || ctx->enomem) {
|
|
pxy_conn_free(ctx, ctx->term ? ctx->term_requestor : 1);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Defer adding the conn to the conn list of its thread until after a successful conn setup,
|
|
// otherwise pxy_thrmgr_timer_cb() may try to access the conn ctx while it is being freed on failure (signal 6 crash)
|
|
pxy_thrmgr_add_conn(ctx);
|
|
}
|
|
|
|
/*
|
|
* The src fd is readable. This is used to sneak-preview the SNI on SSL
|
|
* connections. If ctx->ev is NULL, it was called manually for a non-SSL
|
|
* connection. If ctx->passthrough is set, it was called a second time
|
|
* after the first ssl callout failed because of client cert auth.
|
|
*/
|
|
void
|
|
pxy_fd_readcb(evutil_socket_t fd, UNUSED short what, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *ctx = arg;
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_fd_readcb: ENTER, fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->atime = time(NULL);
|
|
ctx->protoctx->fd_readcb(fd, what, arg);
|
|
}
|
|
|
|
#if defined(__OpenBSD__) || defined(__linux__)
|
|
static void
|
|
identify_user(UNUSED evutil_socket_t fd, UNUSED short what, void *arg)
|
|
{
|
|
pxy_conn_ctx_t *ctx = arg;
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: ENTER, ctx->fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (ctx->ev) {
|
|
event_free(ctx->ev);
|
|
ctx->ev = NULL;
|
|
}
|
|
|
|
if (ctx->identify_user_count++ >= 50) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Cannot get conn user, ctx->fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
goto redirect;
|
|
} else {
|
|
int rc;
|
|
|
|
sqlite3_reset(ctx->thr->get_user);
|
|
sqlite3_bind_text(ctx->thr->get_user, 1, ctx->srchost_str, -1, NULL);
|
|
rc = sqlite3_step(ctx->thr->get_user);
|
|
|
|
// Retry in case we cannot acquire db file or database: SQLITE_BUSY or SQLITE_LOCKED respectively
|
|
if (rc == SQLITE_BUSY || rc == SQLITE_LOCKED) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: User db busy or locked, retrying, count=%d, ctx->fd=%d\n", ctx->identify_user_count, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
// Do not forget to reset sqlite stmt, or else the userdb may remain busy/locked
|
|
sqlite3_reset(ctx->thr->get_user);
|
|
|
|
ctx->ev = event_new(ctx->evbase, -1, 0, identify_user, ctx);
|
|
if (!ctx->ev)
|
|
goto memout;
|
|
struct timeval retry_delay = {0, 100};
|
|
if (event_add(ctx->ev, &retry_delay) == -1)
|
|
goto memout;
|
|
return;
|
|
} else if (rc == SQLITE_DONE) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Conn has no user, ctx->fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
goto redirect;
|
|
} else if (rc == SQLITE_ROW) {
|
|
char *ether = (char *)sqlite3_column_text(ctx->thr->get_user, 1);
|
|
if (strncasecmp(ether, ctx->ether, 17)) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Ethernet addresses do not match, db=%s, arp cache=%s, ctx->fd=%d\n", ether, ctx->ether, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
goto redirect;
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Passed ethernet address test, %s, ctx->fd=%d\n", ether, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->idletime = time(NULL) - sqlite3_column_int(ctx->thr->get_user, 2);
|
|
if (ctx->idletime > ctx->opts->user_timeout) {
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: User entry timed out, idletime=%u, ctx->fd=%d\n", ctx->idletime, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
goto redirect;
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Passed timeout test, idletime=%u, ctx->fd=%d\n", ctx->idletime, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
ctx->user = strdup((char *)sqlite3_column_text(ctx->thr->get_user, 0));
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Conn user=%s, ctx->fd=%d\n", ctx->user, ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "identify_user: Passed user identification, ctx->fd=%d\n", ctx->fd);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
redirect:
|
|
sqlite3_reset(ctx->thr->get_user);
|
|
|
|
if (ctx->ev) {
|
|
event_free(ctx->ev);
|
|
ctx->ev = NULL;
|
|
}
|
|
return;
|
|
|
|
memout:
|
|
log_err_level_printf(LOG_CRIT, "Aborting connection user identification!\n");
|
|
evutil_closesocket(ctx->fd);
|
|
pxy_conn_ctx_free(ctx, 1);
|
|
}
|
|
#endif /* __OpenBSD__ || __linux__ */
|
|
|
|
#ifdef __linux__
|
|
// Assume proc filesystem support
|
|
#define ARP_CACHE "/proc/net/arp"
|
|
|
|
/*
|
|
* We do not care about multiple matches or expiration status of arp cache entries on Linux.
|
|
*/
|
|
static int NONNULL(1)
|
|
get_client_ether(pxy_conn_ctx_t *ctx)
|
|
{
|
|
int rv = 0;
|
|
|
|
FILE *arp_cache = fopen(ARP_CACHE, "r");
|
|
if (!arp_cache) {
|
|
log_err_level_printf(LOG_CRIT, "Failed to open arp cache: \"" ARP_CACHE "\"\n");
|
|
return -1;
|
|
}
|
|
|
|
// Skip the first line, which contains the header
|
|
char header[1024];
|
|
if (!fgets(header, sizeof(header), arp_cache)) {
|
|
log_err_level_printf(LOG_CRIT, "Failed to skip arp cache header\n");
|
|
rv = -1;
|
|
goto out;
|
|
}
|
|
|
|
char ip[46], ether[18];
|
|
//192.168.0.1 0x1 0x2 00:50:56:2c:bf:e0 * enp3s0f1
|
|
while (fscanf(arp_cache, "%45s %*s %*s %17s %*s %*s", ip, ether) == 2) {
|
|
if (!strncasecmp(ip, ctx->srchost_str, 45)) {
|
|
ctx->ether = strdup(ether);
|
|
rv = 1;
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "Arp entry for %s: %s\n", ip, ether);
|
|
#endif /* DEBUG_PROXY */
|
|
goto out;
|
|
}
|
|
}
|
|
out:
|
|
fclose(arp_cache);
|
|
return rv;
|
|
}
|
|
#endif /* __linux__ */
|
|
|
|
#ifdef __OpenBSD__
|
|
/*
|
|
* This is a modified version of the same function from OpenBSD sources,
|
|
* which has a 3-clause BSD license.
|
|
*/
|
|
static char *
|
|
ether_str(struct sockaddr_dl *sdl)
|
|
{
|
|
char hbuf[NI_MAXHOST];
|
|
u_char *cp;
|
|
|
|
if (sdl->sdl_alen) {
|
|
cp = (u_char *)LLADDR(sdl);
|
|
snprintf(hbuf, sizeof(hbuf), "%02x:%02x:%02x:%02x:%02x:%02x",
|
|
cp[0], cp[1], cp[2], cp[3], cp[4], cp[5]);
|
|
return strdup(hbuf);
|
|
} else {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* This is a modified version of a similar function from OpenBSD sources,
|
|
* which has a 3-clause BSD license.
|
|
*/
|
|
static int NONNULL(2)
|
|
get_client_ether(in_addr_t addr, pxy_conn_ctx_t *ctx)
|
|
{
|
|
int mib[7];
|
|
size_t needed;
|
|
char *lim, *buf = NULL, *next;
|
|
struct rt_msghdr *rtm;
|
|
struct sockaddr_inarp *sin;
|
|
struct sockaddr_dl *sdl;
|
|
int found_entry = 0;
|
|
int rdomain = getrtable();
|
|
|
|
mib[0] = CTL_NET;
|
|
mib[1] = PF_ROUTE;
|
|
mib[2] = 0;
|
|
mib[3] = AF_INET;
|
|
mib[4] = NET_RT_FLAGS;
|
|
mib[5] = RTF_LLINFO;
|
|
mib[6] = rdomain;
|
|
while (1) {
|
|
if (sysctl(mib, 7, NULL, &needed, NULL, 0) == -1) {
|
|
log_err_level_printf(LOG_WARNING, "route-sysctl-estimate\n");
|
|
}
|
|
if (needed == 0) {
|
|
return found_entry;
|
|
}
|
|
if ((buf = realloc(buf, needed)) == NULL) {
|
|
return -1;
|
|
}
|
|
if (sysctl(mib, 7, buf, &needed, NULL, 0) == -1) {
|
|
if (errno == ENOMEM)
|
|
continue;
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "actual retrieval of routing table\n");
|
|
#endif /* DEBUG_PROXY */
|
|
}
|
|
lim = buf + needed;
|
|
break;
|
|
}
|
|
|
|
int expired = 0;
|
|
int incomplete = 0;
|
|
for (next = buf; next < lim; next += rtm->rtm_msglen) {
|
|
rtm = (struct rt_msghdr *)next;
|
|
if (rtm->rtm_version != RTM_VERSION)
|
|
continue;
|
|
sin = (struct sockaddr_inarp *)(next + rtm->rtm_hdrlen);
|
|
sdl = (struct sockaddr_dl *)(sin + 1);
|
|
if (addr) {
|
|
if (addr != sin->sin_addr.s_addr)
|
|
continue;
|
|
found_entry++;
|
|
}
|
|
|
|
char *expire = NULL;
|
|
if (rtm->rtm_flags & (RTF_PERMANENT_ARP | RTF_LOCAL)) {
|
|
expire = "permanent";
|
|
} else if (rtm->rtm_rmx.rmx_expire == 0) {
|
|
expire = "static";
|
|
} else if (rtm->rtm_rmx.rmx_expire > time(NULL)) {
|
|
expire = "active";
|
|
} else {
|
|
expire = "expired";
|
|
expired++;
|
|
}
|
|
|
|
char *ether = ether_str(sdl);
|
|
if (ether) {
|
|
// Record the first unexpired complete entry
|
|
if (!ctx->ether && (found_entry - expired) == 1) {
|
|
// Dup before assignment because we free local var ether below
|
|
ctx->ether = strdup(ether);
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "Arp entry for %s: %s\n", inet_ntoa(sin->sin_addr), ether);
|
|
#endif /* DEBUG_PROXY */
|
|
// Do not care about multiple matches, return immediately
|
|
free(ether);
|
|
goto out;
|
|
}
|
|
} else {
|
|
incomplete++;
|
|
}
|
|
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "Arp entry %u for %s: %s (%s)\n", found_entry, inet_ntoa(sin->sin_addr), ether ? ether : "incomplete", expire);
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (ether) {
|
|
free(ether);
|
|
}
|
|
}
|
|
out:
|
|
free(buf);
|
|
return found_entry - expired - incomplete;
|
|
}
|
|
#endif /* __OpenBSD__ */
|
|
|
|
int
|
|
pxy_userauth(pxy_conn_ctx_t *ctx)
|
|
{
|
|
if (ctx->opts->user_auth && !ctx->user) {
|
|
#if defined(__OpenBSD__) || defined(__linux__)
|
|
int ec;
|
|
#if defined(__OpenBSD__)
|
|
ec = get_client_ether(((struct sockaddr_in *)&ctx->srcaddr)->sin_addr.s_addr, ctx);
|
|
#else /* __linux__ */
|
|
ec = get_client_ether(ctx);
|
|
#endif /* __linux__ */
|
|
if (ec == 1) {
|
|
identify_user(0, 0, ctx);
|
|
return 0;
|
|
} else if (ec == 0) {
|
|
log_err_level_printf(LOG_CRIT, "Cannot find ethernet address of client IP address\n");
|
|
} else if (ec > 1) {
|
|
log_err_level_printf(LOG_CRIT, "Multiple ethernet addresses for the same client IP address\n");
|
|
} else {
|
|
// ec == -1
|
|
log_err_level_printf(LOG_CRIT, "Aborting connection setup (out of memory)!\n");
|
|
}
|
|
#endif /* __OpenBSD__ || __linux__ */
|
|
log_err_level_printf(LOG_CRIT, "Aborting connection setup (user auth)!\n");
|
|
pxy_conn_term(ctx, 1);
|
|
return -1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/*
|
|
* Callback for accept events on the socket listener bufferevent.
|
|
* Called when a new incoming connection has been accepted.
|
|
* Initiates the connection to the server. The incoming connection
|
|
* from the client is not being activated until we have a successful
|
|
* connection to the server, because we need the server's certificate
|
|
* in order to set up the SSL session to the client.
|
|
* For consistency, plain TCP works the same way, even if we could
|
|
* start reading from the client while waiting on the connection to
|
|
* the server to connect.
|
|
*/
|
|
void
|
|
pxy_conn_setup(evutil_socket_t fd,
|
|
struct sockaddr *peeraddr, int peeraddrlen,
|
|
pxy_thrmgr_ctx_t *thrmgr,
|
|
proxyspec_t *spec, opts_t *opts,
|
|
evutil_socket_t clisock)
|
|
{
|
|
#ifdef DEBUG_PROXY
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_setup: ENTER, fd=%d\n", fd);
|
|
|
|
char *host, *port;
|
|
if (sys_sockaddr_str(peeraddr, peeraddrlen, &host, &port) == 0) {
|
|
log_dbg_level_printf(LOG_DBG_MODE_FINEST, "pxy_conn_setup: peer addr=[%s]:%s, fd=%d\n", host, port, fd);
|
|
free(host);
|
|
free(port);
|
|
}
|
|
#endif /* DEBUG_PROXY */
|
|
|
|
if (check_fd_usage(fd) == -1) {
|
|
evutil_closesocket(fd);
|
|
return;
|
|
}
|
|
|
|
/* create per connection state and attach to thread */
|
|
pxy_conn_ctx_t *ctx = pxy_conn_ctx_new(fd, thrmgr, spec, opts, clisock);
|
|
if (!ctx) {
|
|
log_err_level_printf(LOG_CRIT, "Error allocating memory\n");
|
|
evutil_closesocket(fd);
|
|
return;
|
|
}
|
|
|
|
ctx->af = peeraddr->sa_family;
|
|
ctx->thr->max_fd = MAX(ctx->thr->max_fd, ctx->fd);
|
|
|
|
/* determine original destination of connection */
|
|
if (spec->natlookup) {
|
|
/* NAT engine lookup */
|
|
ctx->dstaddrlen = sizeof(struct sockaddr_storage);
|
|
if (spec->natlookup((struct sockaddr *)&ctx->dstaddr, &ctx->dstaddrlen, fd, peeraddr, peeraddrlen) == -1) {
|
|
log_err_printf("Connection not found in NAT state table, aborting connection\n");
|
|
goto out;
|
|
}
|
|
} else if (spec->connect_addrlen > 0) {
|
|
/* static forwarding */
|
|
ctx->dstaddrlen = spec->connect_addrlen;
|
|
memcpy(&ctx->dstaddr, &spec->connect_addr, ctx->dstaddrlen);
|
|
} else {
|
|
/* SNI mode */
|
|
if (!ctx->spec->ssl) {
|
|
/* if this happens, the proxyspec parser is broken */
|
|
log_err_printf("SNI mode used for non-SSL connection; aborting connection\n");
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (sys_sockaddr_str(peeraddr, peeraddrlen, &ctx->srchost_str, &ctx->srcport_str) != 0) {
|
|
goto memout;
|
|
}
|
|
|
|
/* prepare logging, part 1 */
|
|
if (opts->pcaplog
|
|
#ifndef WITHOUT_MIRROR
|
|
|| opts->mirrorif
|
|
#endif /* !WITHOUT_MIRROR */
|
|
#ifdef HAVE_LOCAL_PROCINFO
|
|
|| opts->lprocinfo
|
|
#endif /* HAVE_LOCAL_PROCINFO */
|
|
) {
|
|
}
|
|
|
|
// srcaddr is used by packet logging and userauth
|
|
ctx->srcaddrlen = peeraddrlen;
|
|
memcpy(&ctx->srcaddr, peeraddr, ctx->srcaddrlen);
|
|
|
|
ctx->protoctx->fd_readcb(ctx->fd, 0, ctx);
|
|
return;
|
|
|
|
memout:
|
|
log_err_level_printf(LOG_CRIT, "Aborting connection setup (out of memory)!\n");
|
|
out:
|
|
evutil_closesocket(fd);
|
|
pxy_conn_ctx_free(ctx, 1);
|
|
}
|
|
|
|
/* vim: set noet ft=c: */
|