From ac3607a84114dc358a59fc96494238b3ca987b0c Mon Sep 17 00:00:00 2001 From: Soner Tari Date: Sun, 19 Sep 2021 01:34:12 +0300 Subject: [PATCH] Add deferred pass and block actions We should defer pass and/or block actions as long as possible, because a higher precedence rule in SSL filter should be able to override (cancel) deferred pass and block actions taken by a lower precedence rule in Dst Host filter. And in HTTP filter the same applies to deferred block actions taken by Dst Host and SSL filters. Also, thanks to this new deferred actions, now HTTP filter can keep enabled divert and split modes. In other words, a higher precedence HTTP filter rule can cancel a deferred block action set by a lower precedence rule earlier, which was not possible before without deferred actions and rule precedence. And other improvements. --- README.md | 6 ++-- src/protoautossl.c | 4 +++ src/protohttp.c | 77 +++++++++++++++++++++++++++--------------- src/protopassthrough.c | 4 +++ src/protossl.c | 21 ++++++++++-- src/prototcp.c | 41 ++++++++++++++++++---- src/prototcp.h | 2 +- src/pxyconn.c | 24 +++++++++++++ src/pxyconn.h | 7 +++- src/sslproxy.1 | 6 ++-- 10 files changed, 147 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index 597f74c..d02a46d 100644 --- a/README.md +++ b/README.md @@ -383,9 +383,9 @@ In terms of possible filter actions, - Dst Host filtering rules can take all of the filter and log actions. - SSL filtering rules can take all of the filter and log actions. -- HTTP filtering rules take the match and block filter actions, but not the -divert, split, or pass actions. Also, HTTP filtering rules can only disable -logging. +- HTTP filtering rules can take match and block filter actions, can keep +enabled divert and split modes, but cannot take pass action. Also, HTTP +filtering rules can only disable logging. Log actions do not configure any loggers. Global loggers for respective log actions should have been configured for those log actions to have any effect. diff --git a/src/protoautossl.c b/src/protoautossl.c index 7b0fe3e..c28f904 100644 --- a/src/protoautossl.c +++ b/src/protoautossl.c @@ -159,6 +159,10 @@ protoautossl_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) } #endif /* !WITHOUT_USERAUTH */ + if (pxyconn_apply_deferred_block_action(ctx)) { + return; + } + if (autossl_ctx->clienthello_search) { if (protoautossl_peek_and_upgrade(ctx) != 0) { return; diff --git a/src/protohttp.c b/src/protohttp.c index e376b70..6225d7f 100644 --- a/src/protohttp.c +++ b/src/protohttp.c @@ -387,7 +387,7 @@ protohttp_filter_request_header_line(const char *line, protohttp_ctx_t *http_ctx } static int NONNULL(1,2) -protossl_match_host(pxy_conn_ctx_t *ctx, filter_site_t *site) +protohttp_match_host(pxy_conn_ctx_t *ctx, filter_site_t *site) { protohttp_ctx_t *http_ctx = ctx->protoctx->arg; @@ -415,7 +415,7 @@ protossl_match_host(pxy_conn_ctx_t *ctx, filter_site_t *site) } static int NONNULL(1,2) -protossl_match_uri(pxy_conn_ctx_t *ctx, filter_site_t *site) +protohttp_match_uri(pxy_conn_ctx_t *ctx, filter_site_t *site) { protohttp_ctx_t *http_ctx = ctx->protoctx->arg; @@ -450,7 +450,7 @@ protohttp_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) if (http_ctx->http_host) { filter_site_t *site = list->host; while (site) { - if (protossl_match_host(ctx, site)) { + if (protohttp_match_host(ctx, site)) { // Do not print the surrounding slashes log_err_level_printf(LOG_INFO, "Found site: %s for %s:%s, %s:%s" #ifndef WITHOUT_USERAUTH @@ -480,7 +480,7 @@ protohttp_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) if (http_ctx->http_uri) { filter_site_t *site = list->uri; while (site) { - if (protossl_match_uri(ctx, site)) { + if (protohttp_match_uri(ctx, site)) { // Do not print the surrounding slashes log_err_level_printf(LOG_INFO, "Found site: %s for %s:%s, %s:%s" #ifndef WITHOUT_USERAUTH @@ -515,15 +515,32 @@ protohttp_apply_filter(pxy_conn_ctx_t *ctx) int rv = 0; unsigned int action; if ((action = pxyconn_filter(ctx, protohttp_filter))) { - if (action & FILTER_ACTION_BLOCK) { - ctx->filter_precedence = action & FILTER_PRECEDENCE; + ctx->filter_precedence = action & FILTER_PRECEDENCE; + + if (action & FILTER_ACTION_DIVERT) { + if (ctx->divert) { + // Override any deferred block action, if already in divert mode (keep divert mode) + ctx->deferred_action = FILTER_ACTION_NONE; + } else { + log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable divert mode\n"); + } + } + else if (action & FILTER_ACTION_SPLIT) { + if (!ctx->divert) { + // Override any deferred block action, if already in split mode (keep split mode) + ctx->deferred_action = FILTER_ACTION_NONE; + } else { + log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable split mode\n"); + } + } + else if (action & FILTER_ACTION_PASS) { + log_err_level_printf(LOG_WARNING, "HTTP filter cannot take pass action\n"); + } + else if (action & FILTER_ACTION_BLOCK) { + ctx->deferred_action = FILTER_ACTION_NONE; pxy_conn_term(ctx, 1); rv = 1; } - else if (action & (FILTER_ACTION_DIVERT | FILTER_ACTION_SPLIT | FILTER_ACTION_PASS)) { - log_err_level_printf(LOG_WARNING, "HTTP filter cannot take divert, split, or pass actions, any log actions are ignored too\n"); - goto out; - } //else { /* FILTER_ACTION_MATCH */ } if (action & (FILTER_LOG_CONTENT | FILTER_LOG_PCAP @@ -532,9 +549,9 @@ protohttp_apply_filter(pxy_conn_ctx_t *ctx) #endif /* !WITHOUT_MIRROR */ )) { #ifndef WITHOUT_MIRROR - log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable content, pcap, and mirror logging\n"); + log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable content, pcap, or mirror logging\n"); #else /* !WITHOUT_MIRROR */ - log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable content and pcap logging\n"); + log_err_level_printf(LOG_WARNING, "HTTP filter cannot enable content or pcap logging\n"); #endif /* WITHOUT_MIRROR */ } @@ -555,11 +572,16 @@ protohttp_apply_filter(pxy_conn_ctx_t *ctx) ctx->log_mirror = 0; #endif /* !WITHOUT_MIRROR */ } -out: + + // Cannot defer block action any longer + // Match action should not override any deferred action, hence no 'else if' + if (pxyconn_apply_deferred_block_action(ctx)) + rv = 1; + return rv; } -static void NONNULL(1,2,3,5) +static int WUNRES NONNULL(1,2,3,5) protohttp_filter_request_header(struct evbuffer *inbuf, struct evbuffer *outbuf, protohttp_ctx_t *http_ctx, enum conn_type type, pxy_conn_ctx_t *ctx) { char *line; @@ -577,7 +599,7 @@ protohttp_filter_request_header(struct evbuffer *inbuf, struct evbuffer *outbuf, } else { log_finer_va("REMOVE= %s", line); if (ctx->enomem) { - return; + return -1; } } free(line); @@ -590,25 +612,28 @@ protohttp_filter_request_header(struct evbuffer *inbuf, struct evbuffer *outbuf, } if (http_ctx->seen_req_header) { - if (protohttp_apply_filter(ctx)) { - return; - } + if (type == CONN_TYPE_PARENT) { + if (protohttp_apply_filter(ctx)) { + return -1; + } - /* request header complete */ - if ((type == CONN_TYPE_PARENT) && ctx->spec->opts->deny_ocsp) { - protohttp_ocsp_deny(ctx, http_ctx); + /* request header complete */ + if (ctx->spec->opts->deny_ocsp) { + protohttp_ocsp_deny(ctx, http_ctx); + } } if (ctx->enomem) { - return; + return -1; } /* no data left after parsing headers? */ if (evbuffer_get_length(inbuf) == 0) { - return; + return 0; } evbuffer_add_buffer(outbuf, inbuf); } + return 0; } #ifndef WITHOUT_USERAUTH @@ -798,8 +823,7 @@ protohttp_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) /* request header munging */ if (!http_ctx->seen_req_header) { log_finest_va("HTTP Request Header, size=%zu", evbuffer_get_length(inbuf)); - protohttp_filter_request_header(inbuf, outbuf, http_ctx, ctx->type, ctx); - if (ctx->enomem) { + if (protohttp_filter_request_header(inbuf, outbuf, http_ctx, ctx->type, ctx) == -1) { return; } } else { @@ -986,8 +1010,7 @@ protohttp_bev_readcb_src_child(struct bufferevent *bev, pxy_conn_child_ctx_t *ct if (!http_ctx->seen_req_header) { log_finest_va("HTTP Request Header, size=%zu", evbuffer_get_length(inbuf)); // @todo Just remove SSLproxy line, do not filter request on the server side? - protohttp_filter_request_header(inbuf, outbuf, http_ctx, ctx->type, ctx->conn); - if (ctx->conn->enomem) { + if (protohttp_filter_request_header(inbuf, outbuf, http_ctx, ctx->type, ctx->conn) == -1) { return; } } else { diff --git a/src/protopassthrough.c b/src/protopassthrough.c index 5b72441..87a45c5 100644 --- a/src/protopassthrough.c +++ b/src/protopassthrough.c @@ -149,6 +149,10 @@ protopassthrough_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) } #endif /* !WITHOUT_USERAUTH */ + if (pxyconn_apply_deferred_block_action(ctx)) { + return; + } + evbuffer_add_buffer(bufferevent_get_output(ctx->srvdst.bev), bufferevent_get_input(bev)); pxy_try_set_watermark(bev, ctx, ctx->srvdst.bev); } diff --git a/src/protossl.c b/src/protossl.c index 5f5e7be..0238879 100644 --- a/src/protossl.c +++ b/src/protossl.c @@ -760,18 +760,24 @@ protossl_apply_filter(pxy_conn_ctx_t *ctx) ctx->filter_precedence = action & FILTER_PRECEDENCE; if (action & FILTER_ACTION_DIVERT) { + ctx->deferred_action = FILTER_ACTION_NONE; ctx->divert = 1; } else if (action & FILTER_ACTION_SPLIT) { + ctx->deferred_action = FILTER_ACTION_NONE; ctx->divert = 0; } else if (action & FILTER_ACTION_PASS) { + ctx->deferred_action = FILTER_ACTION_NONE; ctx->pass = 1; rv = 1; } else if (action & FILTER_ACTION_BLOCK) { - pxy_conn_term(ctx, 1); - rv = 1; + // Always defer block action, the only action we can defer from this point on + // This block action should override any deferred pass action, + // because the current rule must have a higher precedence + log_fine("Deferring block action"); + ctx->deferred_action = FILTER_ACTION_BLOCK; } //else { /* FILTER_ACTION_MATCH */ } @@ -784,6 +790,12 @@ protossl_apply_filter(pxy_conn_ctx_t *ctx) ctx->log_mirror = !!(action & FILTER_LOG_MIRROR); #endif /* !WITHOUT_MIRROR */ } + + // Cannot defer pass action any longer + // Match action should not override pass action, hence no 'else if' + if (pxyconn_apply_deferred_pass_action(ctx)) + rv = 1; + return rv; } @@ -830,6 +842,8 @@ protossl_srcssl_create(pxy_conn_ctx_t *ctx, SSL *origssl) ctx->enomem = 1; } + // Defers any block action until HTTP filter application + // or until the first src readcb of non-http protos if (protossl_apply_filter(ctx)) { cert_free(cert); return NULL; @@ -1648,7 +1662,8 @@ protossl_bev_eventcb_connected_srvdst(UNUSED struct bufferevent *bev, pxy_conn_c } #endif /* !WITHOUT_USERAUTH */ - if (prototcp_apply_filter(ctx)) { + // Defer any pass or block action until SSL filter application below + if (prototcp_apply_filter(ctx, FILTER_ACTION_PASS | FILTER_ACTION_BLOCK)) { return; } diff --git a/src/prototcp.c b/src/prototcp.c index 5cc0a24..530d66f 100644 --- a/src/prototcp.c +++ b/src/prototcp.c @@ -282,6 +282,10 @@ prototcp_bev_readcb_src(struct bufferevent *bev, pxy_conn_ctx_t *ctx) } #endif /* !WITHOUT_USERAUTH */ + if (pxyconn_apply_deferred_block_action(ctx)) { + return; + } + struct evbuffer *inbuf = bufferevent_get_input(bev); struct evbuffer *outbuf = bufferevent_get_output(ctx->dst.bev); @@ -551,27 +555,48 @@ prototcp_dsthost_filter(pxy_conn_ctx_t *ctx, filter_list_t *list) } int -prototcp_apply_filter(pxy_conn_ctx_t *ctx) +prototcp_apply_filter(pxy_conn_ctx_t *ctx, unsigned int defer_action) { int rv = 0; unsigned int action; if ((action = pxyconn_filter(ctx, prototcp_dsthost_filter))) { ctx->filter_precedence = action & FILTER_PRECEDENCE; + // If we reach here, the matching filtering rule must have a higher precedence + // Override any deferred action, if the current rule action is not match + // Match action cannot override other filter actions + if (action & FILTER_ACTION_DIVERT) { + ctx->deferred_action = FILTER_ACTION_NONE; ctx->divert = 1; } else if (action & FILTER_ACTION_SPLIT) { + ctx->deferred_action = FILTER_ACTION_NONE; ctx->divert = 0; } else if (action & FILTER_ACTION_PASS) { - protopassthrough_engage(ctx); - ctx->pass = 1; - rv = 1; + if (defer_action & FILTER_ACTION_PASS) { + log_fine("Deferring pass action"); + ctx->deferred_action = FILTER_ACTION_PASS; + } + else { + ctx->deferred_action = FILTER_ACTION_NONE; + protopassthrough_engage(ctx); + ctx->pass = 1; + rv = 1; + } } else if (action & FILTER_ACTION_BLOCK) { - pxy_conn_term(ctx, 1); - rv = 1; + if (defer_action & FILTER_ACTION_BLOCK) { + // This block action should override any deferred pass action, + // because the current rule must have a higher precedence + log_fine("Deferring block action"); + ctx->deferred_action = FILTER_ACTION_BLOCK; + } + else { + pxy_conn_term(ctx, 1); + rv = 1; + } } //else { /* FILTER_ACTION_MATCH */ } @@ -604,7 +629,9 @@ prototcp_bev_eventcb_connected_srvdst(UNUSED struct bufferevent *bev, pxy_conn_c } #endif /* !WITHOUT_USERAUTH */ - if (prototcp_apply_filter(ctx)) { + // Defer any block action until HTTP filter application or the first src readcb of non-http proto + // We cannot defer pass actions from this point on + if (prototcp_apply_filter(ctx, FILTER_ACTION_BLOCK)) { return; } diff --git a/src/prototcp.h b/src/prototcp.h index aee4be6..41c65b7 100644 --- a/src/prototcp.h +++ b/src/prototcp.h @@ -53,7 +53,7 @@ void prototcp_bev_eventcb_error_src(struct bufferevent *, pxy_conn_ctx_t *) NONN void prototcp_bev_eventcb_eof_dst(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2); void prototcp_bev_eventcb_error_dst(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2); -int prototcp_apply_filter(pxy_conn_ctx_t *) NONNULL(1); +int prototcp_apply_filter(pxy_conn_ctx_t *, unsigned int) NONNULL(1); void prototcp_bev_eventcb_eof_srvdst(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2); void prototcp_bev_eventcb_error_srvdst(struct bufferevent *, pxy_conn_ctx_t *) NONNULL(1,2); diff --git a/src/pxyconn.c b/src/pxyconn.c index 471ee9d..8009acd 100644 --- a/src/pxyconn.c +++ b/src/pxyconn.c @@ -1995,6 +1995,30 @@ pxy_userauth(pxy_conn_ctx_t *ctx) } #endif /* !WITHOUT_USERAUTH */ +int +pxyconn_apply_deferred_pass_action(pxy_conn_ctx_t *ctx) +{ + if (ctx->deferred_action & FILTER_ACTION_PASS) { + log_fine("Applying deferred pass action"); + ctx->deferred_action = FILTER_ACTION_NONE; + protopassthrough_engage(ctx); + ctx->pass = 1; + return 1; + } + return 0; +} + +int +pxyconn_apply_deferred_block_action(pxy_conn_ctx_t *ctx) +{ + if (ctx->deferred_action & FILTER_ACTION_BLOCK) { + log_fine("Applying deferred block action"); + pxy_conn_term(ctx, 1); + return 1; + } + return 0; +} + unsigned int pxyconn_set_filter_action(pxy_conn_ctx_t *ctx, filter_site_t *site) { diff --git a/src/pxyconn.h b/src/pxyconn.h index 38c4505..2e88844 100644 --- a/src/pxyconn.h +++ b/src/pxyconn.h @@ -333,10 +333,13 @@ struct pxy_conn_ctx { unsigned int log_mirror : 1; #endif /* !WITHOUT_MIRROR */ - // Highest precedence applied by filtering rules + // The precedence of filtering rule applied // precedence can only go up not down unsigned int filter_precedence; + // Deferred filter action from an earlier filter application + unsigned int deferred_action; + #ifdef HAVE_LOCAL_PROCINFO /* local process information */ pxy_conn_lproc_desc_t lproc; @@ -441,6 +444,8 @@ int pxy_is_listuser(userlist_t *, const char * void pxy_classify_user(pxy_conn_ctx_t *) NONNULL(1); void pxy_userauth(pxy_conn_ctx_t *) NONNULL(1); #endif /* !WITHOUT_USERAUTH */ +int pxyconn_apply_deferred_pass_action(pxy_conn_ctx_t *) NONNULL(1); +int pxyconn_apply_deferred_block_action(pxy_conn_ctx_t *) NONNULL(1); unsigned int pxyconn_set_filter_action(pxy_conn_ctx_t *, filter_site_t *) NONNULL(1,2); unsigned int pxyconn_filter(pxy_conn_ctx_t *, proto_filter_func_t) NONNULL(1); void pxy_conn_setup(evutil_socket_t, struct sockaddr *, int, diff --git a/src/sslproxy.1 b/src/sslproxy.1 index cfbd8fc..1bfd27d 100644 --- a/src/sslproxy.1 +++ b/src/sslproxy.1 @@ -396,9 +396,9 @@ In terms of possible filter actions, .LP - Dst Host filtering rules can take all of the filter and log actions. - SSL filtering rules can take all of the filter and log actions. - - HTTP filtering rules take the match and block filter actions, but not the -divert, split, or pass actions. Also, HTTP filtering rules can only disable -logging. + - HTTP filtering rules can take match and block filter actions, can keep +enabled divert and split modes, but cannot take pass action. Also, HTTP +filtering rules can only disable logging. .LP Log actions do not configure any loggers. Global loggers for respective log actions should have been configured for those log actions to have any effect.