X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=jcc.c;h=703feb94c0c632fedfe6cb69d1791cca7da2f3c4;hp=92c943163fe0783fd6b62b3e245ceb66e7fe4010;hb=ad74921fd9f286069c02130bae18a3f95422b10b;hpb=f105bf576b458a28a3919b0416fb6234e78ee9ef diff --git a/jcc.c b/jcc.c index 92c94316..c824d948 100644 --- a/jcc.c +++ b/jcc.c @@ -5,8 +5,8 @@ * Purpose : Main file. Contains main() method, main loop, and * the main connection-handling function. * - * Copyright : Written by and Copyright (C) 2001-2017 the - * Privoxy team. http://www.privoxy.org/ + * Copyright : Written by and Copyright (C) 2001-2021 the + * Privoxy team. https://www.privoxy.org/ * * Based on the Internet Junkbuster originally written * by and Copyright (C) 1997 Anonymous Coders and @@ -62,10 +62,8 @@ #else /* ifndef _WIN32 */ -# if !defined (__OS2__) # include # include -# endif /* ndef __OS2__ */ # include # include # include @@ -86,14 +84,6 @@ # include /* declarations for threads and stuff. */ # endif -# if defined(__EMX__) || defined(__OS2__) -# include /* OS/2/EMX needs a little help with select */ -# endif -# ifdef __OS2__ -#define INCL_DOS -# include -# endif - #ifdef HAVE_POLL #ifdef __GLIBC__ #include @@ -112,6 +102,9 @@ #include "project.h" #include "list.h" #include "jcc.h" +#ifdef FEATURE_HTTPS_INSPECTION +#include "ssl.h" +#endif #include "filters.h" #include "loaders.h" #include "parsers.h" @@ -140,19 +133,20 @@ int urls_rejected = 0; /* total nr of urls rejected */ int g_terminate = 0; #endif -#if !defined(_WIN32) && !defined(__OS2__) +#if !defined(_WIN32) static void sig_handler(int the_signal); #endif -static int client_protocol_is_unsupported(const struct client_state *csp, char *req); +static int client_protocol_is_unsupported(struct client_state *csp, char *req); static jb_err get_request_destination_elsewhere(struct client_state *csp, struct list *headers); static jb_err get_server_headers(struct client_state *csp); static const char *crunch_reason(const struct http_response *rsp); -static void send_crunch_response(const struct client_state *csp, struct http_response *rsp); +static void send_crunch_response(struct client_state *csp, struct http_response *rsp); static char *get_request_line(struct client_state *csp); static jb_err receive_client_request(struct client_state *csp); static jb_err parse_client_request(struct client_state *csp); static void build_request_line(struct client_state *csp, const struct forward_spec *fwd, char **request_line); static jb_err change_request_destination(struct client_state *csp); +static void handle_established_connection(struct client_state *csp); static void chat(struct client_state *csp); static void serve(struct client_state *csp); #if !defined(_WIN32) || defined(_WIN_CONSOLE) @@ -173,10 +167,6 @@ static int32 server_thread(void *data); #define sleep(N) Sleep(((N) * 1000)) #endif -#ifdef __OS2__ -#define sleep(N) DosSleep(((N) * 100)) -#endif - #ifdef FUZZ int process_fuzzed_input(char *fuzz_input_type, char *fuzz_input_file); void show_fuzz_usage(const char *name); @@ -190,12 +180,21 @@ privoxy_mutex_t log_mutex; privoxy_mutex_t log_init_mutex; privoxy_mutex_t connection_reuse_mutex; +#ifdef FEATURE_HTTPS_INSPECTION +privoxy_mutex_t certificate_mutex; +privoxy_mutex_t ssl_init_mutex; +#endif + #ifdef FEATURE_EXTERNAL_FILTERS privoxy_mutex_t external_filter_mutex; #endif #ifdef FEATURE_CLIENT_TAGS privoxy_mutex_t client_tags_mutex; #endif +#ifdef FEATURE_EXTENDED_STATISTICS +privoxy_mutex_t filter_statistics_mutex; +privoxy_mutex_t block_statistics_mutex; +#endif #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) privoxy_mutex_t resolver_mutex; @@ -334,7 +333,7 @@ static const struct cruncher crunchers_light[] = { * * here? */ -#if !defined(_WIN32) && !defined(__OS2__) +#if !defined(_WIN32) /********************************************************************* * * Function : sig_handler @@ -437,7 +436,7 @@ static unsigned int get_write_delay(const struct client_state *csp) * FALSE if the request doesn't look invalid. * *********************************************************************/ -static int client_protocol_is_unsupported(const struct client_state *csp, char *req) +static int client_protocol_is_unsupported(struct client_state *csp, char *req) { /* * If it's a FTP or gopher request, we don't support it. @@ -473,8 +472,20 @@ 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_delayed(csp->cfd, response, strlen(response), - get_write_delay(csp)); + +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)response, strlen(response), + get_write_delay(csp)); + } + else +#endif + { + write_socket_delayed(csp->cfd, response, strlen(response), + get_write_delay(csp)); + } return TRUE; } @@ -548,8 +559,6 @@ static int client_has_unsupported_expectations(const struct client_state *csp) *********************************************************************/ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct list *headers) { - char *req; - if (!(csp->config->feature_flags & RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS)) { log_error(LOG_LEVEL_ERROR, "%s's request: \'%s\' is invalid." @@ -567,10 +576,8 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct } else if (JB_ERR_OK == get_destination_from_headers(headers, csp->http)) { -#ifndef FEATURE_EXTENDED_HOST_PATTERNS /* Split the domain we just got for pattern matching */ init_domain_components(csp->http); -#endif return JB_ERR_OK; } @@ -578,15 +585,12 @@ static jb_err get_request_destination_elsewhere(struct client_state *csp, struct { /* We can't work without destination. Go spread the news.*/ - req = list_to_text(headers); - chomp(req); /* XXX: Use correct size */ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd); log_error(LOG_LEVEL_ERROR, - "Privoxy was unable to get the destination for %s's request:\n%s\n%s", - csp->ip_addr_str, csp->http->cmd, req); - freez(req); + "Privoxy was unable to get the destination for %s's request: %s", + csp->ip_addr_str, csp->http->cmd); write_socket_delayed(csp->cfd, MISSING_DESTINATION_RESPONSE, strlen(MISSING_DESTINATION_RESPONSE), get_write_delay(csp)); @@ -799,7 +803,7 @@ static void log_applied_actions(const struct current_action_spec *actions) * Returns : Nothing. * *********************************************************************/ -static void send_crunch_response(const struct client_state *csp, struct http_response *rsp) +static void send_crunch_response(struct client_state *csp, struct http_response *rsp) { const struct http_request *http = csp->http; char status_code[4]; @@ -829,18 +833,52 @@ static void send_crunch_response(const struct client_state *csp, struct http_res /* Log that the request was crunched and why. */ log_applied_actions(csp->action); - log_error(LOG_LEVEL_CRUNCH, "%s: %s", crunch_reason(rsp), http->url); - log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" %s %u", - csp->ip_addr_str, http->ocmd, status_code, rsp->content_length); - +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) + { + log_error(LOG_LEVEL_CRUNCH, "%s: https://%s%s", crunch_reason(rsp), + http->hostport, http->path); + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s https://%s%s %s\" %s %lu", + csp->ip_addr_str, http->gpc, http->hostport, http->path, + http->version, status_code, rsp->content_length); + } + else +#endif + { + log_error(LOG_LEVEL_CRUNCH, "%s: %s", crunch_reason(rsp), http->url); + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" %s %lu", + csp->ip_addr_str, http->ocmd, status_code, rsp->content_length); + } /* Write the answer to the client */ - 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))) +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) { - /* There is nothing we can do about it. */ - log_error(LOG_LEVEL_ERROR, - "Couldn't deliver the error message through client socket %d: %E", - csp->cfd); + if ((ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)rsp->head, rsp->head_length, + get_write_delay(csp)) < 0) + || (ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)rsp->body, rsp->content_length, + get_write_delay(csp)) < 0)) + { + /* There is nothing we can do about it. */ + log_error(LOG_LEVEL_CONNECT, "Couldn't deliver the error message " + "for https://%s%s through client socket %d using TLS/SSL", + http->hostport, http->url, csp->cfd); + } + } + else +#endif + { + 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_CONNECT, + "Couldn't deliver the error message for %s through client socket %d: %E", + http->url, csp->cfd); + } } /* Clean up and return */ @@ -930,8 +968,8 @@ static int crunch_response_triggered(struct client_state *csp, const struct crun * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) - * 2 : fwd = The forwarding spec used for the request - * XXX: Should use http->fwd instead. + * 2 : fwd = The forwarding spec used for the request. + * Can be NULL. * 3 : request_line = The old request line which will be replaced. * * Returns : Nothing. Terminates in case of memory problems. @@ -941,17 +979,15 @@ static void build_request_line(struct client_state *csp, const struct forward_sp { struct http_request *http = csp->http; - assert(http->ssl == 0); - /* * Downgrade http version from 1.1 to 1.0 * if +downgrade action applies. */ if ((csp->action->flags & ACTION_DOWNGRADE) - && (!strcmpic(http->ver, "HTTP/1.1"))) + && (!strcmpic(http->version, "HTTP/1.1"))) { - freez(http->ver); - http->ver = strdup_or_die("HTTP/1.0"); + freez(http->version); + http->version = strdup_or_die("HTTP/1.0"); } /* @@ -961,7 +997,7 @@ static void build_request_line(struct client_state *csp, const struct forward_sp *request_line = strdup(http->gpc); string_append(request_line, " "); - if (fwd->forward_host && fwd->type != FORWARD_WEBSERVER) + if (fwd != NULL && fwd->forward_host && fwd->type != FORWARD_WEBSERVER) { string_append(request_line, http->url); } @@ -970,7 +1006,7 @@ static void build_request_line(struct client_state *csp, const struct forward_sp string_append(request_line, http->path); } string_append(request_line, " "); - string_append(request_line, http->ver); + string_append(request_line, http->version); if (*request_line == NULL) { @@ -1008,6 +1044,16 @@ static jb_err change_request_destination(struct client_state *csp) log_error(LOG_LEVEL_ERROR, "Couldn't parse rewritten request: %s.", jb_err_to_string(err)); } + if (http->ssl && strcmpic(csp->http->gpc, "CONNECT")) + { + /* + * A client header filter changed the request URL from + * http:// to https:// which we currently don't support. + */ + log_error(LOG_LEVEL_ERROR, "Changing the request destination from http " + "to https behind the client's back currently isn't supported."); + return JB_ERR_PARSE; + } return err; } @@ -1135,6 +1181,22 @@ void save_connection_destination(jb_socket sfd, server_connection->gateway_host = NULL; } server_connection->gateway_port = fwd->gateway_port; + if (NULL != fwd->auth_username) + { + server_connection->auth_username = strdup_or_die(fwd->auth_username); + } + else + { + server_connection->auth_username = NULL; + } + if (NULL != fwd->auth_password) + { + server_connection->auth_password = strdup_or_die(fwd->auth_password); + } + else + { + server_connection->auth_password = NULL; + } if (NULL != fwd->forward_host) { @@ -1146,6 +1208,7 @@ void save_connection_destination(jb_socket sfd, } server_connection->forward_port = fwd->forward_port; } +#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* @@ -1246,7 +1309,6 @@ static void verify_request_length(struct client_state *csp) log_error(LOG_LEVEL_CONNECT, "Complete client request received."); } } -#endif /* FEATURE_CONNECTION_KEEP_ALIVE */ /********************************************************************* @@ -1507,7 +1569,7 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp) return JB_ERR_PARSE; } log_error(LOG_LEVEL_CONNECT, - "Chunked client body completely read. Length: %d", body_length); + "Chunked client body completely read. Length: %lu", body_length); csp->expected_client_content_length = body_length; return JB_ERR_OK; @@ -1570,7 +1632,7 @@ extern int fuzz_client_request(struct client_state *csp, char *fuzz_input_file) if (strcmp(fuzz_input_file, "-") != 0) { log_error(LOG_LEVEL_FATAL, - "Fuzzed client requests can currenty only be read from stdin (-)."); + "Fuzzed client requests can currently only be read from stdin (-)."); } err = receive_client_request(csp); if (err != JB_ERR_OK) @@ -1861,12 +1923,13 @@ static jb_err parse_client_request(struct client_state *csp) #ifdef FEATURE_CONNECTION_KEEP_ALIVE if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) - && (!strcmpic(csp->http->ver, "HTTP/1.1")) + && (!strcmpic(csp->http->version, "HTTP/1.1")) && (csp->http->ssl == 0)) { /* Assume persistence until further notice */ csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ if (csp->http->ssl == 0) { @@ -1887,11 +1950,12 @@ static jb_err parse_client_request(struct client_state *csp) } verify_request_length(csp); } +#ifndef FEATURE_HTTPS_INSPECTION else { csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +#endif err = sed(csp, FILTER_CLIENT_HEADERS); if (JB_ERR_OK != err) @@ -1935,6 +1999,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 @@ -1945,13 +2145,39 @@ static jb_err parse_client_request(struct client_state *csp) * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * - * Returns : 0 on success, anything else is na error. + * Returns : 0 on success, anything else is an error. * *********************************************************************/ 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) @@ -1972,112 +2198,855 @@ 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) - && (flush_socket(csp->server_connection.sfd, csp->client_iob, 0) < 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 write_failure; - + return 0; } +#ifdef FEATURE_HTTPS_INSPECTION /********************************************************************* * - * Function : handle_established_connection + * Function : read_https_request_body * - * Description : Shuffle data between client and server once the - * connection has been established. + * Description : Reads remaining request body from the client. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * - * Returns : Nothing. + * Returns : 0 on success, anything else is an error. * *********************************************************************/ -static void handle_established_connection(struct client_state *csp) +static int read_https_request_body(struct client_state *csp) { - char *hdr; - char *p; - int n; -#ifdef HAVE_POLL - struct pollfd poll_fds[2]; -#else - fd_set rfds; - jb_socket maxfd; - struct timeval timeout; -#endif - int server_body; - int ms_iis5_hack = 0; - unsigned long long byte_count = 0; - struct http_request *http; - long len = 0; /* for buffer sizes (and negative error codes) */ - int buffer_and_filter_content = 0; - unsigned int write_delay; + size_t to_read = csp->expected_client_content_length; + int len; - /* Skeleton for HTTP response, if we should intercept the request */ - struct http_response *rsp; -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - int watch_client_socket; -#endif + assert(to_read != 0); - csp->receive_buffer_size = csp->config->receive_buffer_size; - csp->receive_buffer = zalloc(csp->receive_buffer_size + 1); - if (csp->receive_buffer == NULL) + /* check if all data has been already read */ + if (to_read <= (csp->client_iob->eod - csp->client_iob->cur)) { - log_error(LOG_LEVEL_ERROR, - "Out of memory. Failed to allocate the receive buffer."); - rsp = cgi_error_memory(); - send_crunch_response(csp, rsp); - return; + return 0; } - http = csp->http; + 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); -#ifndef HAVE_POLL - maxfd = (csp->cfd > csp->server_connection.sfd) ? - csp->cfd : csp->server_connection.sfd; -#endif + 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); + } - /* pass data between the client and server - * until one or the other shuts down the connection. - */ + if (to_read != 0) + { + log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes", to_read); + return 1; + } - server_body = 0; + 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_CONNECTION_KEEP_ALIVE - watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); -#endif - write_delay = get_write_delay(csp); - for (;;) +/********************************************************************* + * + * Function : receive_and_send_encrypted_post_data + * + * Description : Reads remaining request body from the client and sends + * it to the server. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : 0 on success, anything else is an error. + * + *********************************************************************/ +static int receive_and_send_encrypted_post_data(struct client_state *csp) +{ + int content_length_known = csp->expected_client_content_length != 0; + + while (is_ssl_pending(&(csp->ssl_client_attr)) + || (content_length_known && csp->expected_client_content_length != 0)) { -#ifndef HAVE_POLL -#ifdef __OS2__ - /* - * FD_ZERO here seems to point to an errant macro which crashes. - * So do this by hand for now... - */ - memset(&rfds,0x00,sizeof(fd_set)); -#else - FD_ZERO(&rfds); -#endif -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - if (!watch_client_socket) + unsigned char buf[BUFFER_SIZE]; + int len; + int max_bytes_to_read = sizeof(buf); + + if (content_length_known && csp->expected_client_content_length < sizeof(buf)) { - maxfd = csp->server_connection.sfd; + max_bytes_to_read = (int)csp->expected_client_content_length; } - else -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + 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 == -1) { - FD_SET(csp->cfd, &rfds); + return 1; } - - FD_SET(csp->server_connection.sfd, &rfds); + if (len == 0) + { + /* XXX: Does this actually happen? */ + break; + } + 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) + { + return 1; + } + if (csp->expected_client_content_length != 0) + { + if (csp->expected_client_content_length >= len) + { + csp->expected_client_content_length -= (unsigned)len; + } + if (csp->expected_client_content_length == 0) + { + log_error(LOG_LEVEL_CONNECT, "Forwarded the last %d bytes", len); + break; + } + } + } + + log_error(LOG_LEVEL_CONNECT, "Done forwarding encrypted request body"); + + return 0; + +} + + +/********************************************************************* + * + * Function : send_https_request + * + * Description : Sends the HTTP headers from the client request + * and all the body data that has already been received. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : 0 on success, anything else is an error. + * + *********************************************************************/ +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) + { + /* FIXME Should handle error properly */ + log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); + } + list_remove_all(csp->https_headers); + + /* + * Write the client's (modified) header to the server + * (along with anything else that may be in the buffer) + */ + ret = ssl_send_data(&(csp->ssl_server_attr), + (const unsigned char *)hdr, strlen(hdr)); + freez(hdr); + + if (ret < 0) + { + log_error(LOG_LEVEL_CONNECT, + "Failed sending encrypted request headers to: %s: %E", + csp->http->hostport); + mark_server_socket_tainted(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)) + { + log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E", + csp->http->hostport); + return 1; + } + if (flushed != 0 || csp->expected_client_content_length != 0) + { + if (csp->expected_client_content_length != 0) + { + if (csp->expected_client_content_length < flushed) + { + log_error(LOG_LEVEL_ERROR, + "Flushed %ld bytes of request body while only expecting %llu", + flushed, csp->expected_client_content_length); + csp->expected_client_content_length = 0; + } + else + { + log_error(LOG_LEVEL_CONNECT, + "Flushed %ld bytes of request body while expecting %llu", + flushed, csp->expected_client_content_length); + csp->expected_client_content_length -= (unsigned)flushed; + if (receive_and_send_encrypted_post_data(csp)) + { + return 1; + } + } + } + else + { + log_error(LOG_LEVEL_CONNECT, + "Flushed %ld bytes of request body", flushed); + } + } + + log_error(LOG_LEVEL_CONNECT, "Encrypted request sent"); + + return 0; + +} + + +/********************************************************************* + * + * Function : receive_encrypted_request + * + * Description : Receives an encrypted request. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK on success, + * JB_ERR_PARSE or JB_ERR_MEMORY otherwise + * + *********************************************************************/ +static jb_err receive_encrypted_request(struct client_state *csp) +{ + char buf[BUFFER_SIZE]; + int len; + char *p; + + do + { + log_error(LOG_LEVEL_HEADER, "Reading encrypted headers"); + if (!is_ssl_pending(&(csp->ssl_client_attr)) && + !data_is_available(csp->cfd, csp->config->socket_timeout)) + { + log_error(LOG_LEVEL_CONNECT, + "Socket %d timed out while waiting for client headers", csp->cfd); + return JB_ERR_PARSE; + } + len = ssl_recv_data(&(csp->ssl_client_attr), + (unsigned char *)buf, sizeof(buf)); + if (len == 0) + { + log_error(LOG_LEVEL_CONNECT, + "Socket %d closed while waiting for client headers", csp->cfd); + return JB_ERR_PARSE; + } + if (len == -1) + { + return JB_ERR_PARSE; + } + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len)) + { + return JB_ERR_MEMORY; + } + p = strstr(csp->client_iob->cur, "\r\n\r\n"); + } while (p == NULL); + + log_error(LOG_LEVEL_HEADER, "Encrypted headers received completely"); + + return JB_ERR_OK; +} + + +/********************************************************************* + * + * Function : change_encrypted_request_destination + * + * Description : Parse a (rewritten) request line from an encrypted + * request and regenerate the http request data. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Forwards the parse_http_request() return code. + * Terminates in case of memory problems. + * + *********************************************************************/ +static jb_err change_encrypted_request_destination(struct client_state *csp) +{ + jb_err err; + char *original_host = csp->http->host; + + log_error(LOG_LEVEL_REDIRECTS, "Rewrite detected: %s", + csp->https_headers->first->str); + csp->http->host = NULL; + free_http_request(csp->http); + err = parse_http_request(csp->https_headers->first->str, csp->http); + if (JB_ERR_OK != err) + { + log_error(LOG_LEVEL_ERROR, "Couldn't parse rewritten request: %s.", + jb_err_to_string(err)); + return err; + } + + if (csp->http->host == NULL) + { + /* + * The rewritten request line did not specify a host + * which means we can use the original host specified + * by the client. + */ + csp->http->host = original_host; + log_error(LOG_LEVEL_REDIRECTS, "Keeping the original host: %s", + csp->http->host); + /* + * If the rewritten request line didn't contain a host + * it also didn't contain a port so we can reuse the host + * and set the port to 443. + */ + freez(csp->http->hostport); + csp->http->hostport = strdup_or_die(csp->http->host); + csp->http->port = 443; + /* + * While the request line didn't mention it, + * we're https-inspecting and want to speak TLS + * with the server. + */ + csp->http->server_ssl = 1; + csp->http->ssl = 1; + } + else + { + /* The rewrite filter added a host so we can ditch the original */ + freez(original_host); + csp->http->server_ssl = csp->http->ssl; + } + + csp->http->client_ssl = 1; + + freez(csp->https_headers->first->str); + build_request_line(csp, NULL, &csp->https_headers->first->str); + + if (!server_use_ssl(csp)) + { + log_error(LOG_LEVEL_REDIRECTS, + "Rewritten request line results in downgrade to http"); + /* + * Replace the unencryptd headers received with the + * CONNECT request with the ones we received securely. + */ + destroy_list(csp->headers); + csp->headers->first = csp->https_headers->first; + csp->headers->last = csp->https_headers->last; + csp->https_headers->first = NULL; + csp->https_headers->last = NULL; + } + + return JB_ERR_OK; + +} + + +/********************************************************************* + * + * Function : process_encrypted_request + * + * Description : Receives and parses an encrypted request. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK on success, + * JB_ERR_PARSE or JB_ERR_MEMORY otherwise + * + *********************************************************************/ +static jb_err process_encrypted_request(struct client_state *csp) +{ + char *p; + char *request_line; + jb_err err; + /* Temporary copy of the client's headers before they get enlisted in csp->https_headers */ + struct list header_list; + struct list *headers = &header_list; + + assert(csp->ssl_with_client_is_opened); + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) + { + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; + } +#endif + err = receive_encrypted_request(csp); + if (err != JB_ERR_OK) + { + if (csp->client_iob->cur == NULL || + csp->client_iob->cur == csp->client_iob->eod) + { + /* + * We did not receive any data, most likely because the + * client is done. Don't log this as a parse failure. + */ + return JB_ERR_PARSE; + } + /* XXX: Also used for JB_ERR_MEMORY */ + log_error(LOG_LEVEL_ERROR, "Failed to receive encrypted request: %s", + jb_err_to_string(err)); + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp)); + return err; + } + + /* We don't need get_request_line() because the whole HTTP head is buffered. */ + request_line = get_header(csp->client_iob); + if (request_line == NULL) + { + log_error(LOG_LEVEL_ERROR, "Failed to get the encrypted request line"); + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp)); + return JB_ERR_PARSE; + } + assert(*request_line != '\0'); + + if (client_protocol_is_unsupported(csp, request_line)) + { + /* + * If the protocol is unsupported we're done here. + * client_protocol_is_unsupported() took care of sending + * the error response and logging the error message. + */ + return JB_ERR_PARSE; + } + +#ifdef FEATURE_FORCE_LOAD + if (force_required(csp, request_line)) + { + csp->flags |= CSP_FLAG_FORCED; + } +#endif /* def FEATURE_FORCE_LOAD */ + + free_http_request(csp->http); + + err = parse_http_request(request_line, csp->http); + /* XXX: Restore ssl setting. This is ugly */ + csp->http->client_ssl = 1; + csp->http->server_ssl = 1; + + freez(request_line); + if (JB_ERR_OK != err) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)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, + "Couldn't parse request line received from %s: %s", + csp->ip_addr_str, jb_err_to_string(err)); + + free_http_request(csp->http); + return JB_ERR_PARSE; + } + + /* Parse the rest of the client's headers. */ + init_list(headers); + for (;;) + { + p = get_header(csp->client_iob); + + if (p == NULL) + { + /* There are no additional headers to read. */ + break; + } + enlist(headers, p); + freez(p); + } + + if (JB_ERR_OK != get_destination_from_https_headers(headers, csp->http)) + { + /* + * Our attempts to get the request destination + * elsewhere failed. + */ + log_error(LOG_LEVEL_ERROR, + "Failed to get the encrypted request destination"); + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp)); + return JB_ERR_PARSE; + } + + /* Split the domain we just got for pattern matching */ + init_domain_components(csp->http); + +#ifdef FEATURE_CLIENT_TAGS + /* XXX: If the headers were enlisted sooner, passing csp would do. */ + if (csp->client_address == NULL) + { + set_client_address(csp, headers); + get_tag_list_for_client(csp->client_tags, csp->client_address); + } +#endif + +#ifdef FEATURE_TOGGLE + if ((csp->flags & CSP_FLAG_TOGGLED_ON) != 0) +#endif + { + /* + * Determine the actions for this request after + * clearing the ones from the previous one. + */ + free_current_action(csp->action); + get_url_actions(csp, csp->http); + } + + enlist(csp->https_headers, csp->http->cmd); + + /* Append the previously read headers */ + err = list_append_list_unique(csp->https_headers, headers); + destroy_list(headers); + if (JB_ERR_OK != err) + { + /* XXX: Send error message */ + return err; + } + + /* XXX: Work around crash */ + csp->error_message = NULL; + + /* XXX: Why do this here? */ + csp->http->ssl = 1; + + err = sed_https(csp); + if (JB_ERR_OK != err) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp)); + log_error(LOG_LEVEL_ERROR, "Failed to parse client request from %s.", + csp->ip_addr_str); + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", + csp->ip_addr_str, csp->http->cmd); + return JB_ERR_PARSE; + } + + if ((NULL == csp->https_headers->first->str) + || (strcmp(csp->http->cmd, csp->https_headers->first->str) && + (JB_ERR_OK != change_encrypted_request_destination(csp)))) + { + log_error(LOG_LEVEL_ERROR, + "Failed to get the request destination in the rewritten headers"); + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)MESSED_UP_REQUEST_RESPONSE, + strlen(MESSED_UP_REQUEST_RESPONSE), get_write_delay(csp)); + return JB_ERR_PARSE; + } + + log_error(LOG_LEVEL_HEADER, "Encrypted request processed"); + log_applied_actions(csp->action); + log_error(LOG_LEVEL_REQUEST, "https://%s%s", csp->http->hostport, + csp->http->path); + + return err; + +} + +/********************************************************************* + * + * Function : cgi_page_requested + * + * Description : Checks if a request is for an internal CGI page. + * + * Parameters : + * 1 : host = The host requested by the client. + * + * Returns : 1 if a CGI page has been requested, 0 otherwise + * + *********************************************************************/ +static int cgi_page_requested(const char *host) +{ + if ((0 == strcmpic(host, CGI_SITE_1_HOST)) + || (0 == strcmpic(host, CGI_SITE_1_HOST ".")) + || (0 == strcmpic(host, CGI_SITE_2_HOST)) + || (0 == strcmpic(host, CGI_SITE_2_HOST "."))) + { + return 1; + } + + return 0; + +} + + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE +/********************************************************************* + * + * Function : continue_https_chat + * + * Description : Behaves similar to chat() but only deals with + * https-inspected requests that arrive on an already + * established connection. The first request is always + * served by chat() which is a lot more complex as it + * has to deal with forwarding settings and connection + * failures etc. + * + * If a connection to the server has already been + * opened it is reused unless the request is blocked + * or the forwarder changed. + * + * If a connection to the server has not yet been + * opened (because the previous request was crunched), + * or the forwarder changed, the connection is dropped + * so that the client retries on a fresh one. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Nothing. + * + *********************************************************************/ +static void continue_https_chat(struct client_state *csp) +{ + const struct forward_spec *fwd; + + if (JB_ERR_OK != process_encrypted_request(csp)) + { + return; + } + + csp->requests_received_total++; + + /* + * We have an encrypted request. Check if one of the crunchers wants it. + */ + if (crunch_response_triggered(csp, crunchers_all)) + { + /* + * Yes. The client got the crunch response and we're done here. + */ + return; + } + if (csp->ssl_with_server_is_opened == 0) + { + log_error(LOG_LEVEL_CONNECT, + "Dropping the client connection on socket %d. " + "The server connection has not been established yet.", + csp->cfd); + csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; + return; + } + assert(csp->server_connection.sfd != JB_INVALID_SOCKET); + + fwd = forward_url(csp, csp->http); + if (!connection_destination_matches(&csp->server_connection, csp->http, fwd)) + { + log_error(LOG_LEVEL_CONNECT, + "Dropping the client connection on socket %d with " + "server socket %d connected to %s. The forwarder has changed.", + csp->cfd, csp->server_connection.sfd, csp->server_connection.host); + csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; + return; + } + + log_error(LOG_LEVEL_CONNECT, + "Reusing server socket %d connected to %s. Requests already sent: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + + if (send_https_request(csp)) + { + /* + * Most likely the server connection timed out. We can't easily + * create a new one so simply drop the client connection without a + * error response to let the client retry. + */ + log_error(LOG_LEVEL_CONNECT, + "Dropping client connection on socket %d. " + "Forwarding the encrypted client request failed.", + csp->cfd); + return; + } + csp->server_connection.requests_sent_total++; + handle_established_connection(csp); + freez(csp->receive_buffer); +} +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +#endif + + +/********************************************************************* + * + * Function : handle_established_connection + * + * Description : Shuffle data between client and server once the + * connection has been established. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Nothing. + * + *********************************************************************/ +static void handle_established_connection(struct client_state *csp) +{ + char *hdr; + char *p; + int n; +#ifdef HAVE_POLL + struct pollfd poll_fds[2]; +#else + fd_set rfds; + jb_socket maxfd; + struct timeval timeout; +#endif + int server_body; + int ms_iis5_hack = 0; + unsigned long long byte_count = 0; + struct http_request *http; + long len = 0; /* for buffer sizes (and negative error codes) */ + int buffer_and_filter_content = 0; + unsigned int write_delay; +#ifdef FEATURE_HTTPS_INSPECTION + int ret = 0; + int use_ssl_tunnel = 0; + csp->dont_verify_certificate = 0; + + if (csp->http->ssl && !(csp->action->flags & ACTION_HTTPS_INSPECTION)) + { + /* Pass encrypted content without filtering. */ + use_ssl_tunnel = 1; + } +#endif + + /* Skeleton for HTTP response, if we should intercept the request */ + struct http_response *rsp; +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + int watch_client_socket; +#endif + + csp->receive_buffer_size = csp->config->receive_buffer_size; + csp->receive_buffer = zalloc(csp->receive_buffer_size + 1); + if (csp->receive_buffer == NULL) + { + log_error(LOG_LEVEL_ERROR, + "Out of memory. Failed to allocate the receive buffer."); + rsp = cgi_error_memory(); + send_crunch_response(csp, rsp); + return; + } + + http = csp->http; + +#ifndef HAVE_POLL + maxfd = (csp->cfd > csp->server_connection.sfd) ? + csp->cfd : csp->server_connection.sfd; +#endif + + /* pass data between the client and server + * until one or the other shuts down the connection. + */ + + server_body = 0; + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); +#endif + write_delay = get_write_delay(csp); + + for (;;) + { +#ifndef HAVE_POLL + FD_ZERO(&rfds); +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if (!watch_client_socket) + { + maxfd = csp->server_connection.sfd; + } + else +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + { + FD_SET(csp->cfd, &rfds); + } + + FD_SET(csp->server_connection.sfd, &rfds); #endif /* ndef HAVE_POLL */ #ifdef FEATURE_CONNECTION_KEEP_ALIVE @@ -2104,20 +3073,20 @@ static void handle_established_connection(struct client_state *csp) { log_error(LOG_LEVEL_CONNECT, "Done reading from server. Content length: %llu as expected. " - "Bytes most recently read: %d.", + "Bytes most recently read: %ld.", byte_count, len); } else { log_error(LOG_LEVEL_CONNECT, "Done reading from server. Expected content length: %llu. " - "Actual content length: %llu. Bytes most recently read: %d.", + "Actual content length: %llu. Bytes most recently read: %ld.", csp->expected_content_length, byte_count, len); } len = 0; /* - * XXX: should not jump around, - * chat() is complicated enough already. + * XXX: Should not jump around, handle_established_connection() + * is complicated enough already. */ goto reading_done; } @@ -2147,9 +3116,10 @@ static void handle_established_connection(struct client_state *csp) #else timeout.tv_sec = csp->config->socket_timeout; timeout.tv_usec = 0; - n = select((int)maxfd+1, &rfds, NULL, NULL, &timeout); + n = select((int)maxfd + 1, &rfds, NULL, NULL, &timeout); #endif /* def HAVE_POLL */ + /*server or client not responding in timeout */ if (n == 0) { log_error(LOG_LEVEL_CONNECT, "Socket timeout %d reached: %s", @@ -2159,6 +3129,9 @@ static void handle_established_connection(struct client_state *csp) send_crunch_response(csp, error_response(csp, "connection-timeout")); } mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } else if (n < 0) @@ -2169,6 +3142,9 @@ static void handle_established_connection(struct client_state *csp) log_error(LOG_LEVEL_ERROR, "select() failed!: %E"); #endif mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } @@ -2176,6 +3152,8 @@ static void handle_established_connection(struct client_state *csp) * This is the body of the browser's request, * just read and write it. * + * Receives data from browser and sends it to server + * * XXX: Make sure the client doesn't use pipelining * behind Privoxy's back. */ @@ -2240,38 +3218,79 @@ static void handle_established_connection(struct client_state *csp) assert(max_bytes_to_read <= csp->receive_buffer_size); #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - len = read_socket(csp->cfd, csp->receive_buffer, max_bytes_to_read); - - if (len <= 0) +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) { - /* XXX: not sure if this is necessary. */ - mark_server_socket_tainted(csp); - break; /* "game over, man" */ + if (csp->http->status == 101) + { + len = ssl_recv_data(&(csp->ssl_client_attr), + (unsigned char *)csp->receive_buffer, + (size_t)max_bytes_to_read); + if (len == -1) + { + log_error(LOG_LEVEL_ERROR, "Failed to receive data " + "on client socket %d for an upgraded connection", + csp->cfd); + break; + } + if (len == 0) + { + log_error(LOG_LEVEL_CONNECT, "Done receiving data " + "on client socket %d for an upgraded connection", + csp->cfd); + break; + } + byte_count += (unsigned long long)len; + len = ssl_send_data(&(csp->ssl_server_attr), + (unsigned char *)csp->receive_buffer, (size_t)len); + if (len == -1) + { + log_error(LOG_LEVEL_ERROR, "Failed to send data " + "on server socket %d for an upgraded connection", + csp->server_connection.sfd); + break; + } + continue; + } + log_error(LOG_LEVEL_CONNECT, "Breaking with TLS/SSL."); + break; } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + len = read_socket(csp->cfd, csp->receive_buffer, max_bytes_to_read); + + if (len <= 0) + { + /* XXX: not sure if this is necessary. */ + mark_server_socket_tainted(csp); + break; /* "game over, man" */ + } #ifdef FEATURE_CONNECTION_KEEP_ALIVE - if (csp->expected_client_content_length != 0) - { - assert(len <= max_bytes_to_read); - csp->expected_client_content_length -= (unsigned)len; - log_error(LOG_LEVEL_CONNECT, - "Expected client content length set to %llu " - "after reading %d bytes.", - csp->expected_client_content_length, len); - if (csp->expected_client_content_length == 0) + if (csp->expected_client_content_length != 0) { + assert(len <= max_bytes_to_read); + csp->expected_client_content_length -= (unsigned)len; log_error(LOG_LEVEL_CONNECT, - "Done reading from the client."); - csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; + "Expected client content length set to %llu " + "after reading %ld bytes.", + csp->expected_client_content_length, len); + if (csp->expected_client_content_length == 0) + { + log_error(LOG_LEVEL_CONNECT, + "Done reading from the client."); + csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; + } } - } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - if (write_socket(csp->server_connection.sfd, csp->receive_buffer, (size_t)len)) - { - log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host); - mark_server_socket_tainted(csp); - return; + if (write_socket(csp->server_connection.sfd, csp->receive_buffer, (size_t)len)) + { + log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host); + mark_server_socket_tainted(csp); + return; + } } continue; } @@ -2304,18 +3323,39 @@ static void handle_established_connection(struct client_state *csp) log_error(LOG_LEVEL_CONNECT, "The server still wants to talk, but the client hung up on us."); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; #endif /* def _WIN32 */ } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - len = read_socket(csp->server_connection.sfd, csp->receive_buffer, (int)csp->receive_buffer_size); +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Reading data from standard or secured connection (HTTP/HTTPS) + */ + if (server_use_ssl(csp)) + { + len = ssl_recv_data(&(csp->ssl_server_attr), + (unsigned char *)csp->receive_buffer, csp->receive_buffer_size); + } + else +#endif + { + len = read_socket(csp->server_connection.sfd, csp->receive_buffer, + (int)csp->receive_buffer_size); + } if (len < 0) { log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host); - if (http->ssl && (csp->fwd == NULL)) + if ((http->ssl && (csp->fwd == NULL)) +#ifdef FEATURE_HTTPS_INSPECTION + && use_ssl_tunnel +#endif + ) { /* * Just hang up. We already confirmed the client's CONNECT @@ -2338,6 +3378,9 @@ static void handle_established_connection(struct client_state *csp) log_error(LOG_LEVEL_ERROR, "Already forwarded the original headers. " "Unable to tell the client about the problem."); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } /* @@ -2403,7 +3446,11 @@ static void handle_established_connection(struct client_state *csp) if (len == 0) { - if (server_body || http->ssl) + if (server_body || (http->ssl +#ifdef FEATURE_HTTPS_INSPECTION + && use_ssl_tunnel +#endif + )) { /* * If we have been buffering up the document, @@ -2450,16 +3497,41 @@ static void handle_established_connection(struct client_state *csp) log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header"); } - 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)) +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) { - log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E"); - freez(hdr); - freez(p); - mark_server_socket_tainted(csp); - return; + if ((ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)hdr, strlen(hdr), + get_write_delay(csp)) < 0) + || (ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *) ((p != NULL) ? p : csp->iob->cur), + csp->content_length, get_write_delay(csp)) < 0)) + { + log_error(LOG_LEVEL_ERROR, "write modified content to " + "client over TLS/SSL failed"); + freez(hdr); + freez(p); + mark_server_socket_tainted(csp); + close_client_and_server_ssl_connections(csp); + return; + } + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + 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); + freez(p); + mark_server_socket_tainted(csp); + return; + } } freez(hdr); @@ -2485,11 +3557,15 @@ static void handle_established_connection(struct client_state *csp) } /* - * If this is an SSL connection or we're in the body - * of the server document, just write it to the client, - * unless we need to buffer the body for later content-filtering + * If we're in the body of the server document, just write it to + * the client, unless we need to buffer the body for later + * content-filtering. */ - if (server_body || http->ssl) + if (server_body || (http->ssl +#ifdef FEATURE_HTTPS_INSPECTION + && use_ssl_tunnel +#endif + )) { if (buffer_and_filter_content) { @@ -2517,21 +3593,49 @@ static void handle_established_connection(struct client_state *csp) rsp = cgi_error_memory(); send_crunch_response(csp, rsp); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } hdrlen = strlen(hdr); - 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)))) +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) { - log_error(LOG_LEVEL_CONNECT, - "Flush header and buffers to client failed: %E"); - freez(hdr); - mark_server_socket_tainted(csp); - return; + if ((ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)hdr, hdrlen, get_write_delay(csp)) < 0) + || ((flushed = ssl_flush_socket(&(csp->ssl_client_attr), + csp->iob)) < 0) + || (ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)csp->receive_buffer, (size_t)len, + get_write_delay(csp)) < 0)) + { + log_error(LOG_LEVEL_CONNECT, + "Flush header and buffers to client failed"); + freez(hdr); + mark_server_socket_tainted(csp); + close_client_and_server_ssl_connections(csp); + return; + } + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + if (write_socket_delayed(csp->cfd, hdr, hdrlen, write_delay) + || ((flushed = flush_iob(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"); + freez(hdr); + mark_server_socket_tainted(csp); + return; + } } /* @@ -2547,12 +3651,34 @@ static void handle_established_connection(struct client_state *csp) } else { - if (write_socket_delayed(csp->cfd, csp->receive_buffer, - (size_t)len, write_delay)) +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) { - log_error(LOG_LEVEL_ERROR, "write to client failed: %E"); - mark_server_socket_tainted(csp); - return; + ret = ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)csp->receive_buffer, (size_t)len, + get_write_delay(csp)); + if (ret < 0) + { + log_error(LOG_LEVEL_ERROR, + "Sending data to client failed"); + mark_server_socket_tainted(csp); + close_client_and_server_ssl_connections(csp); + return; + } + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + 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); + return; + } } } byte_count += (unsigned long long)len; @@ -2571,6 +3697,9 @@ static void handle_established_connection(struct client_state *csp) rsp = cgi_error_memory(); send_crunch_response(csp, rsp); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } @@ -2588,10 +3717,27 @@ 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_delayed(csp->cfd, - INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), get_write_delay(csp)); + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + write_socket_delayed(csp->cfd, + INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); + } mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } else @@ -2602,7 +3748,7 @@ static void handle_established_connection(struct client_state *csp) */ log_error(LOG_LEVEL_CONNECT, "Continuing buffering server headers from socket %d. " - "Bytes most recently read: %d.", csp->cfd, len); + "Bytes most recently read: %ld.", csp->cfd, len); continue; } } @@ -2637,11 +3783,18 @@ static void handle_established_connection(struct client_state *csp) } free_http_request(http); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } + if (!csp->headers->first->str) + { + log_error(LOG_LEVEL_ERROR, "header search: csp->headers->first->str == NULL, assert will be called"); + } assert(csp->headers->first->str); - assert(!http->ssl); + if (strncmpic(csp->headers->first->str, "HTTP", 4) && strncmpic(csp->headers->first->str, "ICY", 3)) { @@ -2658,10 +3811,28 @@ 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_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), + get_write_delay(csp)); + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + 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); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } @@ -2673,10 +3844,28 @@ 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_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), + get_write_delay(csp)); + } + else +#endif + { + 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); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } hdr = list_to_text(csp->headers); @@ -2711,39 +3900,64 @@ static void handle_established_connection(struct client_state *csp) */ freez(hdr); mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } + /* Buffer and pcrs filter this if appropriate. */ + buffer_and_filter_content = content_requires_filtering(csp); - if (!http->ssl) /* We talk plaintext */ - { - buffer_and_filter_content = content_requires_filtering(csp); - } - /* - * Only write if we're not buffering for content modification - */ if (!buffer_and_filter_content) { /* * Write the server's (modified) header to * the client (along with anything else that - * may be in the buffer) + * may be in the buffer). Use standard or secured + * connection. */ - - if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay) - || ((len = flush_socket(csp->cfd, csp->iob, write_delay)) < 0)) +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) { - log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E"); + if ((ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)hdr, strlen(hdr), + get_write_delay(csp)) < 0) + || (len = ssl_flush_socket(&(csp->ssl_client_attr), + csp->iob) < 0)) + { + log_error(LOG_LEVEL_CONNECT, "Write header to client failed"); - /* - * The write failed, so don't bother mentioning it - * to the client... it probably can't hear us anyway. - */ - freez(hdr); - mark_server_socket_tainted(csp); - return; + /* + * The write failed, so don't bother mentioning it + * to the client... it probably can't hear us anyway. + */ + freez(hdr); + mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif + return; + } } - } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + if (write_socket_delayed(csp->cfd, hdr, strlen(hdr), write_delay) + || ((len = flush_iob(csp->cfd, csp->iob, write_delay)) < 0)) + { + log_error(LOG_LEVEL_ERROR, + "write header to client failed"); + /* + * The write failed, so don't bother mentioning it + * to the client... it probably can't hear us anyway. + */ + freez(hdr); + mark_server_socket_tainted(csp); + return; + } + } + } /* we're finished with the server's header */ @@ -2762,15 +3976,36 @@ 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_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, - strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Sending data with standard or secured connection (HTTP/HTTPS) + */ + if (client_use_ssl(csp)) + { + ssl_send_data_delayed(&(csp->ssl_client_attr), + (const unsigned char *)INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), + get_write_delay(csp)); + } + else +#endif /* def FEATURE_HTTPS_INSPECTION */ + { + write_socket_delayed(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE, + strlen(INVALID_SERVER_HEADERS_RESPONSE), write_delay); + } mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; } } continue; } mark_server_socket_tainted(csp); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_and_server_ssl_connections(csp); +#endif return; /* huh? we should never get here */ } @@ -2794,9 +4029,19 @@ static void handle_established_connection(struct client_state *csp) } #endif - log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %llu", - csp->ip_addr_str, http->ocmd, csp->content_length); - +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp)) + { + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s https://%s%s %s\" 200 %llu", + csp->ip_addr_str, http->gpc, http->hostport, http->path, + http->version, csp->content_length); + } + else +#endif + { + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %llu", + csp->ip_addr_str, http->ocmd, csp->content_length); + } csp->server_connection.timestamp = time(NULL); } @@ -2831,6 +4076,9 @@ static void chat(struct client_state *csp) struct http_request *http; /* Skeleton for HTTP response, if we should intercept the request */ struct http_response *rsp; +#ifdef FEATURE_HTTPS_INSPECTION + int use_ssl_tunnel = 0; +#endif http = csp->http; @@ -2845,50 +4093,93 @@ static void chat(struct client_state *csp) /* decide how to route the HTTP request */ fwd = forward_url(csp, http); - if (NULL == fwd) + +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Setting flags to use old solution with SSL tunnel and to disable + * certificate verification. + */ + if (csp->http->ssl && !(csp->action->flags & ACTION_HTTPS_INSPECTION) + && !cgi_page_requested(csp->http->host)) { - log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); - /* Never get here - LOG_LEVEL_FATAL causes program exit */ - return; + use_ssl_tunnel = 1; } + if (http->ssl && (csp->action->flags & ACTION_IGNORE_CERTIFICATE_ERRORS)) + { + csp->dont_verify_certificate = 1; + } +#endif + /* * build the http request to send to the server * we have to do one of the following: * - * create = use the original HTTP request to create a new - * HTTP request that has either the path component - * without the http://domainspec (w/path) or the - * full orininal URL (w/url) - * Note that the path and/or the HTTP version may - * have been altered by now. + * create = use the original HTTP request to create a new + * HTTP request that has either the path component + * without the http://domainspec (w/path) or the + * full orininal URL (w/url) + * Note that the path and/or the HTTP version may + * have been altered by now. * - * connect = Open a socket to the host:port of the server - * and short-circuit server and client socket. + * SSL proxy = Open a socket to the host:port of the server + * and create TLS/SSL connection with server and + * with client. Then behave like mediator between + * client and server over TLS/SSL. * - * pass = Pass the request unchanged if forwarding a CONNECT - * request to a parent proxy. Note that we'll be sending - * the CFAIL message ourselves if connecting to the parent - * fails, but we won't send a CSUCCEED message if it works, - * since that would result in a double message (ours and the - * parent's). After sending the request to the parent, we simply - * tunnel. + * SSL proxy = Pass the request unchanged if forwarding a CONNECT + * with request to a parent proxy. Note that we'll be sending + * forwarding the CFAIL message ourselves if connecting to the parent + * fails, but we won't send a CSUCCEED message if it works, + * since that would result in a double message (ours and the + * parent's). After sending the request to the parent, we + * must parse answer and send it to client. If connection + * with server is established, we do TLS/SSL proxy. Otherwise + * we send parent response to client and close connections. * * here's the matrix: * SSL * 0 1 * +--------+--------+ * | | | - * 0 | create | connect| - * | w/path | | + * 0 | create | SSL | + * | w/path | proxy | * Forwarding +--------+--------+ - * | | | - * 1 | create | pass | - * | w/url | | + * | | SSL | + * 1 | create | proxy | + * | w/url |+forward| * +--------+--------+ * */ +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Presetting SSL client and server flags + */ + if (http->ssl && !use_ssl_tunnel) + { + http->client_ssl = 1; + http->server_ssl = 1; + } + else + { + http->client_ssl = 0; + http->server_ssl = 0; + } +#endif + +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Log the request unless we're https inspecting + * in which case we don't have the path yet and + * will log the request later. + */ + if (!client_use_ssl(csp)) +#endif + { + log_error(LOG_LEVEL_REQUEST, "%s%s", http->hostport, http->path); + } + if (http->ssl && connect_port_is_forbidden(csp)) { const char *acceptable_connect_ports = @@ -2899,18 +4190,27 @@ static void chat(struct client_state *csp) csp->ip_addr_str, acceptable_connect_ports, csp->http->hostport); csp->action->flags |= ACTION_BLOCK; http->ssl = 0; +#ifdef FEATURE_HTTPS_INSPECTION + http->client_ssl = 0; + http->server_ssl = 0; +#endif } - if (http->ssl == 0) - { - freez(csp->headers->first->str); - build_request_line(csp, fwd, &csp->headers->first->str); - } + + freez(csp->headers->first->str); + build_request_line(csp, fwd, &csp->headers->first->str); /* - * We have a request. Check if one of the crunchers wants it. + * We have a request. Check if one of the crunchers wants it + * unless the client wants to use TLS/SSL in which case we + * haven't setup the TLS context yet and will send the crunch + * response later. */ - if (crunch_response_triggered(csp, crunchers_all)) + if ( +#ifdef FEATURE_HTTPS_INSPECTION + !client_use_ssl(csp) && +#endif + crunch_response_triggered(csp, crunchers_all)) { /* * Yes. The client got the crunch response and we're done here. @@ -2919,8 +4219,6 @@ static void chat(struct client_state *csp) } log_applied_actions(csp->action); - log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path); - if (fwd->forward_host) { log_error(LOG_LEVEL_CONNECT, "via [%s]:%d to: %s", @@ -2948,7 +4246,11 @@ static void chat(struct client_state *csp) if (csp->server_connection.sfd != JB_INVALID_SOCKET) { #ifdef FEATURE_CONNECTION_SHARING - if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING +#ifdef FEATURE_HTTPS_INSPECTION + && !server_use_ssl(csp) +#endif + ) { remember_connection(&csp->server_connection); } @@ -2964,7 +4266,54 @@ static void chat(struct client_state *csp) mark_connection_closed(&csp->server_connection); } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ +#ifdef FEATURE_HTTPS_INSPECTION + if (client_use_ssl(csp) && !use_ssl_tunnel) + { + int ret; + /* + * Creating a SSL proxy. + * + * By sending the CSUCCEED message we're lying to the client as + * the connection hasn't actually been established yet. We don't + * establish the connection until we have seen and parsed the + * encrypted client headers. + */ + if (write_socket_delayed(csp->cfd, CSUCCEED, + strlen(CSUCCEED), get_write_delay(csp)) != 0) + { + log_error(LOG_LEVEL_ERROR, "Sending SUCCEED to client failed"); + return; + } + ret = create_client_ssl_connection(csp); + if (ret != 0) + { + log_error(LOG_LEVEL_ERROR, + "Failed to open a secure connection with the client"); + return; + } + if (JB_ERR_OK != process_encrypted_request(csp)) + { + close_client_ssl_connection(csp); + return; + } + /* + * We have an encrypted request. Check if one of the crunchers now + * wants it (for example because the previously invisible path was + * required to match). + */ + if (crunch_response_triggered(csp, crunchers_all)) + { + /* + * Yes. The client got the crunch response and we're done here. + */ + return; + } + } +#endif + /* + * Connecting to destination server + */ csp->server_connection.sfd = forwarded_connect(fwd, http, csp); if (csp->server_connection.sfd == JB_INVALID_SOCKET) @@ -3001,11 +4350,130 @@ static void chat(struct client_state *csp) * client body in the buffer (if there is one) and to * continue parsing the bytes that follow. */ +#ifdef FEATURE_HTTPS_INSPECTION + close_client_ssl_connection(csp); +#endif drain_and_close_socket(csp->cfd); csp->cfd = JB_INVALID_SOCKET; return; } + +#ifdef FEATURE_HTTPS_INSPECTION + /* + * Creating TLS/SSL connections with destination server or parent + * proxy. If forwarding is enabled, we must send client request to + * parent proxy and receive, parse and resend parent proxy answer. + */ + if (http->ssl && !use_ssl_tunnel) + { + if (fwd->forward_host != NULL) + { + char server_response[BUFFER_SIZE]; + int ret = 0; + int len = 0; + char *hdr = list_to_text(csp->headers); + memset(server_response, 0, sizeof(server_response)); + + if (hdr == NULL) + { + log_error(LOG_LEVEL_FATAL, + "Out of memory parsing client header"); + } + list_remove_all(csp->headers); + + /* + * Sending client's CONNECT request to the parent proxy + */ + ret = write_socket(csp->server_connection.sfd, hdr, strlen(hdr)); + + freez(hdr); + + if (ret != 0) + { + log_error(LOG_LEVEL_CONNECT, + "Sending request headers to: %s failed", http->hostport); + mark_server_socket_tainted(csp); + close_client_ssl_connection(csp); + return; + } + + /* Waiting for parent proxy server response */ + len = read_socket(csp->server_connection.sfd, server_response, + sizeof(server_response)-1); + + if (len <= 0) + { + log_error(LOG_LEVEL_ERROR, "No response from parent proxy " + "server on socket %d.", csp->server_connection.sfd); + + rsp = error_response(csp, "no-server-data"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + mark_server_socket_tainted(csp); + close_client_ssl_connection(csp); + return; + } + + /* + * Test if the connection to the destination server was + * established successfully by the parent proxy. + */ + if (!tunnel_established_successfully(server_response, (unsigned int)len)) + { + log_error(LOG_LEVEL_ERROR, + "The forwarder %s failed to establish a connection with %s", + fwd->forward_host, http->host); + rsp = error_response(csp, "connect-failed"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + mark_server_socket_tainted(csp); + close_client_ssl_connection(csp); + return; + } + } /* -END- if (fwd->forward_host != NULL) */ + + /* + * We can now create the TLS/SSL connection with the destination server. + */ + int ret = create_server_ssl_connection(csp); + if (ret != 0) + { + if (csp->server_cert_verification_result != SSL_CERT_VALID && + csp->server_cert_verification_result != SSL_CERT_NOT_VERIFIED) + { + /* + * If the server certificate is invalid, we must inform + * the client and then close connection to the client. + */ + ssl_send_certificate_error(csp); + close_client_and_server_ssl_connections(csp); + return; + } + if (csp->server_cert_verification_result == SSL_CERT_NOT_VERIFIED + || csp->server_cert_verification_result == SSL_CERT_VALID) + { + /* + * The TLS/SSL connection wasn't created but an invalid + * certificate wasn't detected. Report it as connection + * failure. + */ + rsp = error_response(csp, "connect-failed"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + close_client_and_server_ssl_connections(csp); + return; + } + } + }/* -END- if (http->ssl) */ +#endif /* def FEATURE_HTTPS_INSPECTION */ + #ifdef FEATURE_CONNECTION_KEEP_ALIVE save_connection_destination(csp->server_connection.sfd, http, fwd, &csp->server_connection); @@ -3021,7 +4489,11 @@ static void chat(struct client_state *csp) /* Client headers have been sent optimistically */ assert(csp->headers->last == NULL); } - else if (fwd->forward_host || (http->ssl == 0)) + else if (http->ssl == 0 || (fwd->forward_host +#ifdef FEATURE_HTTPS_INSPECTION + && use_ssl_tunnel +#endif + )) { if (send_http_request(csp)) { @@ -3036,18 +4508,53 @@ static void chat(struct client_state *csp) else { /* - * We're running an SSL tunnel and we're not forwarding, - * so just ditch the client headers, send the "connect succeeded" - * message to the client, flush the rest, and get out of the way. + * Using old solution with SSL tunnel or new solution with SSL proxy */ list_remove_all(csp->headers); - if (write_socket_delayed(csp->cfd, CSUCCEED, - strlen(CSUCCEED), get_write_delay(csp))) +#ifdef FEATURE_HTTPS_INSPECTION + if (use_ssl_tunnel) +#endif { - return; + /* + * We're running an SSL tunnel and we're not forwarding, + * so just ditch the client headers, send the "connect succeeded" + * message to the client, flush the rest, and get out of the way. + */ + if (write_socket_delayed(csp->cfd, CSUCCEED, + strlen(CSUCCEED), get_write_delay(csp))) + { + return; + } + } +#ifdef FEATURE_HTTPS_INSPECTION + else + { + /* + * If server certificate has been verified and is invalid, + * we must inform the client and then close the connection + * with client and server. + */ + if (csp->server_cert_verification_result != SSL_CERT_VALID && + csp->server_cert_verification_result != SSL_CERT_NOT_VERIFIED) + { + ssl_send_certificate_error(csp); + close_client_and_server_ssl_connections(csp); + return; + } + if (send_https_request(csp)) + { + rsp = error_response(csp, "connect-failed"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + close_client_and_server_ssl_connections(csp); + return; + } } +#endif /* def FEATURE_HTTPS_INSPECTION */ clear_iob(csp->client_iob); - } + }/* -END- else ... if (http->ssl == 1) */ log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); @@ -3080,7 +4587,7 @@ extern int fuzz_server_response(struct client_state *csp, char *fuzz_input_file) if (strcmp(fuzz_input_file, "-") == 0) { - /* XXX: Doesn'T work yet. */ + /* XXX: Doesn't work yet. */ csp->server_connection.sfd = 0; } else @@ -3145,6 +4652,9 @@ static void prepare_csp_for_next_request(struct client_state *csp) freez(csp->error_message); free_http_request(csp->http); destroy_list(csp->headers); +#ifdef FEATURE_HTTPS_INSPECTION + destroy_list(csp->https_headers); +#endif destroy_list(csp->tags); #ifdef FEATURE_CLIENT_TAGS destroy_list(csp->client_tags); @@ -3173,7 +4683,7 @@ static void prepare_csp_for_next_request(struct client_state *csp) assert(bytes_to_shift > 0); assert(data_length > 0); - log_error(LOG_LEVEL_CONNECT, "Shifting %d pipelined bytes by %d bytes", + log_error(LOG_LEVEL_CONNECT, "Shifting %lu pipelined bytes by %ld bytes", data_length, bytes_to_shift); memmove(csp->client_iob->buf, csp->client_iob->cur, data_length); csp->client_iob->cur = csp->client_iob->buf; @@ -3229,7 +4739,16 @@ static void serve(struct client_state *csp) { unsigned int latency; - chat(csp); +#ifdef FEATURE_HTTPS_INSPECTION + if (continue_chatting && client_use_ssl(csp)) + { + continue_https_chat(csp); + } + else +#endif + { + chat(csp); + } /* * If the request has been crunched, @@ -3269,7 +4788,7 @@ static void serve(struct client_state *csp) { log_error(LOG_LEVEL_CONNECT, "Closing server socket %d connected to %s. " - "Keep-alive %u. Tainted: %u. Socket alive %u. Timeout: %u.", + "Keep-alive: %u. Tainted: %u. Socket alive: %u. Timeout: %u.", csp->server_connection.sfd, csp->server_connection.host, 0 != (csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE), 0 != (csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED), @@ -3281,6 +4800,9 @@ static void serve(struct client_state *csp) forget_connection(csp->server_connection.sfd); } #endif /* def FEATURE_CONNECTION_SHARING */ +#ifdef FEATURE_HTTPS_INSPECTION + close_server_ssl_connection(csp); +#endif close_socket(csp->server_connection.sfd); mark_connection_closed(&csp->server_connection); } @@ -3291,6 +4813,15 @@ static void serve(struct client_state *csp) continue_chatting = 0; config_file_change_detected = 1; } +#ifdef FEATURE_HTTPS_INSPECTION + if (continue_chatting && client_use_ssl(csp) && + csp->ssl_with_client_is_opened == 0) + { + continue_chatting = 0; + log_error(LOG_LEVEL_CONNECT, "Client socket %d is no longer usable. " + "The TLS session has been terminated.", csp->cfd); + } +#endif if (continue_chatting) { @@ -3326,8 +4857,8 @@ static void serve(struct client_state *csp) && socket_is_still_alive(csp->cfd)) { log_error(LOG_LEVEL_CONNECT, - "Client request %u arrived in time on socket %d.", - csp->requests_received_total+1, csp->cfd); + "Data arrived in time on client socket %d. Requests so far: %u", + csp->cfd, csp->requests_received_total); prepare_csp_for_next_request(csp); } else @@ -3335,7 +4866,11 @@ static void serve(struct client_state *csp) #ifdef FEATURE_CONNECTION_SHARING if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) && (csp->server_connection.sfd != JB_INVALID_SOCKET) - && (socket_is_still_alive(csp->server_connection.sfd))) + && (socket_is_still_alive(csp->server_connection.sfd)) +#ifdef FEATURE_HTTPS_INSPECTION + && !server_use_ssl(csp) +#endif + ) { time_t time_open = time(NULL) - csp->server_connection.timestamp; @@ -3382,6 +4917,20 @@ static void serve(struct client_state *csp) chat(csp); #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + if (csp->cfd != JB_INVALID_SOCKET) + { + log_error(LOG_LEVEL_CONNECT, "Closing client socket %d. " + "Keep-alive: %u. Socket alive: %u. Data available: %u. " + "Configuration file change detected: %u. Requests received: %u.", + csp->cfd, 0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE), + socket_is_still_alive(csp->cfd), data_is_available(csp->cfd, 0), + config_file_change_detected, csp->requests_received_total); +#ifdef FEATURE_HTTPS_INSPECTION + close_client_ssl_connection(csp); +#endif + drain_and_close_socket(csp->cfd); + } + if (csp->server_connection.sfd != JB_INVALID_SOCKET) { #ifdef FEATURE_CONNECTION_SHARING @@ -3390,6 +4939,11 @@ static void serve(struct client_state *csp) forget_connection(csp->server_connection.sfd); } #endif /* def FEATURE_CONNECTION_SHARING */ + +#ifdef FEATURE_HTTPS_INSPECTION + close_server_ssl_connection(csp); +#endif /* def FEATURE_HTTPS_INSPECTION */ + close_socket(csp->server_connection.sfd); } @@ -3397,17 +4951,6 @@ static void serve(struct client_state *csp) mark_connection_closed(&csp->server_connection); #endif - if (csp->cfd != JB_INVALID_SOCKET) - { - log_error(LOG_LEVEL_CONNECT, "Closing client socket %d. " - "Keep-alive: %u. Socket alive: %u. Data available: %u. " - "Configuration file change detected: %u. Requests received: %u.", - csp->cfd, 0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE), - socket_is_still_alive(csp->cfd), data_is_available(csp->cfd, 0), - config_file_change_detected, csp->requests_received_total); - drain_and_close_socket(csp->cfd); - } - free_csp_resources(csp); csp->flags &= ~CSP_FLAG_ACTIVE; @@ -3583,6 +5126,12 @@ static void initialize_mutexes(void) /* * Prepare global mutex semaphores */ + +#ifdef FEATURE_HTTPS_INSPECTION + privoxy_mutex_init(&certificate_mutex); + privoxy_mutex_init(&ssl_init_mutex); +#endif + privoxy_mutex_init(&log_mutex); privoxy_mutex_init(&log_init_mutex); privoxy_mutex_init(&connection_reuse_mutex); @@ -3592,6 +5141,10 @@ static void initialize_mutexes(void) #ifdef FEATURE_CLIENT_TAGS privoxy_mutex_init(&client_tags_mutex); #endif +#ifdef FEATURE_EXTENDED_STATISTICS + privoxy_mutex_init(&filter_statistics_mutex); + privoxy_mutex_init(&block_statistics_mutex); +#endif /* * XXX: The assumptions below are a bit naive @@ -3881,7 +5434,7 @@ int main(int argc, char **argv) * are handled when and where they occur without relying * on a signal. */ -#if !defined(_WIN32) && !defined(__OS2__) +#if !defined(_WIN32) { int idx; const int catched_signals[] = { SIGTERM, SIGINT, SIGHUP }; @@ -4014,7 +5567,10 @@ int main(int argc, char **argv) } #endif - chdir("/"); + if (chdir("/") != 0) + { + log_error(LOG_LEVEL_FATAL, "Failed to cd into '/': %E"); + } } /* -END- if (daemon_mode) */ @@ -4343,7 +5899,7 @@ static void listen_loop(void) for (;;) #endif { -#if !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(__OS2__) +#if !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) while (waitpid(-1, NULL, WNOHANG) > 0) { /* zombie children */ @@ -4373,7 +5929,7 @@ static void listen_loop(void) csp = &csp_list->csp; log_error(LOG_LEVEL_CONNECT, - "Waiting for the next client connection. Currently active threads: %d", + "Waiting for the next client connection. Currently active threads: %u", active_threads); /* @@ -4472,10 +6028,11 @@ static void listen_loop(void) #define SELECTED_ONE_OPTION { pthread_t the_thread; + int ret; - errno = pthread_create(&the_thread, &attrs, + ret = pthread_create(&the_thread, &attrs, (void * (*)(void *))serve, csp); - child_id = errno ? -1 : 0; + child_id = ret ? -1 : 0; } #endif @@ -4487,15 +6044,6 @@ static void listen_loop(void) csp); #endif -#if defined(__OS2__) && !defined(SELECTED_ONE_OPTION) -#define SELECTED_ONE_OPTION - child_id = _beginthread( - (void(* _Optlink)(void*))serve, - NULL, - 64 * 1024, - csp); -#endif - #if defined(__BEOS__) && !defined(SELECTED_ONE_OPTION) #define SELECTED_ONE_OPTION { @@ -4598,7 +6146,7 @@ static void listen_loop(void) * XXX: If you assume ... */ log_error(LOG_LEVEL_ERROR, - "Unable to take any additional connections: %E. Active threads: %d", + "Unable to take any additional connections: %E. Active threads: %u", active_threads); write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE, strlen(TOO_MANY_CONNECTIONS_RESPONSE), get_write_delay(csp)); @@ -4618,10 +6166,14 @@ static void listen_loop(void) /* NOTREACHED unless FEATURE_GRACEFUL_TERMINATION is defined */ +#ifdef FEATURE_HTTPS_INSPECTION /* Clean up. Aim: free all memory (no leaks) */ + ssl_release(); +#endif + #ifdef FEATURE_GRACEFUL_TERMINATION - log_error(LOG_LEVEL_ERROR, "Graceful termination requested"); + log_error(LOG_LEVEL_INFO, "Graceful termination requested"); unload_current_config_file(); unload_current_actions_file();