X-Git-Url: http://www.privoxy.org/gitweb/?a=blobdiff_plain;f=jcc.c;h=9da991394577618997f9aec86fb2e473aa253e8d;hb=510005d561c0e8a43f62adff891120d4d0e2829f;hp=1cf56e40883b5f53f659f9d4bfe3786dcb7d24da;hpb=071dda21b7dbb77b3b8a36fd882f187b2fd5d698;p=privoxy.git diff --git a/jcc.c b/jcc.c index 1cf56e40..9da99139 100644 --- a/jcc.c +++ b/jcc.c @@ -1,4 +1,4 @@ -const char jcc_rcs[] = "$Id: jcc.c,v 1.121 2007/01/27 10:52:56 fabiankeil Exp $"; +const char jcc_rcs[] = "$Id: jcc.c,v 1.136 2007/06/01 16:41:11 fabiankeil Exp $"; /********************************************************************* * * File : $Source: /cvsroot/ijbswa/current/jcc.c,v $ @@ -33,6 +33,79 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.121 2007/01/27 10:52:56 fabiankeil Exp $" * * Revisions : * $Log: jcc.c,v $ + * Revision 1.136 2007/06/01 16:41:11 fabiankeil + * Add forward-override{} to change the forwarding settings through + * action sections. This is mainly interesting to forward different + * clients differently (for example based on User-Agent or request + * origin). + * + * Revision 1.135 2007/05/24 17:03:50 fabiankeil + * - Let usage() mention the --chroot parameter. + * - Use read_socket() consistently and always leave + * the last buffer byte alone, even in cases where + * null termination (currently) doesn't matter. + * + * Revision 1.134 2007/05/16 14:59:46 fabiankeil + * - Fix config file loading on Unix if no config file is specified. + * Since r1.97 Privoxy would always interpret the last argument as + * config file, even if it's a valid command line option. + * - Abort in case of unrecognized command line options. Closes #1719696. + * - Remove a bunch of unnecessary strcpy() calls (yay for c&p without thinking). + * - Replace the remaining strcpy() and strcat() calls with strlcpy() and strcat(). + * + * Revision 1.133 2007/05/04 11:23:19 fabiankeil + * - Don't rerun crunchers that only depend on the request URL. + * - Don't count redirects and CGI requests as "blocked requests". + * + * Revision 1.132 2007/04/25 15:15:17 fabiankeil + * Support crunching based on tags created by server-header taggers. + * + * Revision 1.131 2007/04/22 13:24:50 fabiankeil + * Make HTTP snippets static (again). Add a Content-Type for those + * with content so the browser doesn't guess it based on the URL. + * + * Revision 1.130 2007/04/19 13:47:34 fabiankeil + * Move crunching and request line rebuilding out of chat(). + * + * Revision 1.129 2007/04/15 16:39:20 fabiankeil + * Introduce tags as alternative way to specify which + * actions apply to a request. At the moment tags can be + * created based on client and server headers. + * + * Revision 1.128 2007/03/25 16:55:54 fabiankeil + * Don't CLF-log CONNECT requests twice. + * + * Revision 1.127 2007/03/20 13:53:17 fabiankeil + * Log the source address for ACL-related connection drops. + * + * Revision 1.126 2007/03/17 15:20:05 fabiankeil + * New config option: enforce-blocks. + * + * Revision 1.125 2007/03/09 14:12:00 fabiankeil + * - Move null byte check into separate function. + * - Don't confuse the client with error pages + * if a CONNECT request was already confirmed. + * + * Revision 1.124 2007/02/23 14:59:54 fabiankeil + * Speed up NULL byte escaping and only log the complete + * NULL byte requests with header debugging enabled. + * + * Revision 1.123 2007/02/21 18:42:10 fabiankeil + * Answer requests that contain NULL bytes with + * a custom response instead of waiting for more + * data until the client eventually hangs up. + * + * Revision 1.122 2007/02/07 11:12:02 fabiankeil + * - Move delivery and logging of crunched responses + * from chat() into send_crunch_response(). + * - Display the reason for generating http_responses. + * - Log the content length for LOG_LEVEL_CLF correctly + * (still incorrect for some fixed responses). + * - Reword an incorrect comment about + * treat-forbidden-connects-like-blocks violating + * the specs. + * - Add some log messages. + * * Revision 1.121 2007/01/27 10:52:56 fabiankeil * Move mutex initialization into separate * function and exit in case of errors. @@ -878,6 +951,10 @@ static int32 server_thread(void *data); pthread_mutex_t log_mutex; pthread_mutex_t log_init_mutex; +#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) +pthread_mutex_t resolver_mutex; +#endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */ + #ifndef HAVE_GMTIME_R pthread_mutex_t gmtime_mutex; #endif /* ndef HAVE_GMTIME_R */ @@ -886,14 +963,6 @@ pthread_mutex_t gmtime_mutex; pthread_mutex_t localtime_mutex; #endif /* ndef HAVE_GMTIME_R */ -#ifndef HAVE_GETHOSTBYADDR_R -pthread_mutex_t gethostbyaddr_mutex; -#endif /* ndef HAVE_GETHOSTBYADDR_R */ - -#ifndef HAVE_GETHOSTBYNAME_R -pthread_mutex_t gethostbyname_mutex; -#endif /* ndef HAVE_GETHOSTBYNAME_R */ - #ifndef HAVE_RANDOM pthread_mutex_t rand_mutex; #endif /* ndef HAVE_RANDOM */ @@ -917,46 +986,97 @@ static const char VANILLA_WAFER[] = "(copyright_or_otherwise)_applying_to_any_cookie._"; /* HTTP snipplets. */ -const char CSUCCEED[] = +const static char CSUCCEED[] = "HTTP/1.0 200 Connection established\n" "Proxy-Agent: Privoxy/" VERSION "\r\n\r\n"; -const char CHEADER[] = +const static char CHEADER[] = "HTTP/1.0 400 Invalid header received from browser\r\n" + "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Invalid header received from browser.\r\n"; -const char CFORBIDDEN[] = +const static char CFORBIDDEN[] = "HTTP/1.0 403 Connection not allowable\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" "X-Hint: If you read this message interactively, then you know why this happens ,-)\r\n" "Connection: close\r\n\r\n"; -const char FTP_RESPONSE[] = +const static char FTP_RESPONSE[] = "HTTP/1.0 400 Invalid request received from browser\r\n" + "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Invalid request. Privoxy doesn't support FTP.\r\n"; -const char GOPHER_RESPONSE[] = +const static char GOPHER_RESPONSE[] = "HTTP/1.0 400 Invalid request received from browser\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 */ -const char MISSING_DESTINATION_RESPONSE[] = +const static char MISSING_DESTINATION_RESPONSE[] = "HTTP/1.0 400 Bad request received from browser\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Bad request. Privoxy was unable to extract the destination.\r\n"; /* XXX: should be a template */ -const char NO_SERVER_DATA_RESPONSE[] = +const static char NO_SERVER_DATA_RESPONSE[] = "HTTP/1.0 502 Server or forwarder response empty\r\n" "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" "Connection: close\r\n\r\n" "Empty server or forwarder response.\r\n" "The connection was closed without sending any data.\r\n"; +/* XXX: should be a template */ +const static char NULL_BYTE_RESPONSE[] = + "HTTP/1.0 400 Bad request received from browser\r\n" + "Proxy-Agent: Privoxy " VERSION "\r\n" + "Content-Type: text/plain\r\n" + "Connection: close\r\n\r\n" + "Bad request. Null byte(s) before end of request.\r\n"; + +/* A function to crunch a response */ +typedef struct http_response *(*crunch_func_ptr)(struct client_state *); + +/* Crunch function flags */ +#define CF_NO_FLAGS 0 +/* Cruncher applies to forced requests as well */ +#define CF_IGNORE_FORCE 1 +/* Crunched requests are counted for the block statistics */ +#define CF_COUNT_AS_REJECT 2 + +/* A crunch function and its flags */ +struct cruncher +{ + const crunch_func_ptr cruncher; + const int flags; +}; + +/* Complete list of cruncher functions */ +const static struct cruncher crunchers_all[] = { + { direct_response, CF_COUNT_AS_REJECT|CF_IGNORE_FORCE}, + { block_url, CF_COUNT_AS_REJECT }, +#ifdef FEATURE_TRUST + { trust_url, CF_COUNT_AS_REJECT }, +#endif /* def FEATURE_TRUST */ + { redirect_url, CF_NO_FLAGS }, + { dispatch_cgi, CF_IGNORE_FORCE}, + { NULL, 0 } +}; + +/* Light version, used after tags are applied */ +const static struct cruncher crunchers_light[] = { + { block_url, CF_COUNT_AS_REJECT }, + { redirect_url, CF_NO_FLAGS }, + { NULL, 0 } +}; + + #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA) /********************************************************************* * @@ -1043,13 +1163,13 @@ int client_protocol_is_unsupported(const struct client_state *csp, char *req) { if (!strncmpic(req, "GET ftp://", 10)) { - strcpy(buf, FTP_RESPONSE); + strlcpy(buf, FTP_RESPONSE, sizeof(buf)); log_error(LOG_LEVEL_ERROR, "%s tried to use Privoxy as FTP proxy: %s", csp->ip_addr_str, req); } else { - strcpy(buf, GOPHER_RESPONSE); + strlcpy(buf, GOPHER_RESPONSE, sizeof(buf)); log_error(LOG_LEVEL_ERROR, "%s tried to use Privoxy as gopher proxy: %s", csp->ip_addr_str, req); } @@ -1093,7 +1213,6 @@ int client_protocol_is_unsupported(const struct client_state *csp, char *req) *********************************************************************/ jb_err get_request_destination_elsewhere(struct client_state *csp, struct list *headers) { - char buf[BUFFER_SIZE]; char *req; if (!(csp->config->feature_flags & RUNTIME_FEATURE_ACCEPT_INTERCEPTED_REQUESTS)) @@ -1105,8 +1224,7 @@ jb_err get_request_destination_elsewhere(struct client_state *csp, struct list * log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd); - strcpy(buf, CHEADER); - write_socket(csp->cfd, buf, strlen(buf)); + write_socket(csp->cfd, CHEADER, strlen(CHEADER)); destroy_list(headers); return JB_ERR_PARSE; @@ -1132,8 +1250,7 @@ jb_err get_request_destination_elsewhere(struct client_state *csp, struct list * csp->ip_addr_str, csp->http->cmd, req); freez(req); - strcpy(buf, MISSING_DESTINATION_RESPONSE); - write_socket(csp->cfd, buf, strlen(buf)); + write_socket(csp->cfd, MISSING_DESTINATION_RESPONSE, strlen(MISSING_DESTINATION_RESPONSE)); destroy_list(headers); return JB_ERR_PARSE; @@ -1373,6 +1490,182 @@ void send_crunch_response(struct client_state *csp, struct http_response *rsp) } +/********************************************************************* + * + * Function : request_contains_null_bytes + * + * Description : Checks for NULL bytes in the request and sends + * an error message to the client if any were found. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : buf = Data from the client's request to check. + * 3 : len = The data length. + * + * Returns : TRUE if the request contained one or more NULL bytes, or + * FALSE otherwise. + * + *********************************************************************/ +int request_contains_null_bytes(const struct client_state *csp, char *buf, int len) +{ + size_t c_len; /* Request lenght when treated as C string */ + + c_len = strlen(buf); + + if (c_len < len) + { + /* + * Null byte(s) found. Log the request, + * return an error response and hang up. + */ + size_t tmp_len = c_len; + + do + { + /* + * Replace NULL byte(s) with '°' characters + * so the request can be logged as string. + * XXX: Is there a better replacement character? + */ + buf[tmp_len]='°'; + tmp_len += strlen(buf+tmp_len); + } while (tmp_len < len); + + log_error(LOG_LEVEL_ERROR, "%s\'s request contains at least one NULL byte " + "(length=%d, strlen=%d).", csp->ip_addr_str, len, c_len); + log_error(LOG_LEVEL_HEADER, + "Offending request data with NULL bytes turned into \'°\' characters: %s", buf); + + write_socket(csp->cfd, NULL_BYTE_RESPONSE, strlen(NULL_BYTE_RESPONSE)); + + /* XXX: Log correct size */ + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str); + + return TRUE; + } + + return FALSE; +} + + +/********************************************************************* + * + * Function : crunch_response_triggered + * + * Description : Checks if the request has to be crunched, + * and delivers the crunch response if necessary. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : crunchers = list of cruncher functions to run + * + * Returns : TRUE if the request was answered with a crunch response + * FALSE otherwise. + * + *********************************************************************/ +int crunch_response_triggered(struct client_state *csp, const struct cruncher crunchers[]) +{ + struct http_response *rsp = NULL; + const struct cruncher *c; + + for (c = crunchers; c->cruncher != NULL; c++) + { + /* + * Check the cruncher if either Privoxy is toggled + * on and the request isn't forced, or if the cruncher + * applies to forced requests as well. + */ + if (((csp->flags & CSP_FLAG_TOGGLED_ON) && + !(csp->flags & CSP_FLAG_FORCED)) || + (c->flags & CF_IGNORE_FORCE)) + { + rsp = c->cruncher(csp); + if (NULL != rsp) + { + /* Deliver, log and free the interception response. */ + send_crunch_response(csp, rsp); +#ifdef FEATURE_STATISTICS + if (c->flags & CF_COUNT_AS_REJECT) + { + csp->flags |= CSP_FLAG_REJECTED; + } +#endif /* def FEATURE_STATISTICS */ + + return TRUE; + } + } + } + + return FALSE; +} + + +/********************************************************************* + * + * Function : build_request_line + * + * Description : Builds the HTTP request line. + * + * If a HTTP forwarder is used it expects the whole URL, + * web servers only get the path. + * + * Parameters : + * 1 : csp = Current client state (buffers, headers, etc...) + * 2 : fwd = The forwarding spec used for the request + * XXX: Should use http->fwd instead. + * 3 : request_line = The old request line which will be replaced. + * + * Returns : Nothing. Terminates in case of memory problems. + * + *********************************************************************/ +void build_request_line(struct client_state *csp, const struct forward_spec *fwd, char **request_line) +{ + 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"))) + { + freez(http->ver); + http->ver = strdup("HTTP/1.0"); + + if (http->ver == NULL) + { + log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version"); + } + } + + /* + * Rebuild the request line. + */ + freez(*request_line); + *request_line = strdup(http->gpc); + string_append(request_line, " "); + + if (fwd->forward_host) + { + string_append(request_line, http->url); + } + else + { + string_append(request_line, http->path); + } + string_append(request_line, " "); + string_append(request_line, http->ver); + + if (*request_line == NULL) + { + log_error(LOG_LEVEL_FATAL, "Out of memory writing HTTP command"); + } + log_error(LOG_LEVEL_HEADER, "New HTTP Request-Line: %s", *request_line); +} + + /********************************************************************* * * Function : chat @@ -1389,35 +1682,11 @@ void send_crunch_response(struct client_state *csp, struct http_response *rsp) * Parameters : * 1 : csp = Current client state (buffers, headers, etc...) * - * Returns : On success, the number of bytes written are returned (zero - * indicates nothing was written). On error, -1 is returned, - * and errno is set appropriately. If count is zero and the - * file descriptor refers to a regular file, 0 will be - * returned without causing any other effect. For a special - * file, the results are not portable. + * Returns : Nothing. * *********************************************************************/ static void chat(struct client_state *csp) { -/* - * This next lines are a little ugly, but they simplifies the if statements - * below. Basically if TOGGLE, then we want the if to test if the - * CSP_FLAG_TOGGLED_ON flag ist set, else we don't. And if FEATURE_FORCE_LOAD, - * then we want the if to test for CSP_FLAG_FORCED , else we don't - */ -#ifdef FEATURE_TOGGLE -# define IS_TOGGLED_ON_AND (csp->flags & CSP_FLAG_TOGGLED_ON) && -#else /* ifndef FEATURE_TOGGLE */ -# define IS_TOGGLED_ON_AND -#endif /* ndef FEATURE_TOGGLE */ -#ifdef FEATURE_FORCE_LOAD -# define IS_NOT_FORCED_AND !(csp->flags & CSP_FLAG_FORCED) && -#else /* ifndef FEATURE_FORCE_LOAD */ -# define IS_NOT_FORCED_AND -#endif /* def FEATURE_FORCE_LOAD */ - -#define IS_ENABLED_AND IS_TOGGLED_ON_AND IS_NOT_FORCED_AND - char buf[BUFFER_SIZE]; char *hdr; char *p; @@ -1454,6 +1723,8 @@ static void chat(struct client_state *csp) http = csp->http; + memset(buf, 0, sizeof(buf)); + /* * Read the client's request. Note that since we're not using select() we * could get blocked here if a client connected, then didn't say anything! @@ -1461,10 +1732,16 @@ static void chat(struct client_state *csp) for (;;) { - len = read_socket(csp->cfd, buf, sizeof(buf)); + len = read_socket(csp->cfd, buf, sizeof(buf) - 1); if (len <= 0) break; /* error! */ - + + if (request_contains_null_bytes(csp, buf, len)) + { + /* NULL bytes found and dealt with, just hang up. */ + return; + } + /* * If there is no memory left for buffering the * request, there is nothing we can do but hang up @@ -1498,15 +1775,23 @@ static void chat(struct client_state *csp) } #ifdef FEATURE_FORCE_LOAD - /* If this request contains the FORCE_PREFIX, - * better get rid of it now and set the force flag --oes + /* + * If this request contains the FORCE_PREFIX and blocks + * aren't enforced, get rid of it and set the force flag. */ - if (strstr(req, FORCE_PREFIX)) { - strclean(req, FORCE_PREFIX); - log_error(LOG_LEVEL_FORCE, "Enforcing request \"%s\".\n", req); - csp->flags |= CSP_FLAG_FORCED; + if (csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS) + { + log_error(LOG_LEVEL_FORCE, + "Ignored force prefix in request: \"%s\".", req); + } + else + { + strclean(req, FORCE_PREFIX); + log_error(LOG_LEVEL_FORCE, "Enforcing request: \"%s\".", req); + csp->flags |= CSP_FLAG_FORCED; + } } #endif /* def FEATURE_FORCE_LOAD */ @@ -1527,10 +1812,9 @@ static void chat(struct client_state *csp) if (http->cmd == NULL) { - strcpy(buf, CHEADER); - write_socket(csp->cfd, buf, strlen(buf)); + write_socket(csp->cfd, CHEADER, strlen(CHEADER)); /* XXX: Use correct size */ - log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 400 0", csp->ip_addr_str); + log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str); log_error(LOG_LEVEL_ERROR, "Invalid header received from %s.", csp->ip_addr_str); free_http_request(http); @@ -1543,7 +1827,7 @@ static void chat(struct client_state *csp) { if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) ) { - len = read_socket(csp->cfd, buf, sizeof(buf)); + len = read_socket(csp->cfd, buf, sizeof(buf) - 1); if (len <= 0) { log_error(LOG_LEVEL_ERROR, "read from client failed: %E"); @@ -1573,7 +1857,7 @@ static void chat(struct client_state *csp) /* * If we still don't know the request destination, * the request is invalid or the client uses - * Privoxy without it's knowledge. + * Privoxy without its knowledge. */ if (JB_ERR_OK != get_request_destination_elsewhere(csp, headers)) { @@ -1589,9 +1873,55 @@ static void chat(struct client_state *csp) } } - /* decide how to route the HTTP request */ + /* + * Determine the actions for this URL + */ +#ifdef FEATURE_TOGGLE + if (!(csp->flags & CSP_FLAG_TOGGLED_ON)) + { + /* Most compatible set of actions (i.e. none) */ + init_current_action(csp->action); + } + else +#endif /* ndef FEATURE_TOGGLE */ + { + url_actions(http, csp); + } + + /* + * Save a copy of the original request for logging + */ + http->ocmd = strdup(http->cmd); - if ((fwd = forward_url(http, csp)) == NULL) + if (http->ocmd == NULL) + { + log_error(LOG_LEVEL_FATAL, "Out of memory copying HTTP request line"); + } + + enlist(csp->headers, http->cmd); + + /* Append the previously read headers */ + list_append_list_unique(csp->headers, headers); + destroy_list(headers); + + /* + * If the user has not supplied any wafers, and the user has not + * told us to suppress the vanilla wafer, then send the vanilla wafer. + */ + if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER]) + && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0)) + { + enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER); + } + + if (JB_ERR_OK != sed(client_patterns, add_client_headers, csp)) + { + log_error(LOG_LEVEL_FATAL, "Failed to parse client headers"); + } + csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE; + + /* decide how to route the HTTP request */ + if (NULL == (fwd = forward_url(http, csp))) { log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!? This can't happen!"); /* Never get here - LOG_LEVEL_FATAL causes program exit */ @@ -1633,22 +1963,6 @@ static void chat(struct client_state *csp) * */ - /* - * Determine the actions for this URL - */ -#ifdef FEATURE_TOGGLE - if (!(csp->flags & CSP_FLAG_TOGGLED_ON)) - { - /* Most compatible set of actions (i.e. none) */ - init_current_action(csp->action); - } - else -#endif /* ndef FEATURE_TOGGLE */ - { - url_actions(http, csp); - } - - /* * Check if a CONNECT request is allowable: * In the absence of a +limit-connect action, allow only port 443. @@ -1673,88 +1987,30 @@ static void chat(struct client_state *csp) } else { - strcpy(buf, CFORBIDDEN); - write_socket(csp->cfd, buf, strlen(buf)); + write_socket(csp->cfd, CFORBIDDEN, strlen(CFORBIDDEN)); log_error(LOG_LEVEL_CONNECT, "Denying suspicious CONNECT request from %s", csp->ip_addr_str); log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 403 0", csp->ip_addr_str); - return; - } - } - } + list_remove_all(csp->headers); - /* - * Downgrade http version from 1.1 to 1.0 if +downgrade - * action applies - */ - if ( (http->ssl == 0) - && (!strcmpic(http->ver, "HTTP/1.1")) - && (csp->action->flags & ACTION_DOWNGRADE)) - { - freez(http->ver); - http->ver = strdup("HTTP/1.0"); - - if (http->ver == NULL) - { - log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version"); + return; + } } } - /* - * 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"); - } - - /* - * (Re)build the HTTP request for non-SSL requests. - * If forwarding, use the whole URL, else, use only the path. - */ if (http->ssl == 0) { - freez(http->cmd); - - http->cmd = strdup(http->gpc); - string_append(&http->cmd, " "); - - if (fwd->forward_host) - { - string_append(&http->cmd, http->url); - } - else - { - string_append(&http->cmd, http->path); - } - string_append(&http->cmd, " "); - string_append(&http->cmd, http->ver); - - if (http->cmd == NULL) - { - log_error(LOG_LEVEL_FATAL, "Out of memory writing HTTP command"); - } - log_error(LOG_LEVEL_HEADER, "New HTTP Request-Line: %s", http->cmd); + freez(csp->headers->first->str); + build_request_line(csp, fwd, &csp->headers->first->str); } - enlist(csp->headers, http->cmd); - - /* Append the previously read headers */ - list_append_list_unique(csp->headers, headers); - destroy_list(headers); - /* - * If the user has not supplied any wafers, and the user has not - * told us to suppress the vanilla wafer, then send the vanilla wafer. - */ - if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER]) - && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0)) + hdr = list_to_text(csp->headers); + if (hdr == NULL) { - enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER); + /* FIXME Should handle error properly */ + log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); } - #ifdef FEATURE_KILL_POPUPS block_popups = ((csp->action->flags & ACTION_NO_POPUPS) != 0); #endif /* def FEATURE_KILL_POPUPS */ @@ -1767,59 +2023,28 @@ static void chat(struct client_state *csp) jpeg_inspect = ((csp->action->flags & ACTION_JPEG_INSPECT) != 0); /* - * We have a request. Now, check to see if we need to - * intercept it, i.e. If .. + * We have a request. Check if one of the crunchers wants it. */ - - if ( - /* We may not forward the request by rfc2616 sect 14.31 */ - (NULL != (rsp = direct_response(csp))) - - /* or we are enabled and... */ - || (IS_ENABLED_AND ( - - /* ..the request was blocked */ - ( NULL != (rsp = block_url(csp))) - - /* ..or untrusted */ -#ifdef FEATURE_TRUST - || ( NULL != (rsp = trust_url(csp))) -#endif /* def FEATURE_TRUST */ - - /* ..or a redirect kicked in */ - || ( NULL != (rsp = redirect_url(csp))) - )) - - /* - * .. or a CGI call was detected and answered. - * - * This check comes last to give the user the power - * to deny acces to some (or all) of the cgi pages. - */ - || (NULL != (rsp = dispatch_cgi(csp))) - - ) + if (crunch_response_triggered(csp, crunchers_all)) { /* - * Deliver, log and free the interception - * response. Afterwards we're done here. + * Yes. The client got the crunch response + * and we are done here after cleaning up. */ - send_crunch_response(csp, rsp); -#ifdef FEATURE_STATISTICS - /* Count as a rejected request */ - csp->flags |= CSP_FLAG_REJECTED; -#endif /* def FEATURE_STATISTICS */ + freez(hdr); + list_remove_all(csp->headers); return; } - hdr = sed(client_patterns, add_client_headers, csp); - if (hdr == NULL) - { - /* FIXME Should handle error properly */ - log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header"); - } - + /* + * The headers can't be removed earlier because + * they were still needed for the referrer check + * in case of CGI crunches. + * + * XXX: Would it be worth to move the referrer check + * into client_referrer() and set a flag if it's trusted? + */ list_remove_all(csp->headers); log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path); @@ -1863,7 +2088,7 @@ static void chat(struct client_state *csp) /* Write the answer to the client */ - if(rsp != NULL) + if (rsp != NULL) { send_crunch_response(csp, rsp); } @@ -1902,10 +2127,7 @@ static void chat(struct client_state *csp) * so just send the "connect succeeded" message to the * client, flush the rest, and get out of the way. */ - log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 0", - csp->ip_addr_str, http->ocmd); - - if (write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1)) + if (write_socket(csp->cfd, CSUCCEED, strlen(CSUCCEED))) { freez(hdr); return; @@ -1954,7 +2176,7 @@ static void chat(struct client_state *csp) if (FD_ISSET(csp->cfd, &rfds)) { - len = read_socket(csp->cfd, buf, sizeof(buf)); + len = read_socket(csp->cfd, buf, sizeof(buf) - 1); if (len <= 0) { @@ -1985,6 +2207,18 @@ static void chat(struct client_state *csp) { log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host); + if (http->ssl && (fwd->forward_host == NULL)) + { + /* + * Just hang up. We already confirmed the client's CONNECT + * request with status code 200 and unencrypted content is + * no longer welcome. + */ + log_error(LOG_LEVEL_ERROR, + "CONNECT already confirmed. Unable to tell the client about the problem."); + return; + } + rsp = error_response(csp, "connect-failed", errno); if(rsp) @@ -2048,14 +2282,24 @@ static void chat(struct client_state *csp) csp->content_length = (size_t)(csp->iob->eod - csp->iob->cur); } - hdr = sed(server_patterns_light, NULL, csp); + if (JB_ERR_OK != sed(server_patterns_light, NULL, csp)) + { + log_error(LOG_LEVEL_FATAL, "Failed to parse server headers."); + } + hdr = list_to_text(csp->headers); if (hdr == NULL) { /* FIXME Should handle error properly */ log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header"); } + /* + * Shouldn't happen because this was the second sed run + * and tags are only created for the first one. + */ + assert(!crunch_response_triggered(csp, crunchers_all)); + if (write_socket(csp->cfd, hdr, strlen(hdr)) || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length)) { @@ -2108,8 +2352,11 @@ static void chat(struct client_state *csp) int flushed; log_error(LOG_LEVEL_ERROR, "Flushing header and buffers. Stepping back from filtering."); - - hdr = sed(server_patterns, add_server_headers, csp); + if (JB_ERR_OK != sed(server_patterns, add_server_headers, csp)) + { + log_error(LOG_LEVEL_FATAL, "Failed to parse server headers."); + } + hdr = list_to_text(csp->headers); if (hdr == NULL) { /* @@ -2123,6 +2370,18 @@ static void chat(struct client_state *csp) return; } + if (crunch_response_triggered(csp, crunchers_light)) + { + /* + * One of the tags created by a server-header + * tagger triggered a crunch. We already + * delivered the crunch response to the client + * and are done here after cleaning up. + */ + freez(hdr); + return; + } + hdrlen = strlen(hdr); if (write_socket(csp->cfd, hdr, hdrlen) @@ -2203,8 +2462,7 @@ static void chat(struct client_state *csp) { log_error(LOG_LEVEL_ERROR, "Empty server or forwarder response."); log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd); - strcpy(buf, NO_SERVER_DATA_RESPONSE); - write_socket(csp->cfd, buf, strlen(buf)); + write_socket(csp->cfd, NO_SERVER_DATA_RESPONSE, strlen(NO_SERVER_DATA_RESPONSE)); free_http_request(http); return; } @@ -2212,14 +2470,28 @@ static void chat(struct client_state *csp) /* we have now received the entire header. * filter it and send the result to the client */ - - hdr = sed(server_patterns, add_server_headers, csp); + if (JB_ERR_OK != sed(server_patterns, add_server_headers, csp)) + { + log_error(LOG_LEVEL_FATAL, "Failed to parse server headers."); + } + hdr = list_to_text(csp->headers); if (hdr == NULL) { /* FIXME Should handle error properly */ log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header"); } + if (crunch_response_triggered(csp, crunchers_light)) + { + /* + * One of the tags created by a server-header + * tagger triggered a crunch. We already + * delivered the crunch response to the client + * and are done here after cleaning up. + */ + freez(hdr); + return; + } #ifdef FEATURE_KILL_POPUPS /* Start blocking popups if appropriate. */ @@ -2394,13 +2666,17 @@ static int32 server_thread(void *data) void usage(const char *myname) { printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n" -#if !defined(unix) - "Usage: %s [--help] [--version] [configfile]\n" -#else - "Usage: %s [--help] [--version] [--no-daemon] [--pidfile pidfile] [--user user[.group]] [configfile]\n" -#endif - "Aborting.\n", myname); - + "Usage: %s " +#if defined(unix) + "[--chroot] " +#endif /* defined(unix) */ + "[--help] " +#if defined(unix) + "[--no-daemon] [--pidfile pidfile] [--user user[.group]] " +#endif /* defined(unix) */ + "[--version] [configfile]\n" + "Aborting\n", myname); + exit(2); } @@ -2435,7 +2711,14 @@ void initialize_mutexes() * * For example older FreeBSD versions (< 6.x?) * have no gethostbyname_r, but gethostbyname is - * thead safe. + * thread safe. + */ +#if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) + if (!err) err = pthread_mutex_init(&resolver_mutex, 0); +#endif /* !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R) */ + /* + * XXX: should we use a single mutex for + * localtime() and gmtime() as well? */ #ifndef HAVE_GMTIME_R if (!err) err = pthread_mutex_init(&gmtime_mutex, 0); @@ -2445,14 +2728,6 @@ void initialize_mutexes() if (!err) err = pthread_mutex_init(&localtime_mutex, 0); #endif /* ndef HAVE_GMTIME_R */ -#ifndef HAVE_GETHOSTBYADDR_R - if (!err) err = pthread_mutex_init(&gethostbyaddr_mutex, 0); -#endif /* ndef HAVE_GETHOSTBYADDR_R */ - -#ifndef HAVE_GETHOSTBYNAME_R - if (!err) err = pthread_mutex_init(&gethostbyname_mutex, 0); -#endif /* ndef HAVE_GETHOSTBYNAME_R */ - #ifndef HAVE_RANDOM if (!err) err = pthread_mutex_init(&rand_mutex, 0); #endif /* ndef HAVE_RANDOM */ @@ -2524,6 +2799,9 @@ int main(int argc, const char *argv[]) /* * Parse the command line arguments + * + * XXX: simply printing usage information in case of + * invalid arguments isn't particular user friendly. */ while (++argc_pos < argc) { @@ -2604,8 +2882,19 @@ int main(int argc, const char *argv[]) { do_chroot = 1; } - #endif /* defined(unix) */ + + else if (argc_pos + 1 != argc) + { + /* + * This is neither the last command line + * option, nor was it recognized before, + * therefore it must be invalid. + */ + usage(argv[0]); + } + else + #endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */ { configfile = argv[argc_pos]; @@ -2616,24 +2905,30 @@ int main(int argc, const char *argv[]) #if defined(unix) if ( *configfile != '/' ) { - char *abs_file, cwd[1024]; + char cwd[BUFFER_SIZE]; + char *abs_file; + size_t abs_file_size; /* make config-filename absolute here */ - if ( !(getcwd(cwd, sizeof(cwd)))) + if (NULL == getcwd(cwd, sizeof(cwd))) { - perror("get working dir failed"); + perror("failed to get current working directory"); exit( 1 ); } - if (!(basedir = strdup(cwd)) - || (!(abs_file = malloc( strlen( basedir ) + strlen( configfile ) + 5 )))) + /* 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 ); } - strcpy( abs_file, basedir ); - strcat( abs_file, "/" ); - strcat( abs_file, configfile ); + strlcpy(abs_file, basedir, abs_file_size); + strlcat(abs_file, "/", abs_file_size ); + strlcat(abs_file, configfile, abs_file_size); configfile = abs_file; } #endif /* defined unix */ @@ -2808,13 +3103,13 @@ int main(int argc, const char *argv[]) { char putenv_dummy[64]; - strcpy(putenv_dummy, "HOME=/"); + strlcpy(putenv_dummy, "HOME=/", sizeof(putenv_dummy)); if (putenv(putenv_dummy) != 0) { log_error(LOG_LEVEL_FATAL, "Cannot putenv(): HOME"); } - snprintf(putenv_dummy, 64, "USER=%s", pw->pw_name); + snprintf(putenv_dummy, sizeof(putenv_dummy), "USER=%s", pw->pw_name); if (putenv(putenv_dummy) != 0) { log_error(LOG_LEVEL_FATAL, "Cannot putenv(): USER"); @@ -3084,7 +3379,7 @@ static void listen_loop(void) #ifdef FEATURE_ACL if (block_acl(NULL,csp)) { - log_error(LOG_LEVEL_CONNECT, "Connection dropped due to ACL"); + log_error(LOG_LEVEL_CONNECT, "Connection from %s dropped due to ACL", csp->ip_addr_str); close_socket(csp->cfd); freez(csp); continue;