+ 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(const 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_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);
+
+ /* 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=%u).", 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);
+ if (JB_ERR_OK != err)
+ {
+ log_error(LOG_LEVEL_ERROR, "Couldn't parse rewritten request: %s.",
+ jb_err_to_string(err));
+ }
+ 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");
+ }
+ }
+
+ return err;
+}
+
+
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+/*********************************************************************
+ *
+ * Function : server_response_is_complete
+ *
+ * Description : Determines whether we should stop reading
+ * from the server socket.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ * 2 : content_length = Length of content received so far.
+ *
+ * Returns : TRUE if the response is complete,
+ * FALSE otherwise.
+ *
+ *********************************************************************/
+static int server_response_is_complete(struct client_state *csp,
+ unsigned long long content_length)
+{
+ int content_length_known = !!(csp->flags & CSP_FLAG_CONTENT_LENGTH_SET);
+
+ if (!strcmpic(csp->http->gpc, "HEAD"))
+ {
+ /*
+ * "HEAD" implies no body, we are thus expecting
+ * no content. XXX: incomplete "list" of methods?
+ */
+ csp->expected_content_length = 0;
+ content_length_known = TRUE;
+ }
+
+ if (csp->http->status == 304)
+ {
+ /*
+ * Expect no body. XXX: incomplete "list" of status codes?
+ */
+ csp->expected_content_length = 0;
+ content_length_known = TRUE;
+ }
+
+ return (content_length_known && ((0 == csp->expected_content_length)
+ || (csp->expected_content_length <= content_length)));
+}
+
+
+/*********************************************************************
+ *
+ * Function : wait_for_alive_connections
+ *
+ * Description : Waits for alive connections to timeout.
+ *
+ * Parameters : N/A
+ *
+ * Returns : N/A
+ *
+ *********************************************************************/
+static void wait_for_alive_connections(void)
+{
+ int connections_alive = close_unusable_connections();
+
+ while (0 < connections_alive)
+ {
+ log_error(LOG_LEVEL_CONNECT,
+ "Waiting for %d connections to timeout.",
+ connections_alive);
+ sleep(60);
+ connections_alive = close_unusable_connections();
+ }
+
+ log_error(LOG_LEVEL_CONNECT, "No connections to wait for left.");
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : save_connection_destination
+ *
+ * Description : Remembers a connection for reuse later on.
+ *
+ * Parameters :
+ * 1 : sfd = Open socket to remember.
+ * 2 : http = The destination for the connection.
+ * 3 : fwd = The forwarder settings used.
+ * 3 : server_connection = storage.
+ *
+ * Returns : void
+ *
+ *********************************************************************/
+void save_connection_destination(jb_socket sfd,
+ const struct http_request *http,
+ const struct forward_spec *fwd,
+ struct reusable_connection *server_connection)
+{
+ assert(sfd != JB_INVALID_SOCKET);
+ assert(NULL != http->host);
+ server_connection->host = strdup(http->host);
+ if (NULL == server_connection->host)
+ {
+ log_error(LOG_LEVEL_FATAL, "Out of memory saving socket.");
+ }
+ server_connection->port = http->port;
+
+ assert(NULL != fwd);
+ assert(server_connection->gateway_host == NULL);
+ assert(server_connection->gateway_port == 0);
+ assert(server_connection->forwarder_type == 0);
+ assert(server_connection->forward_host == NULL);
+ assert(server_connection->forward_port == 0);
+
+ 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.");
+ }
+ }
+ else
+ {
+ server_connection->gateway_host = NULL;
+ }
+ server_connection->gateway_port = fwd->gateway_port;
+
+ 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.");
+ }
+ }
+ else
+ {
+ server_connection->forward_host = NULL;
+ }
+ server_connection->forward_port = fwd->forward_port;
+}
+#endif /* FEATURE_CONNECTION_KEEP_ALIVE */
+
+
+/*********************************************************************
+ *
+ * Function : mark_server_socket_tainted
+ *
+ * Description : Makes sure we don't reuse a server socket
+ * (if we didn't read everything the server sent
+ * us reusing the socket would lead to garbage).
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : void.
+ *
+ *********************************************************************/
+static void mark_server_socket_tainted(struct client_state *csp)
+{
+ if ((csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE))
+ {
+ log_error(LOG_LEVEL_CONNECT, "Unsetting keep-alive flag.");
+ csp->flags &= ~CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE;
+ }
+}
+
+/*********************************************************************
+ *
+ * Function : get_request_line
+ *
+ * Description : Read the client request line.
+ *
+ * Parameters :
+ * 1 : csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns : Pointer to request line or NULL in case of errors.
+ *
+ *********************************************************************/
+static char *get_request_line(struct client_state *csp)
+{
+ char buf[BUFFER_SIZE];
+ char *request_line = NULL;
+ int len;
+
+ memset(buf, 0, sizeof(buf));
+
+ do
+ {
+ if (!data_is_available(csp->cfd, csp->config->socket_timeout))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Stopped waiting for the request line.");
+ write_socket(csp->cfd, CLIENT_CONNECTION_TIMEOUT_RESPONSE,
+ strlen(CLIENT_CONNECTION_TIMEOUT_RESPONSE));
+ return NULL;
+ }
+
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+
+ if (len <= 0) return NULL;
+
+ /*
+ * 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 NULL;
+ }
+
+ request_line = get_header(csp->iob);
+
+ } while ((NULL != request_line) && ('\0' == *request_line));
+
+ return request_line;
+
+}
+
+
+/*********************************************************************
+ *
+ * Function : receive_client_request
+ *
+ * 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...)
+ *
+ * Returns : JB_ERR_OK, JB_ERR_PARSE or JB_ERR_MEMORY
+ *
+ *********************************************************************/
+static jb_err receive_client_request(struct client_state *csp)
+{
+ char buf[BUFFER_SIZE];
+ char *p;
+ char *req = NULL;
+ struct http_request *http;
+ int len;
+ jb_err err;
+
+ /* 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));
+
+ req = get_request_line(csp);
+ if (req == NULL)
+ {
+ return JB_ERR_PARSE;
+ }
+ assert(*req != '\0');
+
+ if (client_protocol_is_unsupported(csp, req))
+ {
+ return JB_ERR_PARSE;
+ }
+
+#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);
+ freez(req);
+ if (JB_ERR_OK != err)
+ {
+ 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,
+ "Couldn't parse request line received from %s: %s",
+ csp->ip_addr_str, jb_err_to_string(err));
+
+ free_http_request(http);
+ return JB_ERR_PARSE;
+ }
+
+ /* grab the rest of the client's headers */
+ init_list(headers);
+ for (;;)
+ {
+ p = get_header(csp->iob);
+
+ if (p == NULL)
+ {
+ /* There are no additional headers to read. */
+ break;
+ }
+
+ if (*p == '\0')
+ {
+ /*
+ * We didn't receive a complete header
+ * line yet, get the rest of it.
+ */
+ if (!data_is_available(csp->cfd, csp->config->socket_timeout))
+ {
+ log_error(LOG_LEVEL_ERROR,
+ "Stopped grabbing the client headers.");
+ destroy_list(headers);
+ return JB_ERR_PARSE;
+ }
+
+ len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
+ if (len <= 0)
+ {
+ log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
+ destroy_list(headers);
+ return JB_ERR_PARSE;
+ }
+
+ if (add_to_iob(csp, buf, len))
+ {
+ /*
+ * If there is no memory left for buffering the
+ * request, there is nothing we can do but hang up
+ */
+ destroy_list(headers);
+ return JB_ERR_MEMORY;
+ }
+ }
+ else
+ {
+ /*
+ * We were able to read a complete
+ * header and can finaly enlist it.
+ */
+ 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 JB_ERR_PARSE;
+ }
+ }
+
+ /*
+ * 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 */
+ {
+ get_url_actions(csp, http);
+ }
+
+ /*