From 73b7af6379688bc06717396e36bfdf55a994af95 Mon Sep 17 00:00:00 2001 From: Maxim Antonov Date: Thu, 17 Dec 2020 15:05:23 +0700 Subject: [PATCH] Add support for filering client request bodies ... by using CLIENT-BODY-FILTER filters which can be enabled with the client-body-filter action. --- actionlist.h | 1 + actions.c | 5 +- cgiedit.c | 21 ++- default.filter | 14 ++ doc/source/user-manual.sgml | 88 +++++++++- filters.c | 181 +++++++++++++++++--- filters.h | 2 + jcc.c | 290 ++++++++++++++++++++++++++++++++- loaders.c | 8 + parsers.c | 65 ++++++-- parsers.h | 4 +- project.h | 13 +- templates/edit-actions-for-url | 13 ++ 13 files changed, 648 insertions(+), 57 deletions(-) diff --git a/actionlist.h b/actionlist.h index 6129bb00..fc7f5142 100644 --- a/actionlist.h +++ b/actionlist.h @@ -56,6 +56,7 @@ DEFINE_CGI_PARAM_NO_RADIO("block", ACTION_BLOCK, ACTION_STR DEFINE_ACTION_STRING ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR) DEFINE_CGI_PARAM_RADIO ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR, "block", 0) DEFINE_CGI_PARAM_RADIO ("change-x-forwarded-for", ACTION_CHANGE_X_FORWARDED_FOR, ACTION_STRING_CHANGE_X_FORWARDED_FOR, "add", 1) +DEFINE_ACTION_MULTI ("client-body-filter", ACTION_MULTI_CLIENT_BODY_FILTER) DEFINE_ACTION_MULTI ("client-header-filter", ACTION_MULTI_CLIENT_HEADER_FILTER) DEFINE_ACTION_MULTI ("client-header-tagger", ACTION_MULTI_CLIENT_HEADER_TAGGER) DEFINE_ACTION_STRING ("content-type-overwrite", ACTION_CONTENT_TYPE_OVERWRITE, ACTION_STRING_CONTENT_TYPE) diff --git a/actions.c b/actions.c index 7905231f..6a30577c 100644 --- a/actions.c +++ b/actions.c @@ -1117,6 +1117,8 @@ static const char *filter_type_to_string(enum filter_type filter_type) #endif case FT_SUPPRESS_TAG: return "suppress tag filter"; + case FT_CLIENT_BODY_FILTER: + return "client body filter"; case FT_INVALID_FILTER: return "invalid filter type"; } @@ -1187,7 +1189,8 @@ static int action_spec_is_valid(struct client_state *csp, const struct action_sp {ACTION_MULTI_CLIENT_HEADER_FILTER, FT_CLIENT_HEADER_FILTER}, {ACTION_MULTI_SERVER_HEADER_FILTER, FT_SERVER_HEADER_FILTER}, {ACTION_MULTI_CLIENT_HEADER_TAGGER, FT_CLIENT_HEADER_TAGGER}, - {ACTION_MULTI_SERVER_HEADER_TAGGER, FT_SERVER_HEADER_TAGGER} + {ACTION_MULTI_SERVER_HEADER_TAGGER, FT_SERVER_HEADER_TAGGER}, + {ACTION_MULTI_CLIENT_BODY_FILTER, FT_CLIENT_BODY_FILTER} }; int errors = 0; int i; diff --git a/cgiedit.c b/cgiedit.c index e979ebc3..4f73db2b 100644 --- a/cgiedit.c +++ b/cgiedit.c @@ -240,6 +240,18 @@ static const struct filter_type_info filter_type_info[] = "server-header-tagger-all", "server_header_tagger_all", "E", "SERVER-HEADER-TAGGER" }, + { + ACTION_MULTI_SUPPRESS_TAG, + "suppress-tag-params", "suppress-tag", + "suppress-tag-all", "suppress_tag_all", + "U", "SUPPRESS-TAG" + }, + { + ACTION_MULTI_CLIENT_BODY_FILTER, + "client-body-filter-params", "client-body-filter", + "client-body-filter-all", "client_body_filter_all", + "P", "CLIENT-BODY-FILTER" + }, #ifdef FEATURE_EXTERNAL_FILTERS { ACTION_MULTI_EXTERNAL_FILTER, @@ -248,12 +260,6 @@ static const struct filter_type_info filter_type_info[] = "E", "EXTERNAL-CONTENT-FILTER" }, #endif - { - ACTION_MULTI_SUPPRESS_TAG, - "suppress-tag-params", "suppress-tag", - "suppress-tag-all", "suppress_tag_all", - "U", "SUPPRESS-TAG" - }, }; /* FIXME: Following non-static functions should be prototyped in .h or made static */ @@ -3187,6 +3193,9 @@ jb_err cgi_edit_actions_submit(struct client_state *csp, case 'E': multi_action_index = ACTION_MULTI_SERVER_HEADER_TAGGER; break; + case 'P': + multi_action_index = ACTION_MULTI_CLIENT_BODY_FILTER; + break; default: log_error(LOG_LEVEL_ERROR, "Unknown filter type: %c for filter %s. Filter ignored.", type, name); diff --git a/default.filter b/default.filter index f266e154..901bb687 100644 --- a/default.filter +++ b/default.filter @@ -906,3 +906,17 @@ s@^X-Privoxy-Control:\s*@@i SERVER-HEADER-FILTER: privoxy-control Removes X-Privoxy-Control headers. s@^X-Privoxy-Control:.*@@i + +################################################################################# +# +# client-body: Modify client request body +# +################################################################################# +CLIENT-BODY-FILTER: remove-first-byte Removes the first byte from the request body +s@^.@@ + +CLIENT-BODY-FILTER: remove-test Removes "test" everywhere in the request body +s@test@@g + +CLIENT-BODY-FILTER: overwrite-test-value Overwrites the value of the "test" variable with blafasel +s@(test=)[^&\s]*@$1blafasel@g diff --git a/doc/source/user-manual.sgml b/doc/source/user-manual.sgml index cb1243d8..d7477d71 100644 --- a/doc/source/user-manual.sgml +++ b/doc/source/user-manual.sgml @@ -2955,6 +2955,83 @@ example.org/blocked-example-page + + +client-body-filter + + + + Typical use: + + + Rewrite or remove client request body. + + + + + + Effect: + + + All request bodies to which this action applies are filtered on-the-fly through + the specified regular expression based substitutions. + + + + + + Type: + + + Multi-value. + + + + + Parameter: + + + The name of a client-body filter, as defined in one of the + filter files. + + + + + + Notes: + + + Please refer to the filter file chapter + to learn how to create your own client-body filters. + + + The distribution default.filter file contains a selection of + client-body filters for example purposes. + + + The amount of data that can be filtered is limited by the + buffer-limit + option in the main config file. The + default is 4096 KB (4 Megs). Once this limit is exceeded, the whole + request body is passed through unfiltered. + + + + + + Example usage (section): + + +# Remove "test" everywhere in the request body +{+client-body-filter{remove-test}} +/ + + + + + + + @@ -4062,7 +4139,7 @@ problem-host.example.com action is not available. - The amount of data that can be filtered is limited to the + The amount of data that can be filtered is limited by the buffer-limit option in the main config file. The default is 4096 KB (4 Megs). Once this limit is exceeded, the buffered @@ -6889,9 +6966,11 @@ stupid-server.example.com/ filter to rewrite the content that is send to the client, client-header-filter - to rewrite headers that are send by the client, and + to rewrite headers that are send by the client, server-header-filter - to rewrite headers that are send by the server. + to rewrite headers that are send by the server, and + client-body-filter + to rewrite client request body. @@ -6950,7 +7029,8 @@ stupid-server.example.com/ filter file is organized in sections, which are called filters here. Each filter consists of a heading line, that starts with one of the keywords FILTER:, - CLIENT-HEADER-FILTER: or SERVER-HEADER-FILTER: + CLIENT-HEADER-FILTER:, SERVER-HEADER-FILTER: or + CLIENT-BODY-FILTER: followed by the filter's name, and a short (one line) description of what it does. Below that line come the jobs, i.e. lines that define the actual diff --git a/filters.c b/filters.c index 748042c6..95fbe8d7 100644 --- a/filters.c +++ b/filters.c @@ -1568,25 +1568,34 @@ struct re_filterfile_spec *get_filter(const struct client_state *csp, /********************************************************************* * - * Function : pcrs_filter_response + * Function : pcrs_filter_impl * * Description : Execute all text substitutions from all applying - * +filter actions on the text buffer that's been - * accumulated in csp->iob->buf. + * (based on filter_response_body value) +filter + * or +client_body_filter actions on the given buffer. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : filter_response_body = when TRUE execute +filter + * actions; execute +client_body_filter actions otherwise + * 3 : data = Target data + * 4 : data_len = Target data len * * Returns : a pointer to the (newly allocated) modified buffer. * or NULL if there were no hits or something went wrong * *********************************************************************/ -static char *pcrs_filter_response(struct client_state *csp) +static char *pcrs_filter_impl(const struct client_state *csp, int filter_response_body, + const char *data, size_t *data_len) { int hits = 0; size_t size, prev_size; + const int filters_idx = + filter_response_body ? ACTION_MULTI_FILTER : ACTION_MULTI_CLIENT_BODY_FILTER; + const enum filter_type filter_type = + filter_response_body ? FT_CONTENT_FILTER : FT_CLIENT_BODY_FILTER; - char *old = NULL; + const char *old = NULL; char *new = NULL; pcrs_job *job; @@ -1596,7 +1605,7 @@ static char *pcrs_filter_response(struct client_state *csp) /* * Sanity first */ - if (csp->iob->cur >= csp->iob->eod) + if (*data_len == 0) { return(NULL); } @@ -1608,15 +1617,15 @@ static char *pcrs_filter_response(struct client_state *csp) return(NULL); } - size = (size_t)(csp->iob->eod - csp->iob->cur); - old = csp->iob->cur; + size = *data_len; + old = data; /* - * For all applying +filter actions, look if a filter by that + * For all applying actions, look if a filter by that * name exists and if yes, execute it's pcrs_joblist on the * buffer. */ - for (filtername = csp->action->multi[ACTION_MULTI_FILTER]->first; + for (filtername = csp->action->multi[filters_idx]->first; filtername != NULL; filtername = filtername->next) { int current_hits = 0; /* Number of hits caused by this filter */ @@ -1624,7 +1633,7 @@ static char *pcrs_filter_response(struct client_state *csp) int job_hits = 0; /* How many hits the current job caused */ pcrs_job *joblist; - b = get_filter(csp, filtername->str, FT_CONTENT_FILTER); + b = get_filter(csp, filtername->str, filter_type); if (b == NULL) { continue; @@ -1655,7 +1664,7 @@ static char *pcrs_filter_response(struct client_state *csp) * input for the next one. */ current_hits += job_hits; - if (old != csp->iob->cur) + if (old != data) { freez(old); } @@ -1687,9 +1696,18 @@ static char *pcrs_filter_response(struct client_state *csp) if (b->dynamic) pcrs_free_joblist(joblist); - log_error(LOG_LEVEL_RE_FILTER, - "filtering %s%s (size %lu) with \'%s\' produced %d hits (new size %lu).", - csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size); + if (filter_response_body) + { + log_error(LOG_LEVEL_RE_FILTER, + "filtering %s%s (size %lu) with \'%s\' produced %d hits (new size %lu).", + csp->http->hostport, csp->http->path, prev_size, b->name, current_hits, size); + } + else + { + log_error(LOG_LEVEL_RE_FILTER, + "filtering client %s request body (size %lu) with \'%s\' produced %d hits (new size %lu).", + csp->ip_addr_str, prev_size, b->name, current_hits, size); + } #ifdef FEATURE_EXTENDED_STATISTICS update_filter_statistics(b->name, current_hits); #endif @@ -1698,11 +1716,11 @@ static char *pcrs_filter_response(struct client_state *csp) /* * If there were no hits, destroy our copy and let - * chat() use the original in csp->iob + * chat() use the original content */ if (!hits) { - if (old != csp->iob->cur && old != new) + if (old != data && old != new) { freez(old); } @@ -1710,12 +1728,50 @@ static char *pcrs_filter_response(struct client_state *csp) return(NULL); } - csp->flags |= CSP_FLAG_MODIFIED; - csp->content_length = size; - clear_iob(csp->iob); - + *data_len = size; return(new); +} + +/********************************************************************* + * + * Function : pcrs_filter_response_body + * + * Description : Execute all text substitutions from all applying + * +filter actions on the text buffer that's been + * accumulated in csp->iob->buf. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : a pointer to the (newly allocated) modified buffer. + * or NULL if there were no hits or something went wrong + * + *********************************************************************/ +static char *pcrs_filter_response_body(struct client_state *csp) +{ + size_t size = (size_t)(csp->iob->eod - csp->iob->cur); + + char *new = NULL; + + /* + * Sanity first + */ + if (csp->iob->cur >= csp->iob->eod) + { + return NULL; + } + + new = pcrs_filter_impl(csp, TRUE, csp->iob->cur, &size); + + if (new != NULL) + { + csp->flags |= CSP_FLAG_MODIFIED; + csp->content_length = size; + clear_iob(csp->iob); + } + + return new; } @@ -1946,6 +2002,28 @@ static char *execute_external_filter(const struct client_state *csp, #endif /* def FEATURE_EXTERNAL_FILTERS */ +/********************************************************************* + * + * Function : pcrs_filter_request_body + * + * Description : Execute all text substitutions from all applying + * +client_body_filter actions on the given text buffer. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : data = Target data + * 3 : data_len = Target data len + * + * Returns : a pointer to the (newly allocated) modified buffer. + * or NULL if there were no hits or something went wrong + * + *********************************************************************/ +static char *pcrs_filter_request_body(const struct client_state *csp, const char *data, size_t *data_len) +{ + return pcrs_filter_impl(csp, FALSE, data, data_len); +} + + /********************************************************************* * * Function : gif_deanimate_response @@ -2034,7 +2112,7 @@ static filter_function_ptr get_filter_function(const struct client_state *csp) if ((csp->content_type & CT_TEXT) && (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER]))) { - filter_function = pcrs_filter_response; + filter_function = pcrs_filter_response_body; } else if ((csp->content_type & CT_GIF) && (csp->action->flags & ACTION_DEANIMATE)) @@ -2332,6 +2410,46 @@ char *execute_content_filters(struct client_state *csp) } +/********************************************************************* + * + * Function : execute_client_body_filters + * + * Description : Executes client body filters for the request that is buffered + * in the client_iob. Upon success moves client_iob cur pointer + * to the end of the processed data. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : content_length = content length. Upon successful filtering + * the passed value is updated with the new content length. + * + * Returns : Pointer to the modified buffer, or + * NULL if filtering failed or wasn't necessary. + * + *********************************************************************/ +char *execute_client_body_filters(struct client_state *csp, size_t *content_length) +{ + char *ret; + + assert(client_body_filters_enabled(csp->action)); + + if (content_length == 0) + { + /* + * No content, no filtering necessary. + */ + return NULL; + } + + ret = pcrs_filter_request_body(csp, csp->client_iob->cur, content_length); + if (ret != NULL) + { + csp->client_iob->cur = csp->client_iob->eod; + } + return ret; +} + + /********************************************************************* * * Function : get_url_actions @@ -2755,6 +2873,25 @@ int content_filters_enabled(const struct current_action_spec *action) } +/********************************************************************* + * + * Function : client_body_filters_enabled + * + * Description : Checks whether there are any client body filters + * enabled for the current request. + * + * Parameters : + * 1 : action = Action spec to check. + * + * Returns : TRUE for yes, FALSE otherwise + * + *********************************************************************/ +int client_body_filters_enabled(const struct current_action_spec *action) +{ + return !list_is_empty(action->multi[ACTION_MULTI_CLIENT_BODY_FILTER]); +} + + /********************************************************************* * * Function : filters_available diff --git a/filters.h b/filters.h index 6b603fc1..e16a3ea8 100644 --- a/filters.h +++ b/filters.h @@ -84,6 +84,7 @@ extern const struct forward_spec *forward_url(struct client_state *csp, * Content modification */ extern char *execute_content_filters(struct client_state *csp); +extern char *execute_client_body_filters(struct client_state *csp, size_t *filtered_data_len); extern char *execute_single_pcrs_command(char *subject, const char *pcrs_command, int *hits); extern char *rewrite_url(char *old_url, const char *pcrs_command); @@ -91,6 +92,7 @@ extern pcrs_job *compile_dynamic_pcrs_job_list(const struct client_state *csp, c extern int content_requires_filtering(struct client_state *csp); extern int content_filters_enabled(const struct current_action_spec *action); +extern int client_body_filters_enabled(const struct current_action_spec *action); extern int filters_available(const struct client_state *csp); /* diff --git a/jcc.c b/jcc.c index d8e25d5b..44e75610 100644 --- a/jcc.c +++ b/jcc.c @@ -1989,6 +1989,142 @@ static jb_err parse_client_request(struct client_state *csp) } +/********************************************************************* + * + * Function : read_http_request_body + * + * Description : Reads remaining request body from the client. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : 0 on success, anything else is an error. + * + *********************************************************************/ +static int read_http_request_body(struct client_state *csp) +{ + size_t to_read = csp->expected_client_content_length; + int len; + + assert(to_read != 0); + + /* check if all data has been already read */ + if (to_read <= (csp->client_iob->eod - csp->client_iob->cur)) + { + return 0; + } + + for (to_read -= (size_t)(csp->client_iob->eod - csp->client_iob->cur); + to_read > 0 && data_is_available(csp->cfd, csp->config->socket_timeout); + to_read -= (unsigned)len) + { + char buf[BUFFER_SIZE]; + size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf); + + log_error(LOG_LEVEL_CONNECT, + "Waiting for up to %d bytes of request body from the client.", + max_bytes_to_read); + len = read_socket(csp->cfd, buf, (int)max_bytes_to_read); + if (len <= -1) + { + log_error(LOG_LEVEL_CONNECT, "Failed receiving request body from %s: %E", csp->ip_addr_str); + return 1; + } + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, (char *)buf, len)) + { + return 1; + } + assert(to_read >= len); + } + + if (to_read != 0) + { + log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes", + csp->expected_client_content_length); + return 1; + } + log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read", + csp->expected_client_content_length); + return 0; +} + + +/********************************************************************* + * + * Function : update_client_headers + * + * Description : Updates the HTTP headers from the client request. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : new_content_length = new content length value to set + * + * Returns : 0 on success, anything else is an error. + * + *********************************************************************/ +static int update_client_headers(struct client_state *csp, size_t new_content_length) +{ + static const char content_length[] = "Content-Length:"; + int updated = 0; + struct list_entry *p; + +#ifndef FEATURE_HTTPS_INSPECTION + for (p = csp->headers->first; +#else + for (p = csp->http->client_ssl ? csp->https_headers->first : csp->headers->first; +#endif + !updated && (p != NULL); p = p->next) + { + /* Header crunch()ed in previous run? -> ignore */ + if (p->str == NULL) + { + continue; + } + + /* Does the current parser handle this header? */ + if (0 == strncmpic(p->str, content_length, sizeof(content_length) - 1)) + { + updated = (JB_ERR_OK == header_adjust_content_length((char **)&(p->str), new_content_length)); + if (!updated) + { + return 1; + } + } + } + + return !updated; +} + + +/********************************************************************* + * + * Function : can_filter_request_body + * + * Description : Checks if the current request body can be stored in + * the client_iob without hitting buffer limit. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : TRUE if the current request size do not exceed buffer limit + * FALSE otherwise. + * + *********************************************************************/ +static int can_filter_request_body(const struct client_state *csp) +{ + if (!can_add_to_iob(csp->client_iob, csp->config->buffer_limit, + csp->expected_client_content_length)) + { + log_error(LOG_LEVEL_INFO, + "Not filtering request body from %s: buffer limit %d will be exceeded " + "(content length %d)", csp->ip_addr_str, csp->config->buffer_limit, + csp->expected_client_content_length); + return FALSE; + } + return TRUE; +} + + /********************************************************************* * * Function : send_http_request @@ -2006,6 +2142,32 @@ static int send_http_request(struct client_state *csp) { char *hdr; int write_failure; + const char *to_send; + size_t to_send_len; + int filter_client_body = csp->expected_client_content_length != 0 && + client_body_filters_enabled(csp->action) && can_filter_request_body(csp); + + if (filter_client_body) + { + if (read_http_request_body(csp)) + { + return 1; + } + to_send_len = csp->expected_client_content_length; + to_send = execute_client_body_filters(csp, &to_send_len); + if (to_send == NULL) + { + /* just flush client_iob */ + filter_client_body = FALSE; + } + else if (to_send_len != csp->expected_client_content_length && + update_client_headers(csp, to_send_len)) + { + log_error(LOG_LEVEL_HEADER, "Error updating client headers"); + return 1; + } + csp->expected_client_content_length = 0; + } hdr = list_to_text(csp->headers); if (hdr == NULL) @@ -2026,26 +2188,100 @@ static int send_http_request(struct client_state *csp) { log_error(LOG_LEVEL_CONNECT, "Failed sending request headers to: %s: %E", csp->http->hostport); + return 1; + } + + if (filter_client_body) + { + write_failure = 0 != write_socket(csp->server_connection.sfd, to_send, to_send_len); + freez(to_send); + if (write_failure) + { + log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s: %E", + csp->http->hostport); + return 1; + } } - else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) + + if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) && (flush_iob(csp->server_connection.sfd, csp->client_iob, 0) < 0)) { - write_failure = 1; log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E", csp->http->hostport); + return 1; } + return 0; +} + + +#ifdef FEATURE_HTTPS_INSPECTION +/********************************************************************* + * + * Function : read_https_request_body + * + * Description : Reads remaining request body from the client. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : 0 on success, anything else is an error. + * + *********************************************************************/ +static int read_https_request_body(struct client_state *csp) +{ + size_t to_read = csp->expected_client_content_length; + int len; - return write_failure; + assert(to_read != 0); + /* check if all data has been already read */ + if (to_read <= (csp->client_iob->eod - csp->client_iob->cur)) + { + return 0; + } + + for (to_read -= (size_t)(csp->client_iob->eod - csp->client_iob->cur); + to_read > 0 && (is_ssl_pending(&(csp->ssl_client_attr)) || + data_is_available(csp->cfd, csp->config->socket_timeout)); + to_read -= (unsigned)len) + { + unsigned char buf[BUFFER_SIZE]; + size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf); + + log_error(LOG_LEVEL_CONNECT, + "Waiting for up to %d bytes of request body from the client.", + max_bytes_to_read); + len = ssl_recv_data(&(csp->ssl_client_attr), buf, + (unsigned)max_bytes_to_read); + if (len <= 0) + { + log_error(LOG_LEVEL_CONNECT, "Failed receiving request body from %s", csp->ip_addr_str); + return 1; + } + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, (char *)buf, len)) + { + return 1; + } + assert(to_read >= len); + } + + if (to_read != 0) + { + log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes", to_read); + return 1; + } + + log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read", + csp->expected_client_content_length); + return 0; } -#ifdef FEATURE_HTTPS_INSPECTION /********************************************************************* * * Function : receive_and_send_encrypted_post_data * - * Description : Reads remaining POST data from the client and sends + * Description : Reads remaining request body from the client and sends * it to the server. * * Parameters : @@ -2070,7 +2306,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp) max_bytes_to_read = (int)csp->expected_client_content_length; } log_error(LOG_LEVEL_CONNECT, - "Waiting for up to %d bytes of POST data from the client.", + "Waiting for up to %d bytes of request body from the client.", max_bytes_to_read); len = ssl_recv_data(&(csp->ssl_client_attr), buf, (unsigned)max_bytes_to_read); @@ -2083,7 +2319,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp) /* XXX: Does this actually happen? */ break; } - log_error(LOG_LEVEL_CONNECT, "Forwarding %d bytes of encrypted POST data", + log_error(LOG_LEVEL_CONNECT, "Forwarding %d bytes of encrypted request body", len); len = ssl_send_data(&(csp->ssl_server_attr), buf, (size_t)len); if (len == -1) @@ -2104,7 +2340,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp) } } - log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted POST data"); + log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted request body"); return 0; @@ -2129,6 +2365,32 @@ static int send_https_request(struct client_state *csp) char *hdr; int ret; long flushed = 0; + const char *to_send; + size_t to_send_len; + int filter_client_body = csp->expected_client_content_length != 0 && + client_body_filters_enabled(csp->action) && can_filter_request_body(csp); + + if (filter_client_body) + { + if (read_https_request_body(csp)) + { + return 1; + } + to_send_len = csp->expected_client_content_length; + to_send = execute_client_body_filters(csp, &to_send_len); + if (to_send == NULL) + { + /* just flush client_iob */ + filter_client_body = FALSE; + } + else if (to_send_len != csp->expected_client_content_length && + update_client_headers(csp, to_send_len)) + { + log_error(LOG_LEVEL_HEADER, "Error updating client headers"); + return 1; + } + csp->expected_client_content_length = 0; + } hdr = list_to_text(csp->https_headers); if (hdr == NULL) @@ -2155,6 +2417,18 @@ static int send_https_request(struct client_state *csp) return 1; } + if (filter_client_body) + { + ret = ssl_send_data(&(csp->ssl_server_attr), (const unsigned char *)to_send, to_send_len); + freez(to_send); + if (ret < 0) + { + log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s", + csp->http->hostport); + return 1; + } + } + if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) && ((flushed = ssl_flush_socket(&(csp->ssl_server_attr), csp->client_iob)) < 0)) diff --git a/loaders.c b/loaders.c index 47122954..61d27637 100644 --- a/loaders.c +++ b/loaders.c @@ -1164,6 +1164,10 @@ int load_one_re_filterfile(struct client_state *csp, int fileid) new_filter = FT_EXTERNAL_CONTENT_FILTER; } #endif + else if (strncmp(buf, "CLIENT-BODY-FILTER:", 19) == 0) + { + new_filter = FT_CLIENT_BODY_FILTER; + } /* * If this is the head of a new filter block, make it a @@ -1182,6 +1186,10 @@ int load_one_re_filterfile(struct client_state *csp, int fileid) new_bl->name = chomp(buf + 16); } #endif + else if (new_filter == FT_CLIENT_BODY_FILTER) + { + new_bl->name = chomp(buf + 19); + } else { new_bl->name = chomp(buf + 21); diff --git a/parsers.c b/parsers.c index bef3ca9e..69a8fb4b 100644 --- a/parsers.c +++ b/parsers.c @@ -291,6 +291,27 @@ long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay) } +/********************************************************************* + * + * Function : can_add_to_iob + * + * Description : Checks if the given number of bytes can be added to the given iob + * without exceeding the given buffer limit. + * + * Parameters : + * 1 : iob = Destination buffer. + * 2 : buffer_limit = Limit to which the destination may grow + * 3 : n = number of bytes to be added + * + * Returns : TRUE if the given iob can handle given number of bytes + * FALSE buffer limit will be exceeded + * + *********************************************************************/ +int can_add_to_iob(const struct iob *iob, const size_t buffer_limit, size_t n) +{ + return ((size_t)(iob->eod - iob->buf) + n + 1) > buffer_limit ? FALSE : TRUE; +} + /********************************************************************* * * Function : add_to_iob @@ -308,7 +329,7 @@ long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay) * or buffer limit reached. * *********************************************************************/ -jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n) +jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, const char *src, long n) { size_t used, offset, need; char *p; @@ -2003,10 +2024,7 @@ static jb_err get_content_length(const char *header_value, unsigned long long *l * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : header = On input, pointer to header to modify. - * On output, pointer to the modified header, or NULL - * to remove the header. This function frees the - * original string if necessary. + * 2 : header = pointer to the Content-Length header * * Returns : JB_ERR_OK on success, or * JB_ERR_MEMORY on out-of-memory error. @@ -2617,6 +2635,37 @@ static jb_err server_adjust_content_encoding(struct client_state *csp, char **he #endif /* defined(FEATURE_ZLIB) */ +/********************************************************************* + * + * Function : header_adjust_content_length + * + * Description : Replace given header with new Content-Length header. + * + * Parameters : + * 1 : header = On input, pointer to header to modify. + * On output, pointer to the modified header, or NULL + * to remove the header. This function frees the + * original string if necessary. + * 2 : content_length = content length value to set + * + * Returns : JB_ERR_OK on success, or + * JB_ERR_MEMORY on out-of-memory error. + * + *********************************************************************/ +jb_err header_adjust_content_length(char **header, size_t content_length) +{ + const size_t header_length = 50; + freez(*header); + *header = malloc(header_length); + if (*header == NULL) + { + return JB_ERR_MEMORY; + } + create_content_length_header(content_length, *header, header_length); + return JB_ERR_OK; +} + + /********************************************************************* * * Function : server_adjust_content_length @@ -2640,14 +2689,10 @@ static jb_err server_adjust_content_length(struct client_state *csp, char **head /* Regenerate header if the content was modified. */ if (csp->flags & CSP_FLAG_MODIFIED) { - const size_t header_length = 50; - freez(*header); - *header = malloc(header_length); - if (*header == NULL) + if (JB_ERR_OK != header_adjust_content_length(header, csp->content_length)) { return JB_ERR_MEMORY; } - create_content_length_header(csp->content_length, *header, header_length); log_error(LOG_LEVEL_HEADER, "Adjusted Content-Length to %llu", csp->content_length); } diff --git a/parsers.h b/parsers.h index 9d910226..802133f0 100644 --- a/parsers.h +++ b/parsers.h @@ -50,7 +50,8 @@ #define FILTER_SERVER_HEADERS 1 extern long flush_iob(jb_socket fd, struct iob *iob, unsigned int delay); -extern jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, char *src, long n); +extern int can_add_to_iob(const struct iob *iob, const size_t buffer_limit, size_t n); +extern jb_err add_to_iob(struct iob *iob, const size_t buffer_limit, const char *src, long n); extern void clear_iob(struct iob *iob); extern jb_err decompress_iob(struct client_state *csp); extern char *get_header(struct iob *iob); @@ -59,6 +60,7 @@ extern jb_err sed(struct client_state *csp, int filter_server_headers); #ifdef FEATURE_HTTPS_INSPECTION extern jb_err sed_https(struct client_state *csp); #endif +extern jb_err header_adjust_content_length(char **header, size_t content_length); extern jb_err update_server_headers(struct client_state *csp); extern void get_http_time(int time_offset, char *buf, size_t buffer_size); extern jb_err get_destination_from_headers(const struct list *headers, struct http_request *http); diff --git a/project.h b/project.h index 44b62d19..39fd39ec 100644 --- a/project.h +++ b/project.h @@ -641,8 +641,10 @@ struct iob #define ACTION_MULTI_EXTERNAL_FILTER 6 /** Index into current_action_spec::multi[] for tags to suppress. */ #define ACTION_MULTI_SUPPRESS_TAG 7 +/** Index into current_action_spec::multi[] for client body filters to apply. */ +#define ACTION_MULTI_CLIENT_BODY_FILTER 8 /** Number of multi-string actions. */ -#define ACTION_MULTI_COUNT 8 +#define ACTION_MULTI_COUNT 9 /** @@ -1292,17 +1294,18 @@ enum filter_type FT_SERVER_HEADER_FILTER = 2, FT_CLIENT_HEADER_TAGGER = 3, FT_SERVER_HEADER_TAGGER = 4, + FT_SUPPRESS_TAG = 5, + FT_CLIENT_BODY_FILTER = 6, #ifdef FEATURE_EXTERNAL_FILTERS - FT_EXTERNAL_CONTENT_FILTER = 5, + FT_EXTERNAL_CONTENT_FILTER = 7, #endif - FT_SUPPRESS_TAG = 6, FT_INVALID_FILTER = 42, }; #ifdef FEATURE_EXTERNAL_FILTERS -#define MAX_FILTER_TYPES 7 +#define MAX_FILTER_TYPES 8 #else -#define MAX_FILTER_TYPES 6 +#define MAX_FILTER_TYPES 7 #endif /** diff --git a/templates/edit-actions-for-url b/templates/edit-actions-for-url index 1b9ed4f9..5ecb4089 100644 --- a/templates/edit-actions-for-url +++ b/templates/edit-actions-for-url @@ -354,6 +354,19 @@ function show_limit_connect_opts(tf) id="change_x_forwarded_for_mode_add" @change-x-forwarded-for-param-add@>
+ +   + + + client-body-filter * + Filter the client request body. + You can use the radio buttons on this line to disable + all client-body filters applied by previous rules, and/or + you can enable or disable the filters individually below. + +@client-body-filter-params@