X-Git-Url: http://www.privoxy.org/gitweb/?p=privoxy.git;a=blobdiff_plain;f=jcc.c;h=53de19a607b4ca16450e2307b83f9877edbbef18;hp=bd20098e998774c2b57319205eff508e9f081f5e;hb=87085601ca0fa352ecdebbaa37a54400c43ec8c1;hpb=cb1c5ab8d5d827c5ac8c96177c39599937dd087c diff --git a/jcc.c b/jcc.c index bd20098e..53de19a6 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.355 2011/07/08 13:27:31 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.421 2012/12/07 12:50:37 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -6,7 +6,7 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.355 2011/07/08 13:27:31 fabiankeil Exp $" * Purpose : Main file. Contains main() method, main loop, and * the main connection-handling function. * - * Copyright : Written by and Copyright (C) 2001-2010 the + * Copyright : Written by and Copyright (C) 2001-2012 the * Privoxy team. http://www.privoxy.org/ * * Based on the Internet Junkbuster originally written @@ -151,7 +151,9 @@ static void serve(struct client_state *csp); static void usage(const char *myname); #endif static void initialize_mutexes(void); -static jb_socket bind_port_helper(struct configuration_spec *config); +static jb_socket bind_port_helper(const char *haddr, int hport); +static void bind_ports_helper(struct configuration_spec *config, jb_socket sockets[]); +static void close_ports_helper(jb_socket sockets[]); static void listen_loop(void); #ifdef AMIGA @@ -206,31 +208,31 @@ static int received_hup_signal = 0; /* HTTP snipplets. */ static const char CSUCCEED[] = - "HTTP/1.0 200 Connection established\r\n" + "HTTP/1.1 200 Connection established\r\n" "Proxy-Agent: Privoxy/" VERSION "\r\n\r\n"; static const char CHEADER[] = - "HTTP/1.0 400 Invalid header received from client\r\n" + "HTTP/1.1 400 Invalid header received from client\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Invalid header received from client.\r\n"; static const char FTP_RESPONSE[] = - "HTTP/1.0 400 Invalid request received from client\r\n" + "HTTP/1.1 400 Invalid request received from client\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Invalid request. Privoxy doesn't support FTP.\r\n"; static const char GOPHER_RESPONSE[] = - "HTTP/1.0 400 Invalid request received from client\r\n" + "HTTP/1.1 400 Invalid request received from client\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Invalid request. Privoxy doesn't support gopher.\r\n"; /* XXX: should be a template */ static const char MISSING_DESTINATION_RESPONSE[] = - "HTTP/1.0 400 Bad request received from client\r\n" + "HTTP/1.1 400 Bad request received from client\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" @@ -238,7 +240,7 @@ static const char MISSING_DESTINATION_RESPONSE[] = /* XXX: should be a template */ static const char INVALID_SERVER_HEADERS_RESPONSE[] = - "HTTP/1.0 502 Server or forwarder response invalid\r\n" + "HTTP/1.1 502 Server or forwarder response invalid\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" @@ -246,26 +248,33 @@ static const char INVALID_SERVER_HEADERS_RESPONSE[] = /* XXX: should be a template */ static const char MESSED_UP_REQUEST_RESPONSE[] = - "HTTP/1.0 400 Malformed request after rewriting\r\n" + "HTTP/1.1 400 Malformed request after rewriting\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Bad request. Messed up with header filters.\r\n"; static const char TOO_MANY_CONNECTIONS_RESPONSE[] = - "HTTP/1.0 503 Too many open connections\r\n" + "HTTP/1.1 503 Too many open connections\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Maximum number of open connections reached.\r\n"; static const char CLIENT_CONNECTION_TIMEOUT_RESPONSE[] = - "HTTP/1.0 504 Connection timeout\r\n" + "HTTP/1.1 504 Connection timeout\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "The connection timed out because the client request didn't arrive in time.\r\n"; +static const char CLIENT_BODY_PARSE_ERROR_RESPONSE[] = + "HTTP/1.1 400 Failed reading client body\r\n" + "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n\r\n" + "Failed parsing or buffering the chunk-encoded client body.\r\n"; + /* A function to crunch a response */ typedef struct http_response *(*crunch_func_ptr)(struct client_state *); @@ -315,7 +324,7 @@ static const struct cruncher crunchers_light[] = { #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA) /********************************************************************* * - * Function : sig_handler + * Function : sig_handler * * Description : Signal handler for different signals. * Exit gracefully on TERM and INT @@ -325,7 +334,7 @@ static const struct cruncher crunchers_light[] = { * Parameters : * 1 : the_signal = the signal cause this function to call * - * Returns : - + * Returns : - * *********************************************************************/ static void sig_handler(int the_signal) @@ -336,7 +345,7 @@ static void sig_handler(int the_signal) case SIGINT: log_error(LOG_LEVEL_INFO, "exiting by signal %d .. bye", the_signal); #if defined(unix) - if(pidfile) + if (pidfile) { unlink(pidfile); } @@ -348,10 +357,10 @@ static void sig_handler(int the_signal) #if defined(unix) received_hup_signal = 1; #endif - break; + break; default: - /* + /* * We shouldn't be here, unless we catch signals * in main() that we can't handle here! */ @@ -563,7 +572,7 @@ static jb_err get_server_headers(struct client_state *csp) log_error(LOG_LEVEL_HEADER, "Continue hack in da house."); continue_hack_in_da_house = 1; } - else if (*header == '\0') + else if (*header == '\0') { /* * If the header is empty, but the Continue hack @@ -659,6 +668,34 @@ static const char *crunch_reason(const struct http_response *rsp) } +/********************************************************************* + * + * Function : log_applied_actions + * + * Description : Logs the applied actions if LOG_LEVEL_ACTIONS is + * enabled. + * + * Parameters : + * 1 : actions = Current action spec to log + * + * Returns : Nothing. + * + *********************************************************************/ +static void log_applied_actions(const struct current_action_spec *actions) +{ + /* + * The conversion to text requires lots of memory allocations so + * we only do the conversion if the user is actually interested. + */ + if (debug_level_is_enabled(LOG_LEVEL_ACTIONS)) + { + char *actions_as_text = actions_to_line_of_text(actions); + log_error(LOG_LEVEL_ACTIONS, "%s", actions_as_text); + freez(actions_as_text); + } +} + + /********************************************************************* * * Function : send_crunch_response @@ -703,6 +740,7 @@ static void send_crunch_response(const struct client_state *csp, struct http_res status_code[3] = '\0'; /* 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); @@ -721,7 +759,7 @@ static void send_crunch_response(const struct client_state *csp, struct http_res if (cgi_error_memory() != rsp) { free_http_response(rsp); - } + } return; } @@ -749,7 +787,7 @@ static int crunch_response_triggered(struct client_state *csp, const struct crun /* * If CGI request crunching is disabled, * check the CGI dispatcher out of order to - * prevent unintentional blocks or redirects. + * prevent unintentional blocks or redirects. */ if (!(csp->config->feature_flags & RUNTIME_FEATURE_CGI_CRUNCHING) && (NULL != (rsp = dispatch_cgi(csp)))) @@ -821,16 +859,11 @@ static void build_request_line(struct client_state *csp, const struct forward_sp * Downgrade http version from 1.1 to 1.0 * if +downgrade action applies. */ - if ( (csp->action->flags & ACTION_DOWNGRADE) + if ((csp->action->flags & ACTION_DOWNGRADE) && (!strcmpic(http->ver, "HTTP/1.1"))) { freez(http->ver); - http->ver = strdup("HTTP/1.0"); - - if (http->ver == NULL) - { - log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version"); - } + http->ver = strdup_or_die("HTTP/1.0"); } /* @@ -878,7 +911,8 @@ static jb_err change_request_destination(struct client_state *csp) struct http_request *http = csp->http; jb_err err; - log_error(LOG_LEVEL_INFO, "Rewrite detected: %s", csp->headers->first->str); + log_error(LOG_LEVEL_REDIRECTS, "Rewrite detected: %s", + csp->headers->first->str); free_http_request(http); err = parse_http_request(csp->headers->first->str, http); if (JB_ERR_OK != err) @@ -889,12 +923,7 @@ static jb_err change_request_destination(struct client_state *csp) else { /* XXX: ocmd is a misleading name */ - http->ocmd = strdup(http->cmd); - if (http->ocmd == NULL) - { - log_error(LOG_LEVEL_FATAL, - "Out of memory copying rewritten HTTP request line"); - } + http->ocmd = strdup_or_die(http->cmd); } return err; @@ -930,6 +959,7 @@ static int server_response_is_complete(struct client_state *csp, */ csp->expected_content_length = 0; content_length_known = TRUE; + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; } if (csp->http->status == 204 || csp->http->status == 304) @@ -939,6 +969,7 @@ static int server_response_is_complete(struct client_state *csp, */ csp->expected_content_length = 0; content_length_known = TRUE; + csp->flags |= CSP_FLAG_SERVER_CONTENT_LENGTH_SET; } return (content_length_known && ((0 == csp->expected_content_length) @@ -1001,11 +1032,7 @@ void save_connection_destination(jb_socket sfd, assert(NULL != http->host); server_connection->sfd = sfd; - server_connection->host = strdup(http->host); - if (NULL == server_connection->host) - { - log_error(LOG_LEVEL_FATAL, "Out of memory saving socket."); - } + server_connection->host = strdup_or_die(http->host); server_connection->port = http->port; assert(NULL != fwd); @@ -1018,11 +1045,7 @@ void save_connection_destination(jb_socket sfd, server_connection->forwarder_type = fwd->type; if (NULL != fwd->gateway_host) { - server_connection->gateway_host = strdup(fwd->gateway_host); - if (NULL == server_connection->gateway_host) - { - log_error(LOG_LEVEL_FATAL, "Out of memory saving gateway_host."); - } + server_connection->gateway_host = strdup_or_die(fwd->gateway_host); } else { @@ -1032,11 +1055,7 @@ void save_connection_destination(jb_socket sfd, if (NULL != fwd->forward_host) { - server_connection->forward_host = strdup(fwd->forward_host); - if (NULL == server_connection->forward_host) - { - log_error(LOG_LEVEL_FATAL, "Out of memory saving forward_host."); - } + server_connection->forward_host = strdup_or_die(fwd->forward_host); } else { @@ -1055,11 +1074,9 @@ void save_connection_destination(jb_socket sfd, * we do. * * Data that doesn't belong to the current request is - * thrown away to let the client retry on a clean socket. - * - * XXX: This is a hack until we can deal with multiple - * pipelined requests at the same time. - * + * either thrown away to let the client retry on a clean + * socket, or stashed to be dealt with after the current + * request is served. * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) @@ -1070,7 +1087,7 @@ void save_connection_destination(jb_socket sfd, static void verify_request_length(struct client_state *csp) { unsigned long long buffered_request_bytes = - (unsigned long long)(csp->iob->eod - csp->iob->cur); + (unsigned long long)(csp->client_iob->eod - csp->client_iob->cur); if ((csp->expected_client_content_length != 0) && (buffered_request_bytes != 0)) @@ -1084,8 +1101,8 @@ static void verify_request_length(struct client_state *csp) } else { - assert(csp->iob->eod > csp->iob->cur + csp->expected_client_content_length); - csp->iob->eod = csp->iob->cur + csp->expected_client_content_length; + assert(csp->client_iob->eod > csp->client_iob->cur + csp->expected_client_content_length); + csp->client_iob->eod = csp->client_iob->cur + csp->expected_client_content_length; log_error(LOG_LEVEL_CONNECT, "Reducing expected bytes to 0. " "Marking the server socket tainted after throwing %llu bytes away.", buffered_request_bytes - csp->expected_client_content_length); @@ -1100,9 +1117,9 @@ static void verify_request_length(struct client_state *csp) } if (!(csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ) - && ((csp->iob->cur[0] != '\0') || (csp->expected_client_content_length != 0))) + && ((csp->client_iob->cur < csp->client_iob->eod) + || (csp->expected_client_content_length != 0))) { - csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; if (strcmpic(csp->http->gpc, "GET") && strcmpic(csp->http->gpc, "HEAD") && strcmpic(csp->http->gpc, "TRACE") @@ -1111,19 +1128,33 @@ static void verify_request_length(struct client_state *csp) { /* XXX: this is an incomplete hack */ csp->flags &= ~CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; - log_error(LOG_LEVEL_CONNECT, - "There might be a request body. The connection will not be kept alive."); + log_error(LOG_LEVEL_CONNECT, "There better be a request body."); } else { - /* XXX: and so is this */ csp->flags |= CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ; - log_error(LOG_LEVEL_CONNECT, - "Possible pipeline attempt detected. The connection will not " - "be kept alive and we will only serve the first request."); - /* Nuke the pipelined requests from orbit, just to be sure. */ - csp->iob->buf[0] = '\0'; - csp->iob->eod = csp->iob->cur = csp->iob->buf; + + if ((csp->config->feature_flags & RUNTIME_FEATURE_TOLERATE_PIPELINING) == 0) + { + csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; + log_error(LOG_LEVEL_CONNECT, + "Possible pipeline attempt detected. The connection will not " + "be kept alive and we will only serve the first request."); + /* Nuke the pipelined requests from orbit, just to be sure. */ + clear_iob(csp->client_iob); + } + else + { + /* + * Keep the pipelined data around for now, we'll deal with + * it once we're done serving the current request. + */ + csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; + assert(csp->client_iob->eod >= csp->client_iob->cur); + log_error(LOG_LEVEL_CONNECT, "Complete client request followed by " + "%d bytes of pipelined data received.", + (int)(csp->client_iob->eod - csp->client_iob->cur)); + } } } else @@ -1158,7 +1189,7 @@ static void mark_server_socket_tainted(struct client_state *csp) * actually been reused. */ if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE) - && !(csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED)) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) { log_error(LOG_LEVEL_CONNECT, "Marking the server socket %d tainted.", @@ -1187,15 +1218,45 @@ static char *get_request_line(struct client_state *csp) memset(buf, 0, sizeof(buf)); + if ((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0) + { + /* + * If there are multiple pipelined requests waiting, + * the flag will be set again once the next request + * has been parsed. + */ + csp->flags &= ~CSP_FLAG_PIPELINED_REQUEST_WAITING; + + request_line = get_header(csp->client_iob); + if ((NULL != request_line) && ('\0' != *request_line)) + { + return request_line; + } + else + { + log_error(LOG_LEVEL_CONNECT, "No complete request line " + "received yet. Continuing reading from %d.", csp->cfd); + } + } + do { if (!data_is_available(csp->cfd, csp->config->socket_timeout)) { - log_error(LOG_LEVEL_CONNECT, - "Stopped waiting for the request line. Timeout: %d.", - csp->config->socket_timeout); - write_socket(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE, - strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE)); + if (socket_is_still_alive(csp->cfd)) + { + log_error(LOG_LEVEL_CONNECT, + "No request line on socket %d received in time. Timeout: %d.", + csp->cfd, csp->config->socket_timeout); + write_socket(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE, + strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE)); + } + else + { + log_error(LOG_LEVEL_CONNECT, + "The client side of the connection on socket %d got " + "closed without sending a complete request line.", csp->cfd); + } return NULL; } @@ -1207,12 +1268,12 @@ static char *get_request_line(struct client_state *csp) * If there is no memory left for buffering the * request, there is nothing we can do but hang up */ - if (add_to_iob(csp, buf, len)) + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len)) { return NULL; } - request_line = get_header(csp->iob); + request_line = get_header(csp->client_iob); } while ((NULL != request_line) && ('\0' == *request_line)); @@ -1220,6 +1281,146 @@ static char *get_request_line(struct client_state *csp) } +enum chunk_status +{ + CHUNK_STATUS_MISSING_DATA, + CHUNK_STATUS_BODY_COMPLETE, + CHUNK_STATUS_PARSE_ERROR +}; + + +/********************************************************************* + * + * Function : chunked_body_is_complete + * + * Description : Figures out wheter or not a chunked body is complete. + * + * Currently it always starts at the beginning of the + * buffer which is somewhat wasteful and prevents Privoxy + * from starting to forward the correctly parsed chunks + * as soon as theoretically possible. + * + * Should be modified to work with a common buffer, + * and allow the caller to skip already parsed chunks. + * + * This would allow the function to be used for unbuffered + * response bodies as well. + * + * Parameters : + * 1 : iob = Buffer with the body to check. + * 2 : length = Length of complete body + * + * Returns : Enum with the result of the check. + * + *********************************************************************/ +static enum chunk_status chunked_body_is_complete(struct iob *iob, size_t *length) +{ + unsigned int chunksize; + char *p = iob->cur; + + do + { + /* + * We need at least a single digit, followed by "\r\n", + * followed by an unknown amount of data, followed by "\r\n". + */ + if (p + 5 > iob->eod) + { + return CHUNK_STATUS_MISSING_DATA; + } + if (sscanf(p, "%x", &chunksize) != 1) + { + return CHUNK_STATUS_PARSE_ERROR; + } + + /* + * We want at least a single digit, followed by "\r\n", + * followed by the specified amount of data, followed by "\r\n". + */ + if (p + chunksize + 5 > iob->eod) + { + return CHUNK_STATUS_MISSING_DATA; + } + + /* Skip chunk-size. */ + p = strstr(p, "\r\n"); + if (NULL == p) + { + return CHUNK_STATUS_PARSE_ERROR; + } + /* + * Skip "\r\n", the chunk data and another "\r\n". + * Moving p to either the beginning of the next chunk-size + * or one byte beyond the end of the chunked data. + */ + p += 2 + chunksize + 2; + } while (chunksize > 0U); + + *length = (size_t)(p - iob->cur); + assert(*length <= (size_t)(iob->eod - iob->cur)); + assert(p <= iob->eod); + + return CHUNK_STATUS_BODY_COMPLETE; + +} + + +/********************************************************************* + * + * Function : receive_chunked_client_request_body + * + * Description : Read the chunk-encoded client request body. + * Failures are dealt with. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * + * Returns : JB_ERR_OK or JB_ERR_PARSE + * + *********************************************************************/ +static jb_err receive_chunked_client_request_body(struct client_state *csp) +{ + size_t body_length; + enum chunk_status status; + + while (CHUNK_STATUS_MISSING_DATA == + (status = chunked_body_is_complete(csp->client_iob,&body_length))) + { + char buf[BUFFER_SIZE]; + int len; + + if (!data_is_available(csp->cfd, csp->config->socket_timeout)) + { + log_error(LOG_LEVEL_ERROR, + "Timeout while waiting for the client body."); + break; + } + len = read_socket(csp->cfd, buf, sizeof(buf) - 1); + if (len <= 0) + { + log_error(LOG_LEVEL_ERROR, "Read the client body failed: %E"); + break; + } + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len)) + { + break; + } + } + if (status != CHUNK_STATUS_BODY_COMPLETE) + { + write_socket(csp->cfd, CLIENT_BODY_PARSE_ERROR_RESPONSE, + strlen(CLIENT_BODY_PARSE_ERROR_RESPONSE)); + log_error(LOG_LEVEL_CLF, + "%s - - [%T] \"Failed reading chunked client body\" 400 0", csp->ip_addr_str); + return JB_ERR_PARSE; + } + log_error(LOG_LEVEL_CONNECT, + "Chunked client body completely read. Length: %d", body_length); + csp->expected_client_content_length = body_length; + + return JB_ERR_OK; + +} /********************************************************************* * @@ -1228,10 +1429,6 @@ static char *get_request_line(struct client_state *csp) * Description : Read the client's request (more precisely the * client headers) and answer it if necessary. * - * Note that since we're not using select() we could get - * blocked here if a client connected, then didn't say - * anything! - * * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * @@ -1251,6 +1448,9 @@ static jb_err receive_client_request(struct client_state *csp) struct list header_list; struct list *headers = &header_list; + /* We don't care if the arriving data is a valid HTTP request or not. */ + csp->requests_received_total++; + http = csp->http; memset(buf, 0, sizeof(buf)); @@ -1308,7 +1508,7 @@ static jb_err receive_client_request(struct client_state *csp) init_list(headers); for (;;) { - p = get_header(csp->iob); + p = get_header(csp->client_iob); if (p == NULL) { @@ -1337,8 +1537,8 @@ static jb_err receive_client_request(struct client_state *csp) destroy_list(headers); return JB_ERR_PARSE; } - - if (add_to_iob(csp, buf, len)) + + if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len)) { /* * If there is no memory left for buffering the @@ -1350,6 +1550,14 @@ static jb_err receive_client_request(struct client_state *csp) } else { + if (!strncmpic(p, "Transfer-Encoding:", 18)) + { + /* + * XXX: should be called through sed() + * but currently can't. + */ + client_transfer_encoding(csp, &p); + } /* * We were able to read a complete * header and can finally enlist it. @@ -1395,15 +1603,10 @@ static jb_err receive_client_request(struct client_state *csp) get_url_actions(csp, http); } - /* + /* * Save a copy of the original request for logging */ - http->ocmd = strdup(http->cmd); - if (http->ocmd == NULL) - { - log_error(LOG_LEVEL_FATAL, - "Out of memory copying HTTP request line"); - } + http->ocmd = strdup_or_die(http->cmd); enlist(csp->headers, http->cmd); /* Append the previously read headers */ @@ -1445,6 +1648,26 @@ static jb_err parse_client_request(struct client_state *csp) /* Assume persistence until further notice */ csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE; } + + if (csp->http->ssl == 0) + { + /* + * This whole block belongs to chat() but currently + * has to be executed before sed(). + */ + if (csp->flags & CSP_FLAG_CHUNKED_CLIENT_BODY) + { + if (receive_chunked_client_request_body(csp) != JB_ERR_OK) + { + return JB_ERR_PARSE; + } + } + else + { + csp->expected_client_content_length = get_expected_content_length(csp->headers); + } + verify_request_length(csp); + } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ err = sed(csp, FILTER_CLIENT_HEADERS); @@ -1475,13 +1698,6 @@ static jb_err parse_client_request(struct client_state *csp) return JB_ERR_PARSE; } -#ifdef FEATURE_CONNECTION_KEEP_ALIVE - if (csp->http->ssl == 0) - { - verify_request_length(csp); - } -#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - return JB_ERR_OK; } @@ -1531,7 +1747,7 @@ static void chat(struct client_state *csp) struct http_response *rsp; struct timeval timeout; #ifdef FEATURE_CONNECTION_KEEP_ALIVE - int watch_client_socket = 1; + int watch_client_socket; #endif memset(buf, 0, sizeof(buf)); @@ -1622,6 +1838,7 @@ static void chat(struct client_state *csp) return; } + log_applied_actions(csp->action); log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path); if (fwd->forward_host) @@ -1642,17 +1859,28 @@ static void chat(struct client_state *csp) && connection_destination_matches(&csp->server_connection, http, fwd)) { log_error(LOG_LEVEL_CONNECT, - "Reusing server socket %u. Opened for %s.", - csp->server_connection.sfd, csp->server_connection.host); + "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) { - log_error(LOG_LEVEL_CONNECT, - "Closing server socket %u. Opened for %s.", - csp->server_connection.sfd, csp->server_connection.host); - close_socket(csp->server_connection.sfd); +#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 */ @@ -1691,33 +1919,51 @@ static void chat(struct client_state *csp) } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - hdr = list_to_text(csp->headers); - if (hdr == NULL) + csp->server_connection.requests_sent_total++; + + if ((fwd->type == SOCKS_5T) && (NULL == csp->headers->first)) { - /* FIXME Should handle error properly */ - log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); + /* Client headers have been sent optimistically */ + assert(csp->headers->last == NULL); } - list_remove_all(csp->headers); - - if (fwd->forward_host || (http->ssl == 0)) + 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) */ - if (write_socket(csp->server_connection.sfd, hdr, strlen(hdr)) - || (flush_socket(csp->server_connection.sfd, csp->iob) < 0)) + 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, - "write header to: %s failed: %E", http->hostport); + "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); } - - freez(hdr); return; } } @@ -1725,24 +1971,22 @@ static void chat(struct client_state *csp) { /* * We're running an SSL tunnel and we're not forwarding, - * so just send the "connect succeeded" message to the - * client, flush the rest, and get out of the way. + * 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))) { - freez(hdr); return; } - IOB_RESET(csp); + 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); - /* we're finished with the client's header */ - freez(hdr); - maxfd = (csp->cfd > csp->server_connection.sfd) ? csp->cfd : csp->server_connection.sfd; @@ -1752,6 +1996,10 @@ static void chat(struct client_state *csp) server_body = 0; +#ifdef FEATURE_CONNECTION_KEEP_ALIVE + watch_client_socket = 0 == (csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING); +#endif + for (;;) { #ifdef __OS2__ @@ -1865,8 +2113,9 @@ static void chat(struct client_state *csp) */ watch_client_socket = 0; log_error(LOG_LEVEL_CONNECT, - "Stopping to watch the client socket. " - "There's already another request waiting."); + "Stopping to watch the client socket %d. " + "There's already another request waiting.", + csp->cfd); continue; } /* @@ -1959,7 +2208,6 @@ static void chat(struct client_state *csp) } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ - fflush(NULL); len = read_socket(csp->server_connection.sfd, buf, sizeof(buf) - 1); if (len < 0) @@ -2136,7 +2384,7 @@ static void chat(struct client_state *csp) * has been reached, switch to non-filtering mode, i.e. make & write the * header, flush the iob and buf, and get out of the way. */ - if (add_to_iob(csp, buf, len)) + if (add_to_iob(csp->iob, csp->config->buffer_limit, buf, len)) { size_t hdrlen; long flushed; @@ -2147,7 +2395,7 @@ static void chat(struct client_state *csp) hdr = list_to_text(csp->headers); if (hdr == NULL) { - /* + /* * Memory is too tight to even generate the header. * Send our static "Out-of-memory" page. */ @@ -2200,11 +2448,11 @@ static void chat(struct client_state *csp) * Buffer up the data we just read. If that fails, there's * little we can do but send our static out-of-memory page. */ - if (add_to_iob(csp, buf, len)) + if (add_to_iob(csp->iob, csp->config->buffer_limit, buf, len)) { log_error(LOG_LEVEL_ERROR, "Out of memory while looking for end of server headers."); rsp = cgi_error_memory(); - send_crunch_response(csp, rsp); + send_crunch_response(csp, rsp); mark_server_socket_tainted(csp); return; } @@ -2235,8 +2483,8 @@ static void chat(struct client_state *csp) * we can parse the headers we just continue here. */ log_error(LOG_LEVEL_CONNECT, - "Continuing buffering headers. Bytes most recently read: %d.", - len); + "Continuing buffering server headers from socket %d. " + "Bytes most recently read: %d.", csp->cfd, len); continue; } } @@ -2256,18 +2504,19 @@ static void chat(struct client_state *csp) if ((csp->flags & CSP_FLAG_REUSED_CLIENT_CONNECTION)) { log_error(LOG_LEVEL_ERROR, - "Empty server or forwarder response received on socket %d. " + "No server or forwarder response received on socket %d. " "Closing client socket %d without sending data.", csp->server_connection.sfd, csp->cfd); + log_error(LOG_LEVEL_CLF, + "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); } else { log_error(LOG_LEVEL_ERROR, - "Empty server or forwarder response received on socket %d.", + "No server or forwarder response received on socket %d.", csp->server_connection.sfd); send_crunch_response(csp, error_response(csp, "no-server-data")); } - log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); free_http_request(http); mark_server_socket_tainted(csp); return; @@ -2443,13 +2692,14 @@ static void chat(struct client_state *csp) *********************************************************************/ static void prepare_csp_for_next_request(struct client_state *csp) { + unsigned int toggled_on_flag_set = (0 != (csp->flags & CSP_FLAG_TOGGLED_ON)); + csp->content_type = 0; csp->content_length = 0; csp->expected_content_length = 0; csp->expected_client_content_length = 0; list_remove_all(csp->headers); - freez(csp->iob->buf); - memset(csp->iob, 0, sizeof(csp->iob)); + clear_iob(csp->iob); freez(csp->error_message); free_http_request(csp->http); destroy_list(csp->headers); @@ -2461,9 +2711,42 @@ static void prepare_csp_for_next_request(struct client_state *csp) csp->fwd = NULL; } /* XXX: Store per-connection flags someplace else. */ - csp->flags &= CSP_FLAG_TOGGLED_ON; - csp->flags |= CSP_FLAG_ACTIVE; - csp->flags |= CSP_FLAG_REUSED_CLIENT_CONNECTION; + csp->flags = (CSP_FLAG_ACTIVE | CSP_FLAG_REUSED_CLIENT_CONNECTION); + if (toggled_on_flag_set) + { + csp->flags |= CSP_FLAG_TOGGLED_ON; + } + + if (csp->client_iob->eod > csp->client_iob->cur) + { + long bytes_to_shift = csp->client_iob->cur - csp->client_iob->buf; + size_t data_length = (size_t)(csp->client_iob->eod - csp->client_iob->cur); + + assert(bytes_to_shift > 0); + assert(data_length > 0); + + log_error(LOG_LEVEL_CONNECT, "Shifting %d pipelined bytes by %d 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; + assert(csp->client_iob->eod == csp->client_iob->buf + bytes_to_shift + data_length); + csp->client_iob->eod = csp->client_iob->buf + data_length; + memset(csp->client_iob->eod, '\0', (size_t)bytes_to_shift); + + csp->flags |= CSP_FLAG_PIPELINED_REQUEST_WAITING; + } + else + { + /* + * We mainly care about resetting client_iob->cur so we don't + * waste buffer space at the beginning and don't mess up the + * request restoration done by cgi_show_request(). + * + * Freeing the buffer itself isn't technically necessary, + * but makes debugging more convenient. + */ + clear_iob(csp->client_iob); + } } #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */ @@ -2488,16 +2771,19 @@ void serve(struct client_state *csp) static void serve(struct client_state *csp) #endif /* def AMIGA */ { + int config_file_change_detected = 0; /* Only used for debugging */ #ifdef FEATURE_CONNECTION_KEEP_ALIVE #ifdef FEATURE_CONNECTION_SHARING static int monitor_thread_running = 0; #endif /* def FEATURE_CONNECTION_SHARING */ int continue_chatting = 0; + log_error(LOG_LEVEL_CONNECT, "Accepted connection from %s on socket %d", + csp->ip_addr_str, csp->cfd); + do { unsigned int latency; - int config_file_change_detected = 0; /* Only used for debugging */ chat(csp); @@ -2508,32 +2794,55 @@ static void serve(struct client_state *csp) latency = (unsigned)(csp->server_connection.response_received - csp->server_connection.request_sent) / 2; + if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && (csp->flags & CSP_FLAG_CRUNCHED) + && (csp->expected_client_content_length != 0)) + { + csp->flags |= CSP_FLAG_SERVER_SOCKET_TAINTED; + log_error(LOG_LEVEL_CONNECT, + "Tainting client socket %d due to unread data.", csp->cfd); + } + continue_chatting = (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE) - && (((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE) - && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED)) - || (csp->flags & CSP_FLAG_CRUNCHED)) + && !(csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) && (csp->cfd != JB_INVALID_SOCKET) - && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE); + && (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) + && ((csp->flags & CSP_FLAG_SERVER_CONTENT_LENGTH_SET) + || (csp->flags & CSP_FLAG_CHUNKED)); - if (continue_chatting && !(csp->flags & CSP_FLAG_CRUNCHED)) + if (!(csp->flags & CSP_FLAG_CRUNCHED) + && (csp->server_connection.sfd != JB_INVALID_SOCKET)) { - continue_chatting = (csp->server_connection.sfd != JB_INVALID_SOCKET) - && socket_is_still_alive(csp->server_connection.sfd); - if (continue_chatting) + if (!(csp->flags & CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET)) + { + csp->server_connection.keep_alive_timeout = csp->config->default_server_timeout; + } + if (!(csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE) + || (csp->flags & CSP_FLAG_SERVER_SOCKET_TAINTED) + || !socket_is_still_alive(csp->server_connection.sfd) + || !(latency < csp->server_connection.keep_alive_timeout)) { - if (!(csp->flags & CSP_FLAG_SERVER_KEEP_ALIVE_TIMEOUT_SET)) + log_error(LOG_LEVEL_CONNECT, + "Closing server socket %d connected to %s. " + "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), + socket_is_still_alive(csp->server_connection.sfd), + csp->server_connection.keep_alive_timeout); +#ifdef FEATURE_CONNECTION_SHARING + if (csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_SHARING) { - csp->server_connection.keep_alive_timeout = csp->config->default_server_timeout; - log_error(LOG_LEVEL_CONNECT, - "The server didn't specify how long the connection will stay open. " - "Assumed timeout is: %u.", csp->server_connection.keep_alive_timeout); + forget_connection(csp->server_connection.sfd); } - continue_chatting = (latency < csp->server_connection.keep_alive_timeout); +#endif /* def FEATURE_CONNECTION_SHARING */ + close_socket(csp->server_connection.sfd); + mark_connection_closed(&csp->server_connection); } } - if (continue_chatting && any_loaded_file_changed(csp->config->config_file_list)) + if (continue_chatting && any_loaded_file_changed(csp)) { continue_chatting = 0; config_file_change_detected = 1; @@ -2541,38 +2850,47 @@ static void serve(struct client_state *csp) if (continue_chatting) { - unsigned int client_timeout; - - if (csp->server_connection.sfd != JB_INVALID_SOCKET) + if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) != 0) + && socket_is_still_alive(csp->cfd)) { - client_timeout = (unsigned)csp->server_connection.keep_alive_timeout - latency; - log_error(LOG_LEVEL_CONNECT, - "Waiting for the next client request on socket %d. " - "Keeping the server socket %d to %s open.", - csp->cfd, csp->server_connection.sfd, csp->server_connection.host); + log_error(LOG_LEVEL_CONNECT, "Client request %d has been " + "pipelined on socket %d and the socket is still alive.", + csp->requests_received_total+1, csp->cfd); + prepare_csp_for_next_request(csp); + continue; } - else + + if (0 != (csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)) { - client_timeout = 1; /* XXX: Use something else here? */ - log_error(LOG_LEVEL_CONNECT, - "Waiting for the next client request on socket %d. " - "No server socket to keep open.", csp->cfd); + if (csp->server_connection.sfd != JB_INVALID_SOCKET) + { + log_error(LOG_LEVEL_CONNECT, + "Waiting for the next client request on socket %d. " + "Keeping the server socket %d to %s open.", + csp->cfd, csp->server_connection.sfd, csp->server_connection.host); + } + else + { + log_error(LOG_LEVEL_CONNECT, + "Waiting for the next client request on socket %d. " + "No server socket to keep open.", csp->cfd); + } } + if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE) - && data_is_available(csp->cfd, (int)client_timeout) + && data_is_available(csp->cfd, (int)csp->config->keep_alive_timeout) && socket_is_still_alive(csp->cfd)) { log_error(LOG_LEVEL_CONNECT, - "Client request arrived in time on socket %d.", csp->cfd); + "Client request %u arrived in time on socket %d.", + csp->requests_received_total+1, csp->cfd); prepare_csp_for_next_request(csp); } else { - log_error(LOG_LEVEL_CONNECT, - "No additional client request received in time on socket %d.", - csp->cfd); #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))) { time_t time_open = time(NULL) - csp->server_connection.timestamp; @@ -2584,7 +2902,7 @@ static void serve(struct client_state *csp) remember_connection(&csp->server_connection); csp->server_connection.sfd = JB_INVALID_SOCKET; - close_socket(csp->cfd); + drain_and_close_socket(csp->cfd); csp->cfd = JB_INVALID_SOCKET; privoxy_mutex_lock(&connection_reuse_mutex); if (!monitor_thread_running) @@ -2604,15 +2922,13 @@ static void serve(struct client_state *csp) else if (csp->server_connection.sfd != JB_INVALID_SOCKET) { log_error(LOG_LEVEL_CONNECT, - "The connection on server socket %d to %s isn't reusable. Closing. " - "Server connection: keep-alive %u, tainted: %u, socket alive %u. " - "Client connection: socket alive: %u. Server timeout: %u. " + "Closing server socket %d connected to %s. Keep-alive: %u. " + "Tainted: %u. Socket alive: %u. Timeout: %u. " "Configuration file change detected: %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), socket_is_still_alive(csp->server_connection.sfd), - socket_is_still_alive(csp->cfd), csp->server_connection.keep_alive_timeout, config_file_change_detected); } @@ -2639,7 +2955,13 @@ static void serve(struct client_state *csp) if (csp->cfd != JB_INVALID_SOCKET) { - close_socket(csp->cfd); + 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); } csp->flags &= ~CSP_FLAG_ACTIVE; @@ -2684,7 +3006,7 @@ static int32 server_thread(void *data) static void usage(const char *myname) { printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n" - "Usage: %s " + "Usage: %s [--config-test] " #if defined(unix) "[--chroot] " #endif /* defined(unix) */ @@ -2839,6 +3161,7 @@ static void initialize_mutexes(void) #ifndef HAVE_RANDOM privoxy_mutex_init(&rand_mutex); #endif /* ndef HAVE_RANDOM */ + #endif /* def MUTEX_LOCKS_AVAILABLE */ } @@ -2873,6 +3196,7 @@ int main(int argc, char **argv) #endif { int argc_pos = 0; + int do_config_test = 0; unsigned int random_seed; #ifdef unix struct passwd *pw = NULL; @@ -2913,7 +3237,7 @@ int main(int argc, char **argv) const char *pName = argv[argc_pos] + 9; if (*pName == ':') pName++; - exit( (install_service(pName)) ? 0 : 1 ); + exit((install_service(pName)) ? 0 : 1); } else if (strncmp(argv[argc_pos], "--uninstall", 11) == 0) { @@ -2922,7 +3246,7 @@ int main(int argc, char **argv) pName++; exit((uninstall_service(pName)) ? 0 : 1); } - else if (strcmp(argv[argc_pos], "--service" ) == 0) + else if (strcmp(argv[argc_pos], "--service") == 0) { bRunAsService = TRUE; w32_set_service_cwd(); @@ -2939,7 +3263,7 @@ int main(int argc, char **argv) usage(argv[0]); } - else if(strcmp(argv[argc_pos], "--version") == 0) + else if (strcmp(argv[argc_pos], "--version") == 0) { printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n"); exit(0); @@ -2947,31 +3271,26 @@ int main(int argc, char **argv) #if defined(unix) - else if (strcmp(argv[argc_pos], "--no-daemon" ) == 0) + else if (strcmp(argv[argc_pos], "--no-daemon") == 0) { set_debug_level(LOG_LEVEL_FATAL | LOG_LEVEL_ERROR | LOG_LEVEL_INFO); daemon_mode = 0; } - else if (strcmp(argv[argc_pos], "--pidfile" ) == 0) + else if (strcmp(argv[argc_pos], "--pidfile") == 0) { if (++argc_pos == argc) usage(argv[0]); - pidfile = strdup(argv[argc_pos]); + pidfile = strdup_or_die(argv[argc_pos]); } - else if (strcmp(argv[argc_pos], "--user" ) == 0) + else if (strcmp(argv[argc_pos], "--user") == 0) { char *user_arg; char *group_name; if (++argc_pos == argc) usage(argv[argc_pos]); - user_arg = strdup(argv[argc_pos]); - if (NULL == user_arg) - { - log_error(LOG_LEVEL_FATAL, - "Out of memory splitting --user argument '%s'.", argv[argc_pos]); - } + user_arg = strdup_or_die(argv[argc_pos]); group_name = strchr(user_arg, '.'); if (NULL != group_name) { @@ -2996,18 +3315,23 @@ int main(int argc, char **argv) freez(user_arg); } - else if (strcmp(argv[argc_pos], "--pre-chroot-nslookup" ) == 0) + else if (strcmp(argv[argc_pos], "--pre-chroot-nslookup") == 0) { if (++argc_pos == argc) usage(argv[0]); - pre_chroot_nslookup_to_load_resolver = strdup(argv[argc_pos]); + pre_chroot_nslookup_to_load_resolver = strdup_or_die(argv[argc_pos]); } - else if (strcmp(argv[argc_pos], "--chroot" ) == 0) + else if (strcmp(argv[argc_pos], "--chroot") == 0) { do_chroot = 1; } #endif /* defined(unix) */ + else if (strcmp(argv[argc_pos], "--config-test") == 0) + { + do_config_test = 1; + } + else if (argc_pos + 1 != argc) { /* @@ -3029,31 +3353,25 @@ int main(int argc, char **argv) show_version(Argv[0]); #if defined(unix) - if ( *configfile != '/' ) + if (*configfile != '/') { char cwd[BUFFER_SIZE]; char *abs_file; - size_t abs_file_size; + size_t abs_file_size; /* make config-filename absolute here */ if (NULL == getcwd(cwd, sizeof(cwd))) { perror("failed to get current working directory"); - exit( 1 ); + exit(1); } + basedir = strdup_or_die(cwd); /* XXX: why + 5? */ abs_file_size = strlen(cwd) + strlen(configfile) + 5; - basedir = strdup(cwd); - - if (NULL == basedir || - NULL == (abs_file = malloc(abs_file_size))) - { - perror("malloc failed"); - exit( 1 ); - } + abs_file = malloc_or_die(abs_file_size); strlcpy(abs_file, basedir, abs_file_size); - strlcat(abs_file, "/", abs_file_size ); + strlcat(abs_file, "/", abs_file_size); strlcat(abs_file, configfile, abs_file_size); configfile = abs_file; } @@ -3087,12 +3405,11 @@ int main(int argc, char **argv) #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA) { int idx; - const int catched_signals[] = { SIGTERM, SIGINT, SIGHUP, 0 }; - const int ignored_signals[] = { SIGPIPE, 0 }; + const int catched_signals[] = { SIGTERM, SIGINT, SIGHUP }; - for (idx = 0; catched_signals[idx] != 0; idx++) + for (idx = 0; idx < SZ(catched_signals); idx++) { -#ifdef sun /* FIXME: Is it safe to check for HAVE_SIGSET instead? */ +#ifdef sun /* FIXME: Is it safe to check for HAVE_SIGSET instead? */ if (sigset(catched_signals[idx], sig_handler) == SIG_ERR) #else if (signal(catched_signals[idx], sig_handler) == SIG_ERR) @@ -3102,12 +3419,9 @@ int main(int argc, char **argv) } } - for (idx = 0; ignored_signals[idx] != 0; idx++) + if (signal(SIGPIPE, SIG_IGN) == SIG_ERR) { - if (signal(ignored_signals[idx], SIG_IGN) == SIG_ERR) - { - log_error(LOG_LEVEL_FATAL, "Can't set ignore-handler for signal %d: %E", ignored_signals[idx]); - } + log_error(LOG_LEVEL_FATAL, "Can't set ignore-handler for SIGPIPE: %E"); } } @@ -3121,6 +3435,10 @@ int main(int argc, char **argv) # endif /* def _WIN_CONSOLE */ #endif /* def _WIN32 */ + if (do_config_test) + { + exit(NULL == load_config()); + } /* Initialize the CGI subsystem */ cgi_init_error_messages(); @@ -3137,12 +3455,12 @@ int main(int argc, char **argv) int fd; pid_t pid = fork(); - if ( pid < 0 ) /* error */ + if (pid < 0) /* error */ { perror("fork"); - exit( 3 ); + exit(3); } - else if ( pid != 0 ) /* parent */ + else if (pid != 0) /* parent */ { int status; pid_t wpid; @@ -3150,13 +3468,13 @@ int main(int argc, char **argv) * must check for errors * child died due to missing files aso */ - sleep( 1 ); - wpid = waitpid( pid, &status, WNOHANG ); - if ( wpid != 0 ) + sleep(1); + wpid = waitpid(pid, &status, WNOHANG); + if (wpid != 0) { - exit( 1 ); + exit(1); } - exit( 0 ); + exit(0); } /* child */ @@ -3262,7 +3580,7 @@ int main(int argc, char **argv) if (putenv(putenv_dummy) != 0) { log_error(LOG_LEVEL_FATAL, "Cannot putenv(): HOME"); - } + } snprintf(putenv_dummy, sizeof(putenv_dummy), "USER=%s", pw->pw_name); if (putenv(putenv_dummy) != 0) @@ -3283,7 +3601,7 @@ int main(int argc, char **argv) */ if (bRunAsService) { - /* Yup, so now we must attempt to establish a connection + /* Yup, so now we must attempt to establish a connection * with the service dispatcher. This will only work if this * process was launched by the service control manager to * actually run as a service. If this isn't the case, i've @@ -3325,61 +3643,127 @@ int main(int argc, char **argv) * on failure. * * Parameters : - * 1 : config = Privoxy configuration. Specifies port - * to bind to. + * 1 : haddr = Host addres to bind to. Use NULL to bind to + * INADDR_ANY. + * 2 : hport = Specifies port to bind to. * * Returns : Port that was opened. * *********************************************************************/ -static jb_socket bind_port_helper(struct configuration_spec * config) +static jb_socket bind_port_helper(const char *haddr, int hport) { int result; jb_socket bfd; - if (config->haddr == NULL) - { - log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses", - config->hport); - } - else - { - log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s", - config->hport, config->haddr); - } - - result = bind_port(config->haddr, config->hport, &bfd); + result = bind_port(haddr, hport, &bfd); if (result < 0) { + const char *bind_address = (NULL != haddr) ? haddr : "INADDR_ANY"; switch(result) { - case -3 : - log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " - "There may be another Privoxy or some other " - "proxy running on port %d", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", - config->hport, config->hport); + case -3: + log_error(LOG_LEVEL_FATAL, + "can't bind to %s:%d: There may be another Privoxy " + "or some other proxy running on port %d", + bind_address, hport, hport); - case -2 : - log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " - "The hostname is not resolvable", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport); + case -2: + log_error(LOG_LEVEL_FATAL, + "can't bind to %s:%d: The hostname is not resolvable", + bind_address, hport); - default : + default: log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: %E", - (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport); + bind_address, hport); } /* shouldn't get here */ return JB_INVALID_SOCKET; } - config->need_bind = 0; + if (haddr == NULL) + { + log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses", + hport); + } + else + { + log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s", + hport, haddr); + } return bfd; } +/********************************************************************* + * + * Function : bind_ports_helper + * + * Description : Bind the listen ports. Handles logging, and aborts + * on failure. + * + * Parameters : + * 1 : config = Privoxy configuration. Specifies ports + * to bind to. + * 2 : sockets = Preallocated array of opened sockets + * corresponding to specification in config. + * All non-opened sockets will be set to + * JB_INVALID_SOCKET. + * + * Returns : Nothing. Inspect sockets argument. + * + *********************************************************************/ +static void bind_ports_helper(struct configuration_spec * config, + jb_socket sockets[]) +{ + int i; + + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + if (config->hport[i]) + { + sockets[i] = bind_port_helper(config->haddr[i], config->hport[i]); + } + else + { + sockets[i] = JB_INVALID_SOCKET; + } + } + config->need_bind = 0; +} + + +/********************************************************************* + * + * Function : close_ports_helper + * + * Description : Close listenings ports. + * + * Parameters : + * 1 : sockets = Array of opened and non-opened sockets to + * close. All sockets will be set to + * JB_INVALID_SOCKET. + * + * Returns : Nothing. + * + *********************************************************************/ +static void close_ports_helper(jb_socket sockets[]) +{ + int i; + + for (i = 0; i < MAX_LISTENING_SOCKETS; i++) + { + if (JB_INVALID_SOCKET != sockets[i]) + { + close_socket(sockets[i]); + } + sockets[i] = JB_INVALID_SOCKET; + } +} + + #ifdef _WIN32 /* Without this simple workaround we get this compiler warning from _beginthread * warning C4028: formal parameter 1 different from declaration @@ -3406,7 +3790,7 @@ static void listen_loop(void) { struct client_states *csp_list = NULL; struct client_state *csp = NULL; - jb_socket bfd; + jb_socket bfds[MAX_LISTENING_SOCKETS]; struct configuration_spec *config; unsigned int active_threads = 0; @@ -3420,7 +3804,7 @@ static void listen_loop(void) initialize_reusable_connections(); #endif /* def FEATURE_CONNECTION_SHARING */ - bfd = bind_port_helper(config); + bind_ports_helper(config, bfds); #ifdef FEATURE_GRACEFUL_TERMINATION while (!g_terminate) @@ -3465,12 +3849,12 @@ static void listen_loop(void) log_error(LOG_LEVEL_CONNECT, "Listening for new connections ... "); - if (!accept_connection(csp, bfd)) + if (!accept_connection(csp, bfds)) { log_error(LOG_LEVEL_CONNECT, "accept failed: %E"); #ifdef AMIGA - if(!childs) + if (!childs) { exit(1); } @@ -3478,19 +3862,13 @@ static void listen_loop(void) freez(csp_list); continue; } - else - { - log_error(LOG_LEVEL_CONNECT, - "accepted connection from %s on socket %d", - csp->ip_addr_str, csp->cfd); - } csp->flags |= CSP_FLAG_ACTIVE; csp->server_connection.sfd = JB_INVALID_SOCKET; csp->config = config = load_config(); - if ( config->need_bind ) + if (config->need_bind) { /* * Since we were listening to the "old port", we will not see @@ -3505,9 +3883,9 @@ static void listen_loop(void) * that this will hurt people's feelings. */ - close_socket(bfd); + close_ports_helper(bfds); - bfd = bind_port_helper(config); + bind_ports_helper(config, bfds); } #ifdef FEATURE_TOGGLE @@ -3613,7 +3991,7 @@ static void listen_loop(void) #if defined(AMIGA) && !defined(SELECTED_ONE_OPTION) #define SELECTED_ONE_OPTION csp->cfd = ReleaseSocket(csp->cfd, -1); - + #ifdef __amigaos4__ child_id = (int)CreateNewProcTags(NP_Entry, (ULONG)server_thread, NP_Output, Output(), @@ -3629,7 +4007,7 @@ static void listen_loop(void) NP_StackSize, 200*1024, TAG_DONE); #endif - if(0 != child_id) + if (0 != child_id) { childs++; ((struct Task *)child_id)->tc_UserData = csp; @@ -3654,7 +4032,7 @@ static void listen_loop(void) serve(csp); - /* + /* * If we've been toggled or we've blocked the request, tell Mom */ @@ -3665,7 +4043,7 @@ static void listen_loop(void) } #endif /* def FEATURE_TOGGLE */ -#ifdef FEATURE_STATISTICS +#ifdef FEATURE_STATISTICS if (csp->flags & CSP_FLAG_REJECTED) { rc |= RC_FLAG_BLOCKED; @@ -3683,9 +4061,9 @@ static void listen_loop(void) int child_status; #if !defined(_WIN32) && !defined(__CYGWIN__) - wait( &child_status ); + wait(&child_status); - /* + /* * Evaluate child's return code: If the child has * - been toggled, toggle ourselves * - blocked its request, bump up the stats counter @@ -3704,7 +4082,7 @@ static void listen_loop(void) { urls_rejected++; } -#endif /* def FEATURE_STATISTICS */ +#endif /* def FEATURE_STATISTICS */ #endif /* !defined(_WIN32) && defined(__CYGWIN__) */ close_socket(csp->cfd);