From f105bf576b458a28a3919b0416fb6234e78ee9ef Mon Sep 17 00:00:00 2001 From: Fabian Keil Date: Tue, 9 Oct 2018 14:53:59 +0200 Subject: [PATCH] Add delay-response{} action This is useful to tar pit JavaScript requests that are endlessly retried in case of blocks. Sponsored by: Robert Klemme --- actionlist.h | 2 + configure.in | 1 + jbsockets.c | 54 ++++++++++++++ jbsockets.h | 1 + jcc.c | 126 +++++++++++++++++++++++---------- miscutil.c | 39 ++++++++++ miscutil.h | 2 + parsers.c | 5 +- parsers.h | 2 +- project.h | 6 +- templates/edit-actions-for-url | 22 ++++++ 11 files changed, 219 insertions(+), 41 deletions(-) diff --git a/actionlist.h b/actionlist.h index 4011dd7c..01d4d371 100644 --- a/actionlist.h +++ b/actionlist.h @@ -69,6 +69,8 @@ DEFINE_ACTION_STRING ("crunch-server-header", ACTION_CRUNCH_SERVER_HEA DEFINE_CGI_PARAM_NO_RADIO("crunch-server-header", ACTION_CRUNCH_SERVER_HEADER, ACTION_STRING_SERVER_HEADER, "X-Whatever:") DEFINE_ACTION_STRING ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE) DEFINE_CGI_PARAM_RADIO ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE, "first", 0) +DEFINE_ACTION_STRING ("delay-response", ACTION_DELAY_RESPONSE, ACTION_STRING_DELAY_RESPONSE) +DEFINE_CGI_PARAM_NO_RADIO("delay-response", ACTION_DELAY_RESPONSE, ACTION_STRING_DELAY_RESPONSE, "100") DEFINE_CGI_PARAM_RADIO ("deanimate-gifs", ACTION_DEANIMATE, ACTION_STRING_DEANIMATE, "last", 1) DEFINE_ACTION_BOOL ("downgrade-http-version", ACTION_DOWNGRADE) #ifdef FEATURE_EXTERNAL_FILTERS diff --git a/configure.in b/configure.in index d476c4f9..33241508 100644 --- a/configure.in +++ b/configure.in @@ -781,6 +781,7 @@ AC_CHECK_FUNCS([ \ memchr \ memmove \ memset \ + nanosleep \ poll \ putenv \ random \ diff --git a/jbsockets.c b/jbsockets.c index 1fe56e1e..625d830e 100644 --- a/jbsockets.c +++ b/jbsockets.c @@ -673,6 +673,60 @@ int write_socket(jb_socket fd, const char *buf, size_t len) } +/********************************************************************* + * + * Function : write_socket_delayed + * + * Description : Write the contents of buf (for n bytes) to + * socket fd, optionally delaying the operation. + * + * Parameters : + * 1 : fd = File descriptor (aka. handle) of socket to write to. + * 2 : buf = Pointer to data to be written. + * 3 : len = Length of data to be written to the socket "fd". + * 4 : delay = Delay in milliseconds. + * + * Returns : 0 on success (entire buffer sent). + * nonzero on error. + * + *********************************************************************/ +int write_socket_delayed(jb_socket fd, const char *buf, size_t len, unsigned int delay) +{ + size_t i = 0; + + if (delay == 0) + { + return write_socket(fd, buf, len); + } + + while (i < len) + { + size_t write_length; + enum {MAX_WRITE_LENGTH = 10}; + + if ((i + MAX_WRITE_LENGTH) > len) + { + write_length = len - i; + } + else + { + write_length = MAX_WRITE_LENGTH; + } + + privoxy_millisleep(delay); + + if (write_socket(fd, buf + i, write_length) != 0) + { + return 1; + } + i += write_length; + } + + return 0; + +} + + /********************************************************************* * * Function : read_socket diff --git a/jbsockets.h b/jbsockets.h index b7480d39..2a7756a1 100644 --- a/jbsockets.h +++ b/jbsockets.h @@ -43,6 +43,7 @@ struct client_state; extern jb_socket connect_to(const char *host, int portnum, struct client_state *csp); extern int write_socket(jb_socket fd, const char *buf, size_t n); +extern int write_socket_delayed(jb_socket fd, const char *buf, size_t len, unsigned int delay); extern int read_socket(jb_socket fd, char *buf, int n); extern int data_is_available(jb_socket fd, int seconds_to_wait); extern void close_socket(jb_socket fd); diff --git a/jcc.c b/jcc.c index ba0ecea9..92c94316 100644 --- a/jcc.c +++ b/jcc.c @@ -385,6 +385,42 @@ static void sig_handler(int the_signal) #endif +/********************************************************************* + * + * Function : get_write_delay + * + * Description : Parse the delay-response parameter. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Number of milliseconds to delay writes. + * + *********************************************************************/ +static unsigned int get_write_delay(const struct client_state *csp) +{ + unsigned int delay; + char *endptr; + char *newval; + + if ((csp->action->flags & ACTION_DELAY_RESPONSE) == 0) + { + return 0; + } + newval = csp->action->string[ACTION_STRING_DELAY_RESPONSE]; + + delay = (unsigned)strtol(newval, &endptr, 0); + if (*endptr != '\0') + { + log_error(LOG_LEVEL_FATAL, + "Invalid delay-response{} parameter: '%s'", newval); + } + + return delay; + +} + + /********************************************************************* * * Function : client_protocol_is_unsupported @@ -437,7 +473,8 @@ static int client_protocol_is_unsupported(const struct client_state *csp, char * log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, req); freez(req); - write_socket(csp->cfd, response, strlen(response)); + write_socket_delayed(csp->cfd, response, strlen(response), + get_write_delay(csp)); return TRUE; } @@ -469,8 +506,10 @@ static int client_has_unsupported_expectations(const struct client_state *csp) csp->ip_addr_str); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 417 0", csp->ip_addr_str, csp->http->cmd); - write_socket(csp->cfd, UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE, - strlen(UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE)); + write_socket_delayed(csp->cfd, + UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE, + strlen(UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE), + get_write_delay(csp)); return TRUE; } @@ -520,7 +559,8 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd); - write_socket(csp->cfd, CHEADER, strlen(CHEADER)); + write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER), + get_write_delay(csp)); destroy_list(headers); return JB_ERR_PARSE; @@ -548,7 +588,8 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct csp->ip_addr_str, csp->http->cmd, req); freez(req); - write_socket(csp->cfd, MISSING_DESTINATION_RESPONSE, strlen(MISSING_DESTINATION_RESPONSE)); + write_socket_delayed(csp->cfd, MISSING_DESTINATION_RESPONSE, + strlen(MISSING_DESTINATION_RESPONSE), get_write_delay(csp)); destroy_list(headers); return JB_ERR_PARSE; @@ -793,8 +834,8 @@ static void send_crunch_response(const struct client_state *csp, struct http_res csp->ip_addr_str, http->ocmd, status_code, rsp->content_length); /* Write the answer to the client */ - if (write_socket(csp->cfd, rsp->head, rsp->head_length) - || write_socket(csp->cfd, rsp->body, rsp->content_length)) + if (write_socket_delayed(csp->cfd, rsp->head, rsp->head_length, get_write_delay(csp)) + || write_socket_delayed(csp->cfd, rsp->body, rsp->content_length, get_write_delay(csp))) { /* There is nothing we can do about it. */ log_error(LOG_LEVEL_ERROR, @@ -1295,8 +1336,9 @@ static char *get_request_line(struct client_state *csp) log_error(LOG_LEVEL_CONNECT, "No request line on socket %d received in time. Timeout: %d.", csp->cfd, csp->config->socket_timeout); - write_socket(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE, - strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE)); + write_socket_delayed(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE, + strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE), + get_write_delay(csp)); } else { @@ -1458,8 +1500,8 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp) } if (status != CHUNK_STATUS_BODY_COMPLETE) { - write_socket(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE, - strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE)); + write_socket_delayed(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE, + strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE), get_write_delay(csp)); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Failed reading chunked client body\" 400 0", csp->ip_addr_str); return JB_ERR_PARSE; @@ -1667,7 +1709,8 @@ static jb_err receive_client_request(struct client_state *csp) freez(req); if (JB_ERR_OK != err) { - write_socket(csp->cfd, CHEADER, strlen(CHEADER)); + write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER), + get_write_delay(csp)); /* XXX: Use correct size */ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str); log_error(LOG_LEVEL_ERROR, @@ -1857,7 +1900,7 @@ static jb_err parse_client_request(struct client_state *csp) csp->ip_addr_str); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd); - write_socket(csp->cfd, CHEADER, strlen(CHEADER)); + write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER), get_write_delay(csp)); return JB_ERR_PARSE; } csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE; @@ -1870,7 +1913,8 @@ static jb_err parse_client_request(struct client_state *csp) /* * A header filter broke the request line - bail out. */ - write_socket(csp->cfd, MESSED_UP_REQUEST_RESPONSE, strlen(MESSED_UP_REQUEST_RESPONSE)); + write_socket_delayed(csp->cfd, MESSED_UP_REQUEST_RESPONSE, + strlen(MESSED_UP_REQUEST_RESPONSE), get_write_delay(csp)); /* XXX: Use correct size */ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request generated\" 500 0", csp->ip_addr_str); @@ -1930,7 +1974,7 @@ static int send_http_request(struct client_state *csp) csp->http->hostport); } else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) - && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) + && (flush_socket(csp->server_connection.sfd, csp->client_iob, 0) < 0)) { write_failure = 1; log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E", @@ -1973,6 +2017,7 @@ static void handle_established_connection(struct client_state *csp) struct http_request *http; long len = 0; /* for buffer sizes (and negative error codes) */ int buffer_and_filter_content = 0; + unsigned int write_delay; /* Skeleton for HTTP response, if we should intercept the request */ struct http_response *rsp; @@ -2007,6 +2052,7 @@ static void handle_established_connection(struct client_state *csp) #ifdef FEATURE_CONNECTION_KEEP_ALIVE watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); #endif + write_delay = get_write_delay(csp); for (;;) { @@ -2404,9 +2450,10 @@ static void handle_established_connection(struct client_state *csp) log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header"); } - if (write_socket(csp->cfd, hdr, strlen(hdr)) - || write_socket(csp->cfd, - ((p != NULL) ? p : csp->iob->cur), (size_t)csp->content_length)) + if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay) + || write_socket_delayed(csp->cfd, + ((p != NULL) ? p : csp->iob->cur), + (size_t)csp->content_length, write_delay)) { log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E"); freez(hdr); @@ -2474,9 +2521,11 @@ static void handle_established_connection(struct client_state *csp) } hdrlen = strlen(hdr); - if (write_socket(csp->cfd, hdr, hdrlen) - || ((flushed = flush_socket(csp->cfd, csp->iob)) < 0) - || (write_socket(csp->cfd, csp->receive_buffer, (size_t)len))) + if (write_socket_delayed(csp->cfd, hdr, hdrlen, write_delay) + || ((flushed = flush_socket(csp->cfd, csp->iob, + write_delay) < 0) + || (write_socket_delayed(csp->cfd, csp->receive_buffer, + (size_t)len, write_delay)))) { log_error(LOG_LEVEL_CONNECT, "Flush header and buffers to client failed: %E"); @@ -2498,7 +2547,8 @@ static void handle_established_connection(struct client_state *csp) } else { - if (write_socket(csp->cfd, csp->receive_buffer, (size_t)len)) + if (write_socket_delayed(csp->cfd, csp->receive_buffer, + (size_t)len, write_delay)) { log_error(LOG_LEVEL_ERROR, "write to client failed: %E"); mark_server_socket_tainted(csp); @@ -2538,8 +2588,9 @@ static void handle_established_connection(struct client_state *csp) "Applying the MS IIS5 hack didn't help."); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); - write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE)); + write_socket_delayed(csp->cfd, + INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); mark_server_socket_tainted(csp); return; } @@ -2607,8 +2658,8 @@ static void handle_established_connection(struct client_state *csp) csp->headers->first->str); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); - write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE)); + write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); free_http_request(http); mark_server_socket_tainted(csp); return; @@ -2622,8 +2673,8 @@ static void handle_established_connection(struct client_state *csp) { log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); - write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE)); + write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); free_http_request(http); mark_server_socket_tainted(csp); return; @@ -2679,8 +2730,8 @@ static void handle_established_connection(struct client_state *csp) * may be in the buffer) */ - if (write_socket(csp->cfd, hdr, strlen(hdr)) - || ((len = flush_socket(csp->cfd, csp->iob)) < 0)) + if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay) + || ((len = flush_socket(csp->cfd, csp->iob, write_delay)) < 0)) { log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E"); @@ -2711,8 +2762,8 @@ static void handle_established_connection(struct client_state *csp) "Applying the MS IIS5 hack didn't help."); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); - write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE)); + write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); mark_server_socket_tainted(csp); return; } @@ -2990,7 +3041,8 @@ static void chat(struct client_state *csp) * message to the client, flush the rest, and get out of the way. */ list_remove_all(csp->headers); - if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) + if (write_socket_delayed(csp->cfd, CSUCCEED, + strlen(CSUCCEED), get_write_delay(csp))) { return; } @@ -4395,8 +4447,8 @@ static void listen_loop(void) log_error(LOG_LEVEL_CONNECT, "Rejecting connection from %s. Maximum number of connections reached.", csp->ip_addr_str); - write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE, - strlen(TOO_MANY_CONNECTIONS_RESPONSE)); + write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE, + strlen(TOO_MANY_CONNECTIONS_RESPONSE), get_write_delay(csp)); close_socket(csp->cfd); freez(csp->ip_addr_str); freez(csp->listen_addr_str); @@ -4548,8 +4600,8 @@ static void listen_loop(void) log_error(LOG_LEVEL_ERROR, "Unable to take any additional connections: %E. Active threads: %d", active_threads); - write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE, - strlen(TOO_MANY_CONNECTIONS_RESPONSE)); + write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE, + strlen(TOO_MANY_CONNECTIONS_RESPONSE), get_write_delay(csp)); close_socket(csp->cfd); csp->flags &= ~CSP_FLAG_ACTIVE; } diff --git a/miscutil.c b/miscutil.c index fb8938ba..649eecf5 100644 --- a/miscutil.c +++ b/miscutil.c @@ -815,6 +815,45 @@ size_t privoxy_strlcat(char *destination, const char *source, const size_t size) #endif /* ndef HAVE_STRLCAT */ +/********************************************************************* + * + * Function : privoxy_millisleep + * + * Description : Sleep a number of milliseconds + * + * Parameters : + * 1 : delay: Number of milliseconds to sleep + * + * Returns : -1 on error, 0 otherwise + * + *********************************************************************/ +int privoxy_millisleep(unsigned milliseconds) +{ +#ifdef HAVE_NANOSLEEP + struct timespec rqtp = {0}; + struct timespec rmtp = {0}; + + rqtp.tv_sec = milliseconds / 1000; + rqtp.tv_nsec = (milliseconds % 1000) * 1000 * 1000; + + return nanosleep(&rqtp, &rmtp); +#elif defined (_WIN32) + Sleep(milliseconds); + + return 0; +#elif defined(__OS2__) + DosSleep(milliseconds * 10); + + return 0; +#else +#warning Missing privoxy_milisleep() implementation. delay-response{} will not work. + + return -1; +#endif /* def HAVE_NANOSLEEP */ + +} + + #if !defined(HAVE_TIMEGM) && defined(HAVE_TZSET) && defined(HAVE_PUTENV) /********************************************************************* * diff --git a/miscutil.h b/miscutil.h index e7c150ac..008b7620 100644 --- a/miscutil.h +++ b/miscutil.h @@ -91,6 +91,8 @@ size_t privoxy_strlcat(char *destination, const char *source, size_t size); #define strlcat privoxy_strlcat #endif /* ndef HAVE_STRLCAT */ +extern int privoxy_millisleep(unsigned milliseconds); + #if defined(__cplusplus) } #endif diff --git a/parsers.c b/parsers.c index c1d4d42e..f313f4f8 100644 --- a/parsers.c +++ b/parsers.c @@ -256,6 +256,7 @@ static const add_header_func_ptr add_server_headers[] = { * Parameters : * 1 : fd = file descriptor of the socket to read * 2 : iob = The I/O buffer to flush, usually csp->iob. + * 3 : delay = Number of milliseconds to delay the writes * * Returns : On success, the number of bytes written are returned (zero * indicates nothing was written). On error, -1 is returned, @@ -265,7 +266,7 @@ static const add_header_func_ptr add_server_headers[] = { * file, the results are not portable. * *********************************************************************/ -long flush_socket(jb_socket fd, struct iob *iob) +long flush_socket(jb_socket fd, struct iob *iob, unsigned int delay) { long len = iob->eod - iob->cur; @@ -274,7 +275,7 @@ long flush_socket(jb_socket fd, struct iob *iob) return(0); } - if (write_socket(fd, iob->cur, (size_t)len)) + if (write_socket_delayed(fd, iob->cur, (size_t)len, delay)) { return(-1); } diff --git a/parsers.h b/parsers.h index 00de2860..64bd2cfe 100644 --- a/parsers.h +++ b/parsers.h @@ -49,7 +49,7 @@ #define FILTER_CLIENT_HEADERS 0 #define FILTER_SERVER_HEADERS 1 -extern long flush_socket(jb_socket fd, struct iob *iob); +extern long flush_socket(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 void clear_iob(struct iob *iob); extern jb_err decompress_iob(struct client_state *csp); diff --git a/project.h b/project.h index a283772b..779c5c71 100644 --- a/project.h +++ b/project.h @@ -501,6 +501,8 @@ struct iob #define ACTION_HIDE_ACCEPT_LANGUAGE 0x04000000UL /** Action bitmap: Limit the cookie lifetime */ #define ACTION_LIMIT_COOKIE_LIFETIME 0x08000000UL +/** Action bitmap: Delay writes */ +#define ACTION_DELAY_RESPONSE 0x10000000UL /** Action string index: How to deanimate GIFs */ @@ -541,8 +543,10 @@ struct iob #define ACTION_STRING_CHANGE_X_FORWARDED_FOR 17 /** Action string index: how many minutes cookies should be valid. */ #define ACTION_STRING_LIMIT_COOKIE_LIFETIME 18 +/** Action string index: how many milliseconds writes should be delayed. */ +#define ACTION_STRING_DELAY_RESPONSE 19 /** Number of string actions. */ -#define ACTION_STRING_COUNT 19 +#define ACTION_STRING_COUNT 20 /* To make the ugly hack in sed easier to understand */ diff --git a/templates/edit-actions-for-url b/templates/edit-actions-for-url index 2d84af8f..5a106390 100644 --- a/templates/edit-actions-for-url +++ b/templates/edit-actions-for-url @@ -515,6 +515,28 @@ function show_limit_connect_opts(tf) id="deanimate_last" @deanimate-gifs-param-last@> + + + + + delay-response + Send the response in ca. 10 byte chunks and delay each chunk. + + +   +   +   +   + Number of milliseconds to delay chunks:
+ + + -- 2.39.2