X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=jcc.c;h=b335ca962a29c3774f124dfefafcbd044ef17640;hp=b199c47acf9f5ed2d8ca356f106e984c4d40d403;hb=1f28c399b73ef84fff9903a48bf7d14153be224f;hpb=7eee8d2d278242e5581b704a7a69371c3e9a91e9 diff --git a/jcc.c b/jcc.c index b199c47a..b335ca96 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.445 2016/05/25 10:51:10 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.456 2017/05/25 11:16:56 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -6,7 +6,7 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.445 2016/05/25 10:51:10 fabiankeil Exp $" * Purpose : Main file. Contains main() method, main loop, and * the main connection-handling function. * - * Copyright : Written by and Copyright (C) 2001-2016 the + * Copyright : Written by and Copyright (C) 2001-2017 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -50,6 +50,7 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.445 2016/05/25 10:51:10 fabiankeil Exp $" # ifndef STRICT # define STRICT # endif +# include # include # include # endif /* ndef FEATURE_PTHREAD */ @@ -92,12 +93,19 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.445 2016/05/25 10:51:10 fabiankeil Exp $" # ifdef __OS2__ #define INCL_DOS # include -#define bzero(B,N) memset(B,0x00,n) # endif +#ifdef HAVE_POLL +#ifdef __GLIBC__ +#include +#else +#include +#endif /* def __GLIBC__ */ +#else # ifndef FD_ZERO # include # endif +#endif /* HAVE_POLL */ #endif @@ -177,6 +185,11 @@ static int32 server_thread(void *data); #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); +#endif + #ifdef MUTEX_LOCKS_AVAILABLE /* * XXX: Does the locking stuff really belong in this file? @@ -204,9 +217,9 @@ privoxy_mutex_t gmtime_mutex; privoxy_mutex_t localtime_mutex; #endif /* ndef HAVE_GMTIME_R */ -#ifndef HAVE_RANDOM +#if !defined(HAVE_ARC4RANDOM) && !defined(HAVE_RANDOM) privoxy_mutex_t rand_mutex; -#endif /* ndef HAVE_RANDOM */ +#endif /* !defined(HAVE_ARC4RANDOM) && !defined(HAVE_RANDOM) */ #endif /* def MUTEX_LOCKS_AVAILABLE */ @@ -1278,7 +1291,12 @@ static char *get_request_line(struct client_state *csp) do { - if (!data_is_available(csp->cfd, csp->config->socket_timeout)) + if ( +#ifdef FUZZ + 0 == (csp->flags & CSP_FLAG_FUZZED_INPUT) && +#endif + !data_is_available(csp->cfd, csp->config->socket_timeout) + ) { if (socket_is_still_alive(csp->cfd)) { @@ -1424,7 +1442,7 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp) enum chunk_status status; while (CHUNK_STATUS_MISSING_DATA == - (status = chunked_body_is_complete(csp->client_iob,&body_length))) + (status = chunked_body_is_complete(csp->client_iob, &body_length))) { char buf[BUFFER_SIZE]; int len; @@ -1463,6 +1481,80 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp) } +#ifdef FUZZ +/********************************************************************* + * + * Function : fuzz_chunked_transfer_encoding + * + * Description : Treat the fuzzed input as chunked transfer encoding + * to check and dechunk. + * + * Parameters : + * 1 : csp = Used to store the data. + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of dechunking + * + *********************************************************************/ +extern int fuzz_chunked_transfer_encoding(struct client_state *csp, char *fuzz_input_file) +{ + size_t length; + size_t size = (size_t)(csp->iob->eod - csp->iob->cur); + enum chunk_status status; + + status = chunked_body_is_complete(csp->iob, &length); + if (CHUNK_STATUS_BODY_COMPLETE != status) + { + log_error(LOG_LEVEL_INFO, "Chunked body is incomplete or invalid"); + } + + return (JB_ERR_OK == remove_chunked_transfer_coding(csp->iob->cur, &size)); + +} + + +/********************************************************************* + * + * Function : fuzz_client_request + * + * Description : Try to get a client request from the fuzzed input. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : Result of fuzzing. + * + *********************************************************************/ +extern int fuzz_client_request(struct client_state *csp, char *fuzz_input_file) +{ + jb_err err; + + csp->cfd = 0; + csp->ip_addr_str = "fuzzer"; + + if (strcmp(fuzz_input_file, "-") != 0) + { + log_error(LOG_LEVEL_FATAL, + "Fuzzed client requests can currenty only be read from stdin (-)."); + } + err = receive_client_request(csp); + if (err != JB_ERR_OK) + { + return 1; + } + err = parse_client_request(csp); + if (err != JB_ERR_OK) + { + return 1; + } + + return 0; + +} +#endif /* def FUZZ */ + + #ifdef FEATURE_FORCE_LOAD /********************************************************************* * @@ -1805,21 +1897,61 @@ static jb_err parse_client_request(struct client_state *csp) /********************************************************************* * - * Function : chat + * Function : send_http_request * - * Description : Once a connection from the client has been accepted, - * this function is called (via serve()) to handle the - * main business of the communication. This function - * returns after dealing with a single request. It can - * be called multiple times with the same client socket - * if the client is keeping the connection alive. + * Description : Sends the HTTP headers from the client request + * and all the body data that has already been received. * - * The decision whether or not a client connection will - * be kept alive is up to the caller which also must - * close the client socket when done. + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) * - * FIXME: chat is nearly thousand lines long. - * Ridiculous. + * Returns : 0 on success, anything else is na error. + * + *********************************************************************/ +static int send_http_request(struct client_state *csp) +{ + char *hdr; + int write_failure; + + hdr = list_to_text(csp->headers); + if (hdr == NULL) + { + /* FIXME Should handle error properly */ + log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); + } + list_remove_all(csp->headers); + + /* + * Write the client's (modified) header to the server + * (along with anything else that may be in the buffer) + */ + write_failure = 0 != write_socket(csp->server_connection.sfd, hdr, strlen(hdr)); + freez(hdr); + + if (write_failure) + { + log_error(LOG_LEVEL_CONNECT, "Failed sending request headers to: %s: %E", + csp->http->hostport); + } + else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) + && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) + { + write_failure = 1; + log_error(LOG_LEVEL_CONNECT, "Failed sending request body to: %s: %E", + csp->http->hostport); + } + + return write_failure; + +} + + +/********************************************************************* + * + * 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...) @@ -1827,25 +1959,29 @@ static jb_err parse_client_request(struct client_state *csp) * Returns : Nothing. * *********************************************************************/ -static void chat(struct client_state *csp) +static void handle_established_connection(struct client_state *csp, + const struct forward_spec *fwd) { char buf[BUFFER_SIZE]; char *hdr; char *p; - fd_set rfds; 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; - const struct forward_spec *fwd; struct http_request *http; long len = 0; /* for buffer sizes (and negative error codes) */ int buffer_and_filter_content = 0; /* Skeleton for HTTP response, if we should intercept the request */ struct http_response *rsp; - struct timeval timeout; #ifdef FEATURE_CONNECTION_KEEP_ALIVE int watch_client_socket; #endif @@ -1854,317 +1990,73 @@ static void chat(struct client_state *csp) http = csp->http; - if (receive_client_request(csp) != JB_ERR_OK) - { - return; - } - if (parse_client_request(csp) != JB_ERR_OK) - { - return; - } - - /* decide how to route the HTTP request */ - fwd = forward_url(csp, http); - if (NULL == fwd) - { - log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); - /* Never get here - LOG_LEVEL_FATAL causes program exit */ - return; - } +#ifndef HAVE_POLL + maxfd = (csp->cfd > csp->server_connection.sfd) ? + csp->cfd : csp->server_connection.sfd; +#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. - * - * connect = Open a socket to the host:port of the server - * and short-circuit server and client socket. - * - * 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. - * - * here's the matrix: - * SSL - * 0 1 - * +--------+--------+ - * | | | - * 0 | create | connect| - * | w/path | | - * Forwarding +--------+--------+ - * | | | - * 1 | create | pass | - * | w/url | | - * +--------+--------+ - * + /* pass data between the client and server + * until one or the other shuts down the connection. */ - if (http->ssl && connect_port_is_forbidden(csp)) - { - const char *acceptable_connect_ports = - csp->action->string[ACTION_STRING_LIMIT_CONNECT]; - assert(NULL != acceptable_connect_ports); - log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. " - "limit-connect{%s} doesn't allow CONNECT requests to %s", - csp->ip_addr_str, acceptable_connect_ports, csp->http->hostport); - csp->action->flags |= ACTION_BLOCK; - http->ssl = 0; - } + server_body = 0; - if (http->ssl == 0) - { - freez(csp->headers->first->str); - build_request_line(csp, fwd, &csp->headers->first->str); - } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); +#endif - /* - * We have a request. Check if one of the crunchers wants it. - */ - if (crunch_response_triggered(csp, crunchers_all)) + for (;;) { +#ifndef HAVE_POLL +#ifdef __OS2__ /* - * Yes. The client got the crunch response and we're done here. + * FD_ZERO here seems to point to an errant macro which crashes. + * So do this by hand for now... */ - return; - } - - 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", - fwd->forward_host, fwd->forward_port, http->hostport); - } - else - { - log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport); - } + memset(&rfds,0x00,sizeof(fd_set)); +#else + FD_ZERO(&rfds); +#endif +#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); + } - /* here we connect to the server, gateway, or the forwarder */ + FD_SET(csp->server_connection.sfd, &rfds); +#endif /* ndef HAVE_POLL */ #ifdef FEATURE_CONNECTION_KEEP_ALIVE - if ((csp->server_connection.sfd != JB_INVALID_SOCKET) - && socket_is_still_alive(csp->server_connection.sfd) - && connection_destination_matches(&csp->server_connection, http, fwd)) - { - log_error(LOG_LEVEL_CONNECT, - "Reusing server socket %d connected to %s. Total requests: %u.", - csp->server_connection.sfd, csp->server_connection.host, - csp->server_connection.requests_sent_total); - } - else - { - if (csp->server_connection.sfd != JB_INVALID_SOCKET) + if ((csp->flags & CSP_FLAG_CHUNKED) + && !(csp->flags & CSP_FLAG_CONTENT_LENGTH_SET) + && ((csp->iob->eod - csp->iob->cur) >= 5) + && !memcmp(csp->iob->eod-5, "0\r\n\r\n", 5)) { -#ifdef FEATURE_CONNECTION_SHARING - if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + /* + * XXX: This check should be obsolete now, + * but let's wait a while to be sure. + */ + log_error(LOG_LEVEL_CONNECT, + "Looks like we got the last chunk together with " + "the server headers but didn't detect it earlier. " + "We better stop reading."); + byte_count = (unsigned long long)(csp->iob->eod - csp->iob->cur); + csp->expected_content_length = byte_count; + csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET; + } + if (server_body && server_response_is_complete(csp, byte_count)) + { + if (csp->expected_content_length == byte_count) { - remember_connection(&csp->server_connection); - } - else -#endif /* def FEATURE_CONNECTION_SHARING */ - { - log_error(LOG_LEVEL_CONNECT, - "Closing server socket %d connected to %s. Total requests: %u.", - csp->server_connection.sfd, csp->server_connection.host, - csp->server_connection.requests_sent_total); - close_socket(csp->server_connection.sfd); - } - mark_connection_closed(&csp->server_connection); - } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - - csp->server_connection.sfd = forwarded_connect(fwd, http, csp); - - if (csp->server_connection.sfd == JB_INVALID_SOCKET) - { - if ((fwd->type != SOCKS_NONE) && (fwd->type != FORWARD_WEBSERVER)) - { - /* Socks error. */ - rsp = error_response(csp, "forwarding-failed"); - } - else if (errno == EINVAL) - { - rsp = error_response(csp, "no-such-domain"); - } - else - { - rsp = error_response(csp, "connect-failed"); - } - - /* Write the answer to the client */ - if (rsp != NULL) - { - send_crunch_response(csp, rsp); - } - - /* - * Temporary workaround to prevent already-read client - * bodies from being parsed as new requests. For now we - * err on the safe side and throw all the following - * requests under the bus, even if no client body has been - * buffered. A compliant client will repeat the dropped - * requests on an untainted connection. - * - * The proper fix is to discard the no longer needed - * client body in the buffer (if there is one) and to - * continue parsing the bytes that follow. - */ - drain_and_close_socket(csp->cfd); - csp->cfd = JB_INVALID_SOCKET; - - return; - } -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - save_connection_destination(csp->server_connection.sfd, - http, fwd, &csp->server_connection); - csp->server_connection.keep_alive_timeout = - (unsigned)csp->config->keep_alive_timeout; - } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - - csp->server_connection.requests_sent_total++; - - if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) - { - /* Client headers have been sent optimistically */ - assert(csp->headers->last == NULL); - } - else if (fwd->forward_host || (http->ssl == 0)) - { - int write_failure; - hdr = list_to_text(csp->headers); - if (hdr == NULL) - { - /* FIXME Should handle error properly */ - log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); - } - list_remove_all(csp->headers); - - /* - * Write the client's (modified) header to the server - * (along with anything else that may be in the buffer) - */ - write_failure = 0 != write_socket(csp->server_connection.sfd, hdr, strlen(hdr)); - freez(hdr); - - if (write_failure) - { - log_error(LOG_LEVEL_CONNECT, - "Failed sending request headers to: %s: %E", http->hostport); - } - else if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0) - && (flush_socket(csp->server_connection.sfd, csp->client_iob) < 0)) - { - write_failure = 1; - log_error(LOG_LEVEL_CONNECT, - "Failed sending request body to: %s: %E", http->hostport); - } - - if (write_failure) - { - rsp = error_response(csp, "connect-failed"); - if (rsp) - { - send_crunch_response(csp, rsp); - } - return; - } - } - 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. - */ - list_remove_all(csp->headers); - if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) - { - return; - } - clear_iob(csp->client_iob); - } - - log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); - - /* XXX: should the time start earlier for optimistically sent data? */ - csp->server_connection.request_sent = time(NULL); - - maxfd = (csp->cfd > csp->server_connection.sfd) ? - csp->cfd : csp->server_connection.sfd; - - /* 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 - - for (;;) - { -#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) - { - maxfd = csp->server_connection.sfd; - } - else -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - { - FD_SET(csp->cfd, &rfds); - } - - FD_SET(csp->server_connection.sfd, &rfds); - -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - if ((csp->flags & CSP_FLAG_CHUNKED) - && !(csp->flags & CSP_FLAG_CONTENT_LENGTH_SET) - && ((csp->iob->eod - csp->iob->cur) >= 5) - && !memcmp(csp->iob->eod-5, "0\r\n\r\n", 5)) - { - /* - * XXX: This check should be obsolete now, - * but let's wait a while to be sure. - */ - log_error(LOG_LEVEL_CONNECT, - "Looks like we got the last chunk together with " - "the server headers but didn't detect it earlier. " - "We better stop reading."); - byte_count = (unsigned long long)(csp->iob->eod - csp->iob->cur); - csp->expected_content_length = byte_count; - csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET; - } - if (server_body && server_response_is_complete(csp, byte_count)) - { - if (csp->expected_content_length == byte_count) - { - log_error(LOG_LEVEL_CONNECT, - "Done reading from server. Content length: %llu as expected. " - "Bytes most recently read: %d.", - byte_count, len); + log_error(LOG_LEVEL_CONNECT, + "Done reading from server. Content length: %llu as expected. " + "Bytes most recently read: %d.", + byte_count, len); } else { @@ -2182,9 +2074,32 @@ static void chat(struct client_state *csp) } #endif /* FEATURE_CONNECTION_KEEP_ALIVE */ +#ifdef HAVE_POLL + poll_fds[0].fd = csp->cfd; +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if (!watch_client_socket) + { + /* + * Ignore incoming data, but still watch out + * for disconnects etc. These flags are always + * implied anyway but explicitly setting them + * doesn't hurt. + */ + poll_fds[0].events = POLLERR|POLLHUP; + } + else +#endif + { + poll_fds[0].events = POLLIN; + } + poll_fds[1].fd = csp->server_connection.sfd; + poll_fds[1].events = POLLIN; + n = poll(poll_fds, 2, csp->config->socket_timeout * 1000); +#else timeout.tv_sec = csp->config->socket_timeout; timeout.tv_usec = 0; n = select((int)maxfd+1, &rfds, NULL, NULL, &timeout); +#endif /* def HAVE_POLL */ if (n == 0) { @@ -2199,7 +2114,11 @@ static void chat(struct client_state *csp) } else if (n < 0) { +#ifdef HAVE_POLL + log_error(LOG_LEVEL_ERROR, "poll() failed!: %E"); +#else log_error(LOG_LEVEL_ERROR, "select() failed!: %E"); +#endif mark_server_socket_tainted(csp); return; } @@ -2211,7 +2130,21 @@ static void chat(struct client_state *csp) * XXX: Make sure the client doesn't use pipelining * behind Privoxy's back. */ +#ifdef HAVE_POLL + if ((poll_fds[0].revents & (POLLERR|POLLHUP|POLLNVAL)) != 0) + { + log_error(LOG_LEVEL_CONNECT, + "The client socket %d has become unusable while " + "the server socket %d is still open.", + csp->cfd, csp->server_connection.sfd); + mark_server_socket_tainted(csp); + break; + } + + if (poll_fds[0].revents != 0) +#else if (FD_ISSET(csp->cfd, &rfds)) +#endif /* def HAVE_POLL*/ { int max_bytes_to_read = sizeof(buf) - 1; @@ -2299,7 +2232,11 @@ static void chat(struct client_state *csp) * If `hdr' is null, then it's the header otherwise it's the body. * FIXME: Does `hdr' really mean `host'? No. */ +#ifdef HAVE_POLL + if (poll_fds[1].revents != 0) +#else if (FD_ISSET(csp->server_connection.sfd, &rfds)) +#endif /* HAVE_POLL */ { #ifdef FEATURE_CONNECTION_KEEP_ALIVE /* @@ -2798,6 +2735,322 @@ static void chat(struct client_state *csp) } +/********************************************************************* + * + * Function : chat + * + * Description : Once a connection from the client has been accepted, + * this function is called (via serve()) to handle the + * main business of the communication. This function + * returns after dealing with a single request. It can + * be called multiple times with the same client socket + * if the client is keeping the connection alive. + * + * The decision whether or not a client connection will + * be kept alive is up to the caller which also must + * close the client socket when done. + * + * FIXME: chat is nearly thousand lines long. + * Ridiculous. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : Nothing. + * + *********************************************************************/ +static void chat(struct client_state *csp) +{ + const struct forward_spec *fwd; + struct http_request *http; + /* Skeleton for HTTP response, if we should intercept the request */ + struct http_response *rsp; + + http = csp->http; + + if (receive_client_request(csp) != JB_ERR_OK) + { + return; + } + if (parse_client_request(csp) != JB_ERR_OK) + { + return; + } + + /* decide how to route the HTTP request */ + fwd = forward_url(csp, http); + if (NULL == fwd) + { + log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); + /* Never get here - LOG_LEVEL_FATAL causes program exit */ + return; + } + + /* + * 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. + * + * connect = Open a socket to the host:port of the server + * and short-circuit server and client socket. + * + * 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. + * + * here's the matrix: + * SSL + * 0 1 + * +--------+--------+ + * | | | + * 0 | create | connect| + * | w/path | | + * Forwarding +--------+--------+ + * | | | + * 1 | create | pass | + * | w/url | | + * +--------+--------+ + * + */ + + if (http->ssl && connect_port_is_forbidden(csp)) + { + const char *acceptable_connect_ports = + csp->action->string[ACTION_STRING_LIMIT_CONNECT]; + assert(NULL != acceptable_connect_ports); + log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. " + "limit-connect{%s} doesn't allow CONNECT requests to %s", + csp->ip_addr_str, acceptable_connect_ports, csp->http->hostport); + csp->action->flags |= ACTION_BLOCK; + http->ssl = 0; + } + + if (http->ssl == 0) + { + 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. + */ + if (crunch_response_triggered(csp, crunchers_all)) + { + /* + * Yes. The client got the crunch response and we're done here. + */ + return; + } + + 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", + fwd->forward_host, fwd->forward_port, http->hostport); + } + else + { + log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport); + } + + /* here we connect to the server, gateway, or the forwarder */ + +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + if ((csp->server_connection.sfd != JB_INVALID_SOCKET) + && socket_is_still_alive(csp->server_connection.sfd) + && connection_destination_matches(&csp->server_connection, http, fwd)) + { + log_error(LOG_LEVEL_CONNECT, + "Reusing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + } + else + { + if (csp->server_connection.sfd != JB_INVALID_SOCKET) + { +#ifdef FEATURE_CONNECTION_SHARING + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) + { + remember_connection(&csp->server_connection); + } + else +#endif /* def FEATURE_CONNECTION_SHARING */ + { + log_error(LOG_LEVEL_CONNECT, + "Closing server socket %d connected to %s. Total requests: %u.", + csp->server_connection.sfd, csp->server_connection.host, + csp->server_connection.requests_sent_total); + close_socket(csp->server_connection.sfd); + } + mark_connection_closed(&csp->server_connection); + } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + csp->server_connection.sfd = forwarded_connect(fwd, http, csp); + + if (csp->server_connection.sfd == JB_INVALID_SOCKET) + { + if (fwd->type != SOCKS_NONE) + { + /* Socks error. */ + rsp = error_response(csp, "forwarding-failed"); + } + else if (errno == EINVAL) + { + rsp = error_response(csp, "no-such-domain"); + } + else + { + rsp = error_response(csp, "connect-failed"); + } + + /* Write the answer to the client */ + if (rsp != NULL) + { + send_crunch_response(csp, rsp); + } + + /* + * Temporary workaround to prevent already-read client + * bodies from being parsed as new requests. For now we + * err on the safe side and throw all the following + * requests under the bus, even if no client body has been + * buffered. A compliant client will repeat the dropped + * requests on an untainted connection. + * + * The proper fix is to discard the no longer needed + * client body in the buffer (if there is one) and to + * continue parsing the bytes that follow. + */ + drain_and_close_socket(csp->cfd); + csp->cfd = JB_INVALID_SOCKET; + + return; + } +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + save_connection_destination(csp->server_connection.sfd, + http, fwd, &csp->server_connection); + csp->server_connection.keep_alive_timeout = + (unsigned)csp->config->keep_alive_timeout; + } +#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ + + csp->server_connection.requests_sent_total++; + + if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) + { + /* Client headers have been sent optimistically */ + assert(csp->headers->last == NULL); + } + else if (fwd->forward_host || (http->ssl == 0)) + { + if (send_http_request(csp)) + { + rsp = error_response(csp, "connect-failed"); + if (rsp) + { + send_crunch_response(csp, rsp); + } + return; + } + } + 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. + */ + list_remove_all(csp->headers); + if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) + { + return; + } + clear_iob(csp->client_iob); + } + + log_error(LOG_LEVEL_CONNECT, "to %s successful", http->hostport); + + /* XXX: should the time start earlier for optimistically sent data? */ + csp->server_connection.request_sent = time(NULL); + + handle_established_connection(csp, fwd); +} + + +#ifdef FUZZ +/********************************************************************* + * + * Function : fuzz_server_response + * + * Description : Treat the input as a whole server response. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : fuzz_input_file = File to read the input from. + * + * Returns : 0 + * + *********************************************************************/ +extern int fuzz_server_response(struct client_state *csp, char *fuzz_input_file) +{ + static struct forward_spec fwd; /* Zero'd due to being static */ + csp->cfd = 0; + + if (strcmp(fuzz_input_file, "-") == 0) + { + /* XXX: Doesn'T work yet. */ + csp->server_connection.sfd = 0; + } + else + { + csp->server_connection.sfd = open(fuzz_input_file, O_RDONLY); + if (csp->server_connection.sfd == -1) + { + log_error(LOG_LEVEL_FATAL, "Failed to open %s: %E", + fuzz_input_file); + } + } + csp->content_type |= CT_GIF; + csp->action->flags |= ACTION_DEANIMATE; + csp->action->string[ACTION_STRING_DEANIMATE] = "last"; + + csp->http->path = strdup_or_die("/"); + csp->http->host = strdup_or_die("fuzz.example.org"); + csp->http->hostport = strdup_or_die("fuzz.example.org:80"); + /* Prevent client socket monitoring */ + csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; + csp->flags |= CSP_FLAG_CHUNKED; + + csp->config->feature_flags |= RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE; + csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; + + csp->content_type |= CT_DECLARED|CT_GIF; + + csp->config->socket_timeout = 0; + + cgi_init_error_messages(); + + handle_established_connection(csp, &fwd); + + return 0; +} +#endif + + #ifdef FEATURE_CONNECTION_KEEP_ALIVE /********************************************************************* * @@ -3128,7 +3381,7 @@ static int32 server_thread(void *data) * Returns : No. ,-) * *********************************************************************/ -static void usage(const char *myname) +static void usage(const char *name) { printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n" "Usage: %s [--config-test] " @@ -3139,8 +3392,14 @@ static void usage(const char *myname) #if defined(unix) "[--no-daemon] [--pidfile pidfile] [--pre-chroot-nslookup hostname] [--user user[.group]] " #endif /* defined(unix) */ - "[--version] [configfile]\n" - "Aborting\n", myname); + "[--version] [configfile]\n", + name); + +#ifdef FUZZ + show_fuzz_usage(name); +#endif + + printf("Aborting\n"); exit(2); @@ -3289,14 +3548,13 @@ static void initialize_mutexes(void) privoxy_mutex_init(&localtime_mutex); #endif /* ndef HAVE_GMTIME_R */ -#ifndef HAVE_RANDOM +#if !defined(HAVE_ARC4RANDOM) && !defined(HAVE_RANDOM) privoxy_mutex_init(&rand_mutex); -#endif /* ndef HAVE_RANDOM */ +#endif /* !defined(HAVE_ARC4RANDOM) && !defined(HAVE_RANDOM) */ #endif /* def MUTEX_LOCKS_AVAILABLE */ } - /********************************************************************* * * Function : main @@ -3328,13 +3586,19 @@ int main(int argc, char **argv) { int argc_pos = 0; int do_config_test = 0; +#ifndef HAVE_ARC4RANDOM unsigned int random_seed; +#endif #ifdef unix struct passwd *pw = NULL; struct group *grp = NULL; int do_chroot = 0; char *pre_chroot_nslookup_to_load_resolver = NULL; #endif +#ifdef FUZZ + char *fuzz_input_type = NULL; + char *fuzz_input_file = NULL; +#endif Argc = argc; Argv = argv; @@ -3462,7 +3726,20 @@ int main(int argc, char **argv) { do_config_test = 1; } - +#ifdef FUZZ + else if (strcmp(argv[argc_pos], "--fuzz") == 0) + { + argc_pos++; + if (argc < argc_pos + 2) usage(argv[0]); + fuzz_input_type = argv[argc_pos]; + argc_pos++; + fuzz_input_file = argv[argc_pos]; + } + else if (strcmp(argv[argc_pos], "--stfu") == 0) + { + set_debug_level(LOG_LEVEL_STFU); + } +#endif else if (argc_pos + 1 != argc) { /* @@ -3519,19 +3796,24 @@ int main(int argc, char **argv) InitWin32(); #endif +#ifndef HAVE_ARC4RANDOM random_seed = (unsigned int)time(NULL); #ifdef HAVE_RANDOM srandom(random_seed); #else srand(random_seed); #endif /* ifdef HAVE_RANDOM */ +#endif /* ifndef HAVE_ARC4RANDOM */ /* * Unix signal handling * * Catch the abort, interrupt and terminate signals for a graceful exit * Catch the hangup signal so the errlog can be reopened. - * Ignore the broken pipe signals (FIXME: Why?) + * + * Ignore the broken pipe signal as connection failures + * are handled when and where they occur without relying + * on a signal. */ #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA) { @@ -3566,6 +3848,13 @@ int main(int argc, char **argv) # endif /* def _WIN_CONSOLE */ #endif /* def _WIN32 */ +#ifdef FUZZ + if (fuzz_input_type != NULL) + { + exit(process_fuzzed_input(fuzz_input_type, fuzz_input_file)); + } +#endif + if (do_config_test) { exit(NULL == load_config()); @@ -3820,6 +4109,7 @@ static jb_socket bind_port_helper(const char *haddr, int hport) return JB_INVALID_SOCKET; } +#ifndef HAVE_POLL #ifndef _WIN32 if (bfd >= FD_SETSIZE) { @@ -3827,6 +4117,7 @@ static jb_socket bind_port_helper(const char *haddr, int hport) "Bind socket number too high to use select(): %d >= %d", bfd, FD_SETSIZE); } +#endif #endif if (haddr == NULL)