+/*********************************************************************
+ *
+ * Function : crunch_reason
+ *
+ * Description : Translates the crunch reason code into a string.
+ *
+ * Parameters :
+ * 1 : rsp = a http_response
+ *
+ * Returns : A string with the crunch reason or an error description.
+ *
+ *********************************************************************/
+static const char *crunch_reason(const struct http_response *rsp)
+{
+ char * reason = NULL;
+
+ assert(rsp != NULL);
+ if (rsp == NULL)
+ {
+ return "Internal error while searching for crunch reason";
+ }
+
+ switch (rsp->reason)
+ {
+ case RSP_REASON_UNSUPPORTED:
+ reason = "Unsupported HTTP feature";
+ break;
+ case RSP_REASON_BLOCKED:
+ reason = "Blocked";
+ break;
+ case RSP_REASON_UNTRUSTED:
+ reason = "Untrusted";
+ break;
+ case RSP_REASON_REDIRECTED:
+ reason = "Redirected";
+ break;
+ case RSP_REASON_CGI_CALL:
+ reason = "CGI Call";
+ break;
+ case RSP_REASON_NO_SUCH_DOMAIN:
+ reason = "DNS failure";
+ break;
+ case RSP_REASON_FORWARDING_FAILED:
+ reason = "Forwarding failed";
+ break;
+ case RSP_REASON_CONNECT_FAILED:
+ reason = "Connection failure";
+ break;
+ case RSP_REASON_OUT_OF_MEMORY:
+ reason = "Out of memory (may mask other reasons)";
+ break;
+ default:
+ reason = "No reason recorded";
+ break;
+ }
+
+ return reason;
+}
+
+
+/*********************************************************************
+ *
+ * Function : send_crunch_response
+ *
+ * Description : Delivers already prepared response for
+ * intercepted requests, logs the interception
+ * and frees the response.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 1 : rsp = Fully prepared response. Will be freed on exit.
+ *
+ * Returns : Nothing.
+ *
+ *********************************************************************/
+static void send_crunch_response(struct client_state *csp, struct http_response *rsp)
+{
+ const struct http_request *http = csp->http;
+ char status_code[4];
+
+ assert(rsp != NULL);
+ assert(rsp->head != NULL);
+
+ if (rsp == NULL)
+ {
+ /*
+ * Not supposed to happen. If it does
+ * anyway, treat it as an unknown error.
+ */
+ cgi_error_unknown(csp, rsp, RSP_REASON_INTERNAL_ERROR);
+ /* return code doesn't matter */
+ }
+
+ if (rsp == NULL)
+ {
+ /* If rsp is still NULL, we have serious internal problems. */
+ log_error(LOG_LEVEL_FATAL,
+ "NULL response in send_crunch_response and cgi_error_unknown failed as well.");
+ }
+
+ /*
+ * Extract the status code from the actual head
+ * that was send to the client. It is the only
+ * way to get it right for all requests, including
+ * the fixed ones for out-of-memory problems.
+ *
+ * A head starts like this: 'HTTP/1.1 200...'
+ * 0123456789|11
+ * 10
+ */
+ status_code[0] = rsp->head[9];
+ status_code[1] = rsp->head[10];
+ status_code[2] = rsp->head[11];
+ status_code[3] = '\0';
+
+ /* Write the answer to the client */
+ if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+ || write_socket(csp->cfd, rsp->body, rsp->content_length))
+ {
+ /* There is nothing we can do about it. */
+ log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", csp->http->host);
+ }
+
+ /* Log that the request was crunched and why. */
+ log_error(LOG_LEVEL_GPC, "%s%s crunch! (%s)",
+ http->hostport, http->path, crunch_reason(rsp));
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" %s %d",
+ csp->ip_addr_str, http->ocmd, status_code, rsp->content_length);
+
+ /* Clean up and return */
+ if (cgi_error_memory() != rsp)
+ {
+ free_http_response(rsp);
+ }
+ return;
+}
+
+
+#if 0
+/*********************************************************************
+ *
+ * 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.
+ *
+ * XXX: currently not used, see comment in chat().
+ *
+ * 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.
+ *
+ *********************************************************************/
+static 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;
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * 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.
+ *
+ *********************************************************************/
+static int crunch_response_triggered(struct client_state *csp, const struct cruncher crunchers[])
+{
+ struct http_response *rsp = NULL;
+ const struct cruncher *c;
+
+ /*
+ * If CGI request crunching is disabled,
+ * check the CGI dispatcher out of order to
+ * prevent unintentional blocks or redirects.
+ */
+ if (!(csp->config->feature_flags & RUNTIME_FEATURE_CGI_CRUNCHING)
+ && (NULL != (rsp = dispatch_cgi(csp))))
+ {
+ /* Deliver, log and free the interception response. */
+ send_crunch_response(csp, rsp);
+ return TRUE;
+ }
+
+ 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.
+ *
+ *********************************************************************/
+static 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 : change_request_destination
+ *
+ * Description : Parse a (rewritten) request line and regenerate
+ * the http request data.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Forwards the parse_http_request() return code.
+ * Terminates in case of memory problems.
+ *
+ *********************************************************************/
+static jb_err change_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);
+ free_http_request(http);
+ err = parse_http_request(csp->headers->first->str, http, csp);
+ if (JB_ERR_OK != err)
+ {
+ log_error(LOG_LEVEL_ERROR, "Couldn't parse rewritten request: %s.",
+ jb_err_to_string(err));
+ }
+ http->ocmd = strdup(http->cmd); /* XXX: ocmd is a misleading name */
+ if (http->ocmd == NULL)
+ {
+ log_error(LOG_LEVEL_FATAL, "Out of memory copying rewritten HTTP request line");
+ }
+
+ return err;
+}
+
+
+/*********************************************************************
+ *
+ * Function : chat
+ *
+ * Description : Once a connection to the client has been accepted,
+ * this function is called (via serve()) to handle the
+ * main business of the communication. When this
+ * function returns, the caller must close the client
+ * socket handle.
+ *
+ * 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)
+{
+ char buf[BUFFER_SIZE];
+ char *hdr;
+ char *p;
+ char *req = NULL;
+ fd_set rfds;
+ int n;
+ jb_socket maxfd;
+ int server_body;
+ int ms_iis5_hack = 0;
+ size_t byte_count = 0;
+ int forwarded_connect_retries = 0;
+ int max_forwarded_connect_retries = csp->config->forwarded_connect_retries;
+ const struct forward_spec * fwd;
+ struct http_request *http;
+ int len; /* for buffer sizes (and negative error codes) */
+ jb_err err;
+#ifdef FEATURE_KILL_POPUPS
+ int block_popups_now = 0; /* bool, 1==currently blocking popups */
+#endif /* def FEATURE_KILL_POPUPS */
+
+ /* Function that does the content filtering for the current request */
+ filter_function_ptr content_filter = NULL;
+
+ /* Skeleton for HTTP response, if we should intercept the request */
+ struct http_response *rsp;
+
+ /* Temporary copy of the client's headers before they get enlisted in csp->headers */
+ struct list header_list;
+ struct list *headers = &header_list;
+
+ 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!
+ */
+
+ do
+ {
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+
+ if (len <= 0) break; /* error! */
+
+ /*
+ * 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))
+ {
+ return;
+ }
+
+ req = get_header(csp);
+
+ } while ((NULL != req) && ('\0' == *req));
+
+ if ((NULL != req) && ('\0' != *req))
+ {
+ /* Request received. Validate and parse it. */
+
+#if 0
+ /*
+ * XXX: Temporary disabled to prevent problems
+ * with POST requests whose bodies are allowed to
+ * contain NULL bytes. BR#1730105.
+ *
+ * The main purpose of this check is to properly
+ * log stuff like BitTorrent traffic and other junk
+ * that hits public proxies. It's not required for
+ * Privoxy to functions as those requests are discarded
+ * later on anyway.
+ *
+ * It probably should be rewritten to only check
+ * the head of the request. Another option would
+ * be to let all POST requests pass, although that
+ * may not be good enough.
+ */
+ if (request_contains_null_bytes(csp, buf, len))
+ {
+ /* NULL bytes found and dealt with, just hang up. */
+ return;
+ }
+#endif
+
+ /* Does the request line look invalid? */
+ if (client_protocol_is_unsupported(csp, req))
+ {
+ /*
+ * Yes. The request has already been
+ * answered with a error response, the buffers
+ * were freed and we're done with chatting.
+ */
+ return;
+ }
+
+#ifdef FEATURE_FORCE_LOAD
+ /*
+ * 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))
+ {
+ 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 */
+ err = parse_http_request(req, http, csp);
+ if (JB_ERR_OK != err)
+ {
+ log_error(LOG_LEVEL_ERROR, "Couldn't parse request: %s.", jb_err_to_string(err));
+ }
+
+ freez(req);
+ }
+
+ if (http->cmd == NULL)
+ {
+ write_socket(csp->cfd, CHEADER, strlen(CHEADER));
+ /* XXX: Use correct size */
+ 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);
+ return;
+ }
+
+ /* grab the rest of the client's headers */
+ init_list(headers);
+ for (;;)
+ {
+ if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
+ {
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+ if (len <= 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
+ return;
+ }
+
+ /*
+ * 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))
+ {
+ return;
+ }
+ continue;
+ }
+
+ if (p == NULL) break;
+
+ enlist(headers, p);
+ freez(p);
+
+ }
+
+ if (http->host == NULL)
+ {
+ /*
+ * If we still don't know the request destination,
+ * the request is invalid or the client uses
+ * Privoxy without its knowledge.
+ */
+ if (JB_ERR_OK != get_request_destination_elsewhere(csp, headers))
+ {
+ /*
+ * Our attempts to get the request destination
+ * elsewhere failed or Privoxy is configured
+ * to only accept proxy requests.
+ *
+ * An error response has already been send
+ * and we're done here.
+ */
+ return;
+ }
+ }
+
+ /*
+ * 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 (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);
+
+ err = sed(client_patterns, add_client_headers, csp);
+ if (JB_ERR_OK != err)
+ {
+ assert(err == JB_ERR_PARSE);
+ log_error(LOG_LEVEL_FATAL, "Failed to parse client headers");
+ }
+ csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
+
+ /* Check request line for rewrites. */
+ if ((NULL == csp->headers->first->str)
+ || (strcmp(http->cmd, csp->headers->first->str) &&
+ (JB_ERR_OK != change_request_destination(csp))))
+ {
+ /*
+ * A header filter broke the request line - bail out.
+ */
+ write_socket(csp->cfd, MESSED_UP_REQUEST_RESPONSE, strlen(MESSED_UP_REQUEST_RESPONSE));
+ /* XXX: Use correct size */
+ log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request generated\" 500 0", csp->ip_addr_str);
+ log_error(LOG_LEVEL_ERROR, "Invalid request line after applying header filters.");
+
+ free_http_request(http);
+ return;
+ }
+
+ /* 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 */
+ }
+
+ /*
+ * 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 | |
+ * +--------+--------+
+ *