Add a client-body-tagger action
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index ff390f1..19b2f49 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -5,7 +5,7 @@
  * Purpose     :  Main file.  Contains main() method, main loop, and
  *                the main connection-handling function.
  *
- * Copyright   :  Written by and Copyright (C) 2001-2021 the
+ * Copyright   :  Written by and Copyright (C) 2001-2022 the
  *                Privoxy team. https://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -127,6 +127,10 @@ struct file_list     files[1];
 #ifdef FEATURE_STATISTICS
 int urls_read     = 0;     /* total nr of urls read inc rejected */
 int urls_rejected = 0;     /* total nr of urls rejected */
+#ifdef MUTEX_LOCKS_AVAILABLE
+unsigned long long number_of_requests_received = 0;
+unsigned long long number_of_requests_blocked = 0;
+#endif
 #endif /* def FEATURE_STATISTICS */
 
 #ifdef FEATURE_GRACEFUL_TERMINATION
@@ -191,9 +195,12 @@ privoxy_mutex_t external_filter_mutex;
 #ifdef FEATURE_CLIENT_TAGS
 privoxy_mutex_t client_tags_mutex;
 #endif
+#ifdef FEATURE_STATISTICS
+privoxy_mutex_t block_statistics_mutex;
+#endif
 #ifdef FEATURE_EXTENDED_STATISTICS
 privoxy_mutex_t filter_statistics_mutex;
-privoxy_mutex_t block_statistics_mutex;
+privoxy_mutex_t block_reason_statistics_mutex;
 #endif
 
 #if !defined(HAVE_GETHOSTBYADDR_R) || !defined(HAVE_GETHOSTBYNAME_R)
@@ -228,64 +235,71 @@ static const char CHEADER[] =
    "HTTP/1.1 400 Invalid header received from client\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
-   "Invalid header received from client.\r\n";
+   "Invalid header received from client.\n";
 
 static const char FTP_RESPONSE[] =
    "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";
+   "Invalid request. Privoxy doesn't support FTP.\n";
 
 static const char GOPHER_RESPONSE[] =
    "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";
+   "Invalid request. Privoxy doesn't support gopher.\n";
 
 /* XXX: should be a template */
 static const char MISSING_DESTINATION_RESPONSE[] =
    "HTTP/1.1 400 Bad request received from client\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";
+   "Bad request. Privoxy was unable to extract the destination.\n";
 
 /* XXX: should be a template */
 static const char INVALID_SERVER_HEADERS_RESPONSE[] =
    "HTTP/1.1 502 Server or forwarder response invalid\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
-   "Bad response. The server or forwarder response doesn't look like HTTP.\r\n";
+   "Bad response. The server or forwarder response doesn't look like HTTP.\n";
 
 /* XXX: should be a template */
 static const char MESSED_UP_REQUEST_RESPONSE[] =
    "HTTP/1.1 400 Malformed request after rewriting\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
-   "Bad request. Messed up with header filters.\r\n";
+   "Bad request. Messed up with header filters.\n";
 
 static const char TOO_MANY_CONNECTIONS_RESPONSE[] =
    "HTTP/1.1 503 Too many open connections\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
-   "Maximum number of open connections reached.\r\n";
+   "Maximum number of open connections reached.\n";
 
 static const char CLIENT_CONNECTION_TIMEOUT_RESPONSE[] =
    "HTTP/1.1 504 Connection timeout\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";
+   "The connection timed out because the client request didn't arrive in time.\n";
 
 static const char CLIENT_BODY_PARSE_ERROR_RESPONSE[] =
    "HTTP/1.1 400 Failed reading client body\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";
+   "Failed parsing or buffering the chunk-encoded client body.\n";
+
+static const char CLIENT_BODY_BUFFER_ERROR_RESPONSE[] =
+   "HTTP/1.1 400 Failed reading client body\r\n"
+   "Content-Type: text/plain\r\n"
+   "Connection: close\r\n\r\n"
+   "Failed to buffer the client body to apply content filters.\n"
+   "Could be caused by a socket timeout\n";
 
 static const char UNSUPPORTED_CLIENT_EXPECTATION_ERROR_RESPONSE[] =
    "HTTP/1.1 417 Expecting too much\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
-   "Privoxy detected an unsupported Expect header value.\r\n";
+   "Privoxy detected an unsupported Expect header value.\n";
 
 /* A function to crunch a response */
 typedef struct http_response *(*crunch_func_ptr)(struct client_state *);
@@ -376,7 +390,8 @@ static void sig_handler(int the_signal)
           * We shouldn't be here, unless we catch signals
           * in main() that we can't handle here!
           */
-         log_error(LOG_LEVEL_FATAL, "sig_handler: exiting on unexpected signal %d", the_signal);
+         log_error(LOG_LEVEL_FATAL,
+            "sig_handler: exiting on unexpected signal %d", the_signal);
    }
    return;
 
@@ -944,6 +959,11 @@ static int crunch_response_triggered(struct client_state *csp, const struct crun
 #ifdef FEATURE_STATISTICS
             if (c->flags & CF_COUNT_AS_REJECT)
             {
+#ifdef MUTEX_LOCKS_AVAILABLE
+               privoxy_mutex_lock(&block_statistics_mutex);
+               number_of_requests_blocked++;
+               privoxy_mutex_unlock(&block_statistics_mutex);
+#endif
                csp->flags |= CSP_FLAG_REJECTED;
             }
 #endif /* def FEATURE_STATISTICS */
@@ -994,12 +1014,35 @@ static void build_request_line(struct client_state *csp, const struct forward_sp
     * Rebuild the request line.
     */
    freez(*request_line);
-   *request_line = strdup(http->gpc);
-   string_append(request_line, " ");
+#ifdef FEATURE_HTTPS_INSPECTION
+   if (fwd != NULL && fwd->forward_host &&
+       fwd->type != FORWARD_WEBSERVER && client_use_ssl(csp))
+   {
+      *request_line = strdup("CONNECT ");
+   }
+   else
+#endif
+   {
+      *request_line = strdup(http->gpc);
+      string_append(request_line, " ");
+   }
 
    if (fwd != NULL && fwd->forward_host && fwd->type != FORWARD_WEBSERVER)
    {
-      string_append(request_line, http->url);
+#ifdef FEATURE_HTTPS_INSPECTION
+      if (client_use_ssl(csp))
+      {
+         char port_string[10];
+
+         string_append(request_line, http->host);
+         snprintf(port_string, sizeof(port_string), ":%d", http->port);
+         string_append(request_line, port_string);
+      }
+      else
+#endif
+      {
+         string_append(request_line, http->url);
+      }
    }
    else
    {
@@ -1152,10 +1195,10 @@ static void wait_for_alive_connections(void)
  * Returns     : void
  *
  *********************************************************************/
-void save_connection_destination(jb_socket sfd,
-                                 const struct http_request *http,
-                                 const struct forward_spec *fwd,
-                                 struct reusable_connection *server_connection)
+static 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);
@@ -1502,6 +1545,12 @@ static enum chunk_status chunked_body_is_complete(struct iob *iob, size_t *lengt
       /* Move beyond the chunkdata. */
       p += 2 + chunksize;
 
+      /* Make sure we're still within the buffer and have two bytes left */
+      if (p + 2 > iob->eod)
+      {
+         return CHUNK_STATUS_MISSING_DATA;
+      }
+
       /* There should be another "\r\n" to skip */
       if (memcmp(p, "\r\n", 2))
       {
@@ -1552,7 +1601,8 @@ static jb_err receive_chunked_client_request_body(struct client_state *csp)
       len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
       if (len <= 0)
       {
-         log_error(LOG_LEVEL_ERROR, "Read the client body failed: %E");
+         log_error(LOG_LEVEL_ERROR,
+            "Reading the client body failed: %E");
          break;
       }
       if (add_to_iob(csp->client_iob, csp->config->buffer_limit, buf, len))
@@ -1782,6 +1832,19 @@ static jb_err receive_client_request(struct client_state *csp)
       free_http_request(http);
       return JB_ERR_PARSE;
    }
+   if (http->ssl && strcmpic(http->gpc, "CONNECT"))
+   {
+      write_socket_delayed(csp->cfd, CHEADER, strlen(CHEADER),
+         get_write_delay(csp));
+      /* XXX: Use correct size */
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0",
+         csp->ip_addr_str);
+      log_error(LOG_LEVEL_ERROR, "Client %s tried to send a https "
+         "URL without sending a CONNECT request first",
+         csp->ip_addr_str);
+      free_http_request(http);
+      return JB_ERR_PARSE;
+   }
 
    /* grab the rest of the client's headers */
    init_list(headers);
@@ -1804,7 +1867,8 @@ static jb_err receive_client_request(struct client_state *csp)
          if (!data_is_available(csp->cfd, csp->config->socket_timeout))
          {
             log_error(LOG_LEVEL_ERROR,
-               "Stopped grabbing the client headers.");
+               "Client headers did not arrive in time. Timeout: %d",
+               csp->config->socket_timeout);
             destroy_list(headers);
             return JB_ERR_PARSE;
          }
@@ -1860,7 +1924,7 @@ static jb_err receive_client_request(struct client_state *csp)
           * elsewhere failed or Privoxy is configured
           * to only accept proxy requests.
           *
-          * An error response has already been send
+          * An error response has already been sent
           * and we're done here.
           */
          return JB_ERR_PARSE;
@@ -2032,7 +2096,7 @@ static int read_http_request_body(struct client_state *csp)
       size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf);
 
       log_error(LOG_LEVEL_CONNECT,
-         "Waiting for up to %d bytes of request body from the client.",
+         "Waiting for up to %lu bytes of request body from the client.",
          max_bytes_to_read);
       len = read_socket(csp->cfd, buf, (int)max_bytes_to_read);
       if (len <= -1)
@@ -2049,12 +2113,13 @@ static int read_http_request_body(struct client_state *csp)
 
    if (to_read != 0)
    {
-      log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes",
-         csp->expected_client_content_length);
+      log_error(LOG_LEVEL_CONNECT,
+         "Not enough request body has been read: expected %lu more bytes",
+         to_read);
       return 1;
    }
-   log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read",
-      csp->expected_client_content_length);
+   log_error(LOG_LEVEL_CONNECT,
+      "The last %d bytes of the request body have been read", len);
    return 0;
 }
 
@@ -2108,7 +2173,7 @@ static int update_client_headers(struct client_state *csp, size_t new_content_le
 
 /*********************************************************************
  *
- * Function    : can_filter_request_body
+ * Function    : can_buffer_request_body
  *
  * Description : Checks if the current request body can be stored in
  *               the client_iob without hitting buffer limit.
@@ -2120,14 +2185,14 @@ static int update_client_headers(struct client_state *csp, size_t new_content_le
  *               FALSE otherwise.
  *
  *********************************************************************/
-static int can_filter_request_body(const struct client_state *csp)
+static int can_buffer_request_body(const struct client_state *csp)
 {
    if (!can_add_to_iob(csp->client_iob, csp->config->buffer_limit,
                        csp->expected_client_content_length))
    {
       log_error(LOG_LEVEL_INFO,
-         "Not filtering request body from %s: buffer limit %d will be exceeded "
-         "(content length %d)", csp->ip_addr_str, csp->config->buffer_limit,
+         "Not filtering request body from %s: buffer limit %lu will be exceeded "
+         "(content length %llu)", csp->ip_addr_str, csp->config->buffer_limit,
          csp->expected_client_content_length);
       return FALSE;
    }
@@ -2145,39 +2210,13 @@ static int can_filter_request_body(const struct client_state *csp)
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
  *
- * Returns     :  0 on success, anything else is an error.
+ * Returns     :  0 on success, 1 on error, 2 if the request got crunched.
  *
  *********************************************************************/
 static int send_http_request(struct client_state *csp)
 {
    char *hdr;
    int write_failure;
-   const char *to_send;
-   size_t to_send_len;
-   int filter_client_body = csp->expected_client_content_length != 0 &&
-      client_body_filters_enabled(csp->action) && can_filter_request_body(csp);
-
-   if (filter_client_body)
-   {
-      if (read_http_request_body(csp))
-      {
-         return 1;
-      }
-      to_send_len = csp->expected_client_content_length;
-      to_send = execute_client_body_filters(csp, &to_send_len);
-      if (to_send == NULL)
-      {
-         /* just flush client_iob */
-         filter_client_body = FALSE;
-      }
-      else if (to_send_len != csp->expected_client_content_length &&
-         update_client_headers(csp, to_send_len))
-      {
-         log_error(LOG_LEVEL_HEADER, "Error updating client headers");
-         return 1;
-      }
-      csp->expected_client_content_length = 0;
-   }
 
    hdr = list_to_text(csp->headers);
    if (hdr == NULL)
@@ -2201,18 +2240,7 @@ static int send_http_request(struct client_state *csp)
       return 1;
    }
 
-   if (filter_client_body)
-   {
-      write_failure = 0 != write_socket(csp->server_connection.sfd, to_send, to_send_len);
-      freez(to_send);
-      if (write_failure)
-      {
-         log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s: %E",
-            csp->http->hostport);
-         return 1;
-      }
-   }
-
+   /* XXX: Filtered data is not sent if there's a pipelined request? */
    if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
       && (flush_iob(csp->server_connection.sfd, csp->client_iob, 0) < 0))
    {
@@ -2259,13 +2287,15 @@ static int read_https_request_body(struct client_state *csp)
       size_t max_bytes_to_read = to_read < sizeof(buf) ? to_read : sizeof(buf);
 
       log_error(LOG_LEVEL_CONNECT,
-         "Waiting for up to %d bytes of request body from the client.",
+         "Buffering encrypted client body. Prepared to read up to %lu bytes.",
          max_bytes_to_read);
       len = ssl_recv_data(&(csp->ssl_client_attr), buf,
          (unsigned)max_bytes_to_read);
       if (len <= 0)
       {
-         log_error(LOG_LEVEL_CONNECT, "Failed receiving request body from %s", csp->ip_addr_str);
+         log_error(LOG_LEVEL_CONNECT,
+            "Did not receive the whole encrypted request body from %s",
+            csp->ip_addr_str);
          return 1;
       }
       if (add_to_iob(csp->client_iob, csp->config->buffer_limit, (char *)buf, len))
@@ -2277,11 +2307,14 @@ static int read_https_request_body(struct client_state *csp)
 
    if (to_read != 0)
    {
-      log_error(LOG_LEVEL_CONNECT, "Not enough request body has been read: expected %d more bytes", to_read);
+      log_error(LOG_LEVEL_CONNECT,
+         "Not enough encrypted request body has been read: expected %lu more bytes",
+         to_read);
       return 1;
    }
 
-   log_error(LOG_LEVEL_CONNECT, "The last %d bytes of the request body have been read",
+   log_error(LOG_LEVEL_CONNECT,
+      "The last %llu bytes of the encrypted request body have been read",
       csp->expected_client_content_length);
    return 0;
 }
@@ -2304,8 +2337,9 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp)
 {
    int content_length_known = csp->expected_client_content_length != 0;
 
-   while (is_ssl_pending(&(csp->ssl_client_attr))
-      || (content_length_known && csp->expected_client_content_length != 0))
+   while ((content_length_known && csp->expected_client_content_length != 0) ||
+      (is_ssl_pending(&(csp->ssl_client_attr)) ||
+            data_is_available(csp->cfd, csp->config->socket_timeout)))
    {
       unsigned char buf[BUFFER_SIZE];
       int len;
@@ -2316,7 +2350,7 @@ static int receive_and_send_encrypted_post_data(struct client_state *csp)
          max_bytes_to_read = (int)csp->expected_client_content_length;
       }
       log_error(LOG_LEVEL_CONNECT,
-         "Waiting for up to %d bytes of request body from the client.",
+         "Prepared to read up to %d bytes of encrypted request body from the client.",
          max_bytes_to_read);
       len = ssl_recv_data(&(csp->ssl_client_attr), buf,
          (unsigned)max_bytes_to_read);
@@ -2375,32 +2409,6 @@ static int send_https_request(struct client_state *csp)
    char *hdr;
    int ret;
    long flushed = 0;
-   const char *to_send;
-   size_t to_send_len;
-   int filter_client_body = csp->expected_client_content_length != 0 &&
-      client_body_filters_enabled(csp->action) && can_filter_request_body(csp);
-
-   if (filter_client_body)
-   {
-      if (read_https_request_body(csp))
-      {
-         return 1;
-      }
-      to_send_len = csp->expected_client_content_length;
-      to_send = execute_client_body_filters(csp, &to_send_len);
-      if (to_send == NULL)
-      {
-         /* just flush client_iob */
-         filter_client_body = FALSE;
-      }
-      else if (to_send_len != csp->expected_client_content_length &&
-         update_client_headers(csp, to_send_len))
-      {
-         log_error(LOG_LEVEL_HEADER, "Error updating client headers");
-         return 1;
-      }
-      csp->expected_client_content_length = 0;
-   }
 
    hdr = list_to_text(csp->https_headers);
    if (hdr == NULL)
@@ -2427,18 +2435,7 @@ static int send_https_request(struct client_state *csp)
       return 1;
    }
 
-   if (filter_client_body)
-   {
-      ret = ssl_send_data(&(csp->ssl_server_attr), (const unsigned char *)to_send, to_send_len);
-      freez(to_send);
-      if (ret < 0)
-      {
-         log_error(LOG_LEVEL_CONNECT, "Failed sending filtered request body to: %s",
-            csp->http->hostport);
-         return 1;
-      }
-   }
-
+   /* XXX: Client body isn't sent if there's pipelined data? */
    if (((csp->flags & CSP_FLAG_PIPELINED_REQUEST_WAITING) == 0)
       && ((flushed = ssl_flush_socket(&(csp->ssl_server_attr),
             csp->client_iob)) < 0))
@@ -2486,9 +2483,10 @@ static int send_https_request(struct client_state *csp)
 
 /*********************************************************************
  *
- * Function    :  receive_encrypted_request
+ * Function    :  receive_encrypted_request_headers
  *
- * Description :  Receives an encrypted request.
+ * Description :  Receives the encrypted request headers when
+ *                https-inspecting.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2497,7 +2495,7 @@ static int send_https_request(struct client_state *csp)
  *                JB_ERR_PARSE or JB_ERR_MEMORY otherwise
  *
  *********************************************************************/
-static jb_err receive_encrypted_request(struct client_state *csp)
+static jb_err receive_encrypted_request_headers(struct client_state *csp)
 {
    char buf[BUFFER_SIZE];
    int len;
@@ -2505,7 +2503,7 @@ static jb_err receive_encrypted_request(struct client_state *csp)
 
    do
    {
-      log_error(LOG_LEVEL_HEADER, "Reading encrypted headers");
+      log_error(LOG_LEVEL_HEADER, "Waiting for encrypted client headers");
       if (!is_ssl_pending(&(csp->ssl_client_attr)) &&
           !data_is_available(csp->cfd, csp->config->socket_timeout))
       {
@@ -2556,6 +2554,7 @@ static jb_err change_encrypted_request_destination(struct client_state *csp)
 {
    jb_err err;
    char *original_host = csp->http->host;
+   int original_port = csp->http->port;
 
    log_error(LOG_LEVEL_REDIRECTS, "Rewrite detected: %s",
       csp->https_headers->first->str);
@@ -2572,22 +2571,32 @@ static jb_err change_encrypted_request_destination(struct client_state *csp)
 
    if (csp->http->host == NULL)
    {
+      char port_string[10];
       /*
        * The rewritten request line did not specify a host
        * which means we can use the original host specified
        * by the client.
        */
       csp->http->host = original_host;
+      csp->http->port = original_port;
       log_error(LOG_LEVEL_REDIRECTS, "Keeping the original host: %s",
          csp->http->host);
       /*
        * If the rewritten request line didn't contain a host
        * it also didn't contain a port so we can reuse the host
-       * and set the port to 443.
+       * port.
        */
       freez(csp->http->hostport);
       csp->http->hostport = strdup_or_die(csp->http->host);
-      csp->http->port = 443;
+      snprintf(port_string, sizeof(port_string), ":%d", original_port);
+      err = string_append(&csp->http->hostport, port_string);
+      if (err != JB_ERR_OK)
+      {
+         log_error(LOG_LEVEL_ERROR, "Failed to rebuild hostport: %s.",
+            jb_err_to_string(err));
+         return err;
+      }
+
       /*
        * While the request line didn't mention it,
        * we're https-inspecting and want to speak TLS
@@ -2630,9 +2639,10 @@ static jb_err change_encrypted_request_destination(struct client_state *csp)
 
 /*********************************************************************
  *
- * Function    :  process_encrypted_request
+ * Function    :  process_encrypted_request_headers
  *
- * Description :  Receives and parses an encrypted request.
+ * Description :  Receives and parses the encrypted headers send
+ *                by the client when https-inspecting.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -2641,7 +2651,7 @@ static jb_err change_encrypted_request_destination(struct client_state *csp)
  *                JB_ERR_PARSE or JB_ERR_MEMORY otherwise
  *
  *********************************************************************/
-static jb_err process_encrypted_request(struct client_state *csp)
+static jb_err process_encrypted_request_headers(struct client_state *csp)
 {
    char *p;
    char *request_line;
@@ -2658,7 +2668,7 @@ static jb_err process_encrypted_request(struct client_state *csp)
       csp->flags |= CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
    }
 #endif
-   err = receive_encrypted_request(csp);
+   err = receive_encrypted_request_headers(csp);
    if (err != JB_ERR_OK)
    {
       if (csp->client_iob->cur == NULL ||
@@ -2753,6 +2763,8 @@ static jb_err process_encrypted_request(struct client_state *csp)
          "Failed to get the encrypted request destination");
       ssl_send_data_delayed(&(csp->ssl_client_attr),
          (const unsigned char *)CHEADER, strlen(CHEADER), get_write_delay(csp));
+      destroy_list(headers);
+
       return JB_ERR_PARSE;
    }
 
@@ -2825,8 +2837,7 @@ static jb_err process_encrypted_request(struct client_state *csp)
       return JB_ERR_PARSE;
    }
 
-   log_error(LOG_LEVEL_HEADER, "Encrypted request processed");
-   log_applied_actions(csp->action);
+   log_error(LOG_LEVEL_HEADER, "Encrypted request headers processed");
    log_error(LOG_LEVEL_REQUEST, "https://%s%s", csp->http->hostport,
       csp->http->path);
 
@@ -2892,11 +2903,18 @@ static void continue_https_chat(struct client_state *csp)
 {
    const struct forward_spec *fwd;
 
-   if (JB_ERR_OK != process_encrypted_request(csp))
+   if (JB_ERR_OK != process_encrypted_request_headers(csp))
    {
+      csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
       return;
    }
 
+#if defined(FEATURE_STATISTICS) && defined(MUTEX_LOCKS_AVAILABLE)
+   privoxy_mutex_lock(&block_statistics_mutex);
+   number_of_requests_received++;
+   privoxy_mutex_unlock(&block_statistics_mutex);
+#endif
+
    csp->requests_received_total++;
 
    /*
@@ -2920,6 +2938,46 @@ static void continue_https_chat(struct client_state *csp)
    }
    assert(csp->server_connection.sfd != JB_INVALID_SOCKET);
 
+   if (csp->expected_client_content_length != 0 &&
+      (client_body_filters_enabled(csp->action) ||
+       client_body_taggers_enabled(csp->action)) &&
+      can_buffer_request_body(csp))
+   {
+      int content_modified;
+
+      if (read_https_request_body(csp))
+      {
+         /* XXX: handle */
+         return;
+      }
+      if (client_body_taggers_enabled(csp->action))
+      {
+         execute_client_body_taggers(csp, csp->expected_client_content_length);
+         if (crunch_response_triggered(csp, crunchers_all))
+         {
+            /*
+             * Yes. The client got the crunch response and we're done here.
+             */
+            return;
+         }
+      }
+      if (client_body_filters_enabled(csp->action))
+      {
+         size_t modified_content_length = csp->expected_client_content_length;
+         content_modified = execute_client_body_filters(csp,
+            &modified_content_length);
+         if ((content_modified == 1) &&
+            (modified_content_length != csp->expected_client_content_length) &&
+            update_client_headers(csp, modified_content_length))
+         {
+            /* XXX: Send error response */
+            log_error(LOG_LEVEL_HEADER, "Error updating client headers");
+            return;
+         }
+      }
+      csp->expected_client_content_length = 0;
+   }
+
    fwd = forward_url(csp, csp->http);
    if (!connection_destination_matches(&csp->server_connection, csp->http, fwd))
    {
@@ -2931,6 +2989,8 @@ static void continue_https_chat(struct client_state *csp)
       return;
    }
 
+   log_applied_actions(csp->action);
+
    log_error(LOG_LEVEL_CONNECT,
       "Reusing server socket %d connected to %s. Requests already sent: %u.",
       csp->server_connection.sfd, csp->server_connection.host,
@@ -2949,6 +3009,7 @@ static void continue_https_chat(struct client_state *csp)
          csp->cfd);
       return;
    }
+   csp->server_connection.request_sent = time(NULL);
    csp->server_connection.requests_sent_total++;
    handle_established_connection(csp);
    freez(csp->receive_buffer);
@@ -2962,7 +3023,8 @@ static void continue_https_chat(struct client_state *csp)
  * Function    :  handle_established_connection
  *
  * Description :  Shuffle data between client and server once the
- *                connection has been established.
+ *                connection has been established and the request
+ *                has been sent.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -3038,6 +3100,28 @@ static void handle_established_connection(struct client_state *csp)
 
    for (;;)
    {
+#ifdef FEATURE_HTTPS_INSPECTION
+      if (server_use_ssl(csp) && is_ssl_pending(&(csp->ssl_server_attr)))
+      {
+         /*
+          * It's possible that the TLS library already consumed all the
+          * data the server intends to send. If that happens poll() and
+          * select() will no longer see the data as available so we have
+          * to skip the calls.
+          */
+         goto server_wants_to_talk;
+      }
+      if (watch_client_socket && client_use_ssl(csp) &&
+         is_ssl_pending(&(csp->ssl_client_attr)))
+      {
+         /*
+          * The TLS libray may also consume all of the remaining data
+          * from the client when we're shuffling the data from an
+          * unbuffered request body to the server.
+          */
+         goto client_wants_to_talk;
+      }
+#endif
 #ifndef HAVE_POLL
       FD_ZERO(&rfds);
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
@@ -3124,7 +3208,7 @@ static void handle_established_connection(struct client_state *csp)
       n = select((int)maxfd + 1, &rfds, NULL, NULL, &timeout);
 #endif /* def HAVE_POLL */
 
-      /*server or client not responding in timeout */
+      /* Server or client not responding in timeout */
       if (n == 0)
       {
          log_error(LOG_LEVEL_CONNECT, "Socket timeout %d reached: %s",
@@ -3178,7 +3262,13 @@ static void handle_established_connection(struct client_state *csp)
       if (FD_ISSET(csp->cfd, &rfds))
 #endif /* def HAVE_POLL*/
       {
-         int max_bytes_to_read = (int)csp->receive_buffer_size;
+         int max_bytes_to_read;
+
+#ifdef FEATURE_HTTPS_INSPECTION
+         client_wants_to_talk:
+#endif
+
+         max_bytes_to_read = (int)csp->receive_buffer_size;
 
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
          if ((csp->flags & CSP_FLAG_CLIENT_REQUEST_COMPLETELY_READ))
@@ -3311,6 +3401,9 @@ static void handle_established_connection(struct client_state *csp)
       if (FD_ISSET(csp->server_connection.sfd, &rfds))
 #endif /* HAVE_POLL */
       {
+#ifdef FEATURE_HTTPS_INSPECTION
+         server_wants_to_talk:
+#endif
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
          /*
           * If we are buffering content, we don't want to eat up to
@@ -3515,8 +3608,8 @@ static void handle_established_connection(struct client_state *csp)
                               (const unsigned char *) ((p != NULL) ? p : csp->iob->cur),
                               csp->content_length, get_write_delay(csp)) < 0))
                      {
-                        log_error(LOG_LEVEL_ERROR, "write modified content to "
-                           "client over TLS/SSL failed");
+                        log_error(LOG_LEVEL_ERROR,
+                           "Failed to send the modified content to the client over TLS");
                         freez(hdr);
                         freez(p);
                         mark_server_socket_tainted(csp);
@@ -3547,8 +3640,8 @@ static void handle_established_connection(struct client_state *csp)
             }
 
             /*
-             * This is NOT the body, so
-             * Let's pretend the server just sent us a blank line.
+             * This is not the body, so let's pretend the server just sent
+             * us a blank line.
              */
             snprintf(csp->receive_buffer, csp->receive_buffer_size, "\r\n");
             len = (int)strlen(csp->receive_buffer);
@@ -3841,6 +3934,18 @@ static void handle_established_connection(struct client_state *csp)
                return;
             }
 
+            /*
+             * Disable redirect checkers, so that they will be only run
+             * again if the user also enables them through tags.
+             *
+             * From a performance point of view it doesn't matter,
+             * but it prevents duplicated log messages.
+             */
+#ifdef FEATURE_FAST_REDIRECTS
+            csp->action->flags &= ~ACTION_FAST_REDIRECTS;
+#endif
+            csp->action->flags &= ~ACTION_REDIRECT;
+
             /*
              * We have now received the entire server header,
              * filter it and send the result to the client
@@ -3928,8 +4033,8 @@ static void handle_established_connection(struct client_state *csp)
                   if ((ssl_send_data_delayed(&(csp->ssl_client_attr),
                           (const unsigned char *)hdr, strlen(hdr),
                           get_write_delay(csp)) < 0)
-                     || (len = ssl_flush_socket(&(csp->ssl_client_attr),
-                            csp->iob) < 0))
+                     || ((len = ssl_flush_socket(&(csp->ssl_client_attr),
+                            csp->iob)) < 0))
                   {
                      log_error(LOG_LEVEL_CONNECT, "Write header to client failed");
 
@@ -4091,14 +4196,18 @@ static void chat(struct client_state *csp)
    {
       return;
    }
+
+#if defined(FEATURE_STATISTICS) && defined(MUTEX_LOCKS_AVAILABLE)
+   privoxy_mutex_lock(&block_statistics_mutex);
+   number_of_requests_received++;
+   privoxy_mutex_unlock(&block_statistics_mutex);
+#endif
+
    if (parse_client_request(csp) != JB_ERR_OK)
    {
       return;
    }
 
-   /* decide how to route the HTTP request */
-   fwd = forward_url(csp, http);
-
 #ifdef FEATURE_HTTPS_INSPECTION
    /*
     * Setting flags to use old solution with SSL tunnel and to disable
@@ -4201,10 +4310,6 @@ static void chat(struct client_state *csp)
 #endif
    }
 
-
-   freez(csp->headers->first->str);
-   build_request_line(csp, fwd, &csp->headers->first->str);
-
    /*
     * We have a request. Check if one of the crunchers wants it
     * unless the client wants to use TLS/SSL in which case we
@@ -4223,7 +4328,126 @@ static void chat(struct client_state *csp)
       return;
    }
 
+#ifdef FEATURE_HTTPS_INSPECTION
+   if (client_use_ssl(csp) && !use_ssl_tunnel)
+   {
+      int ret;
+      /*
+       * Creating a SSL proxy.
+       *
+       * By sending the CSUCCEED message we're lying to the client as
+       * the connection hasn't actually been established yet. We don't
+       * establish the connection until we have seen and parsed the
+       * encrypted client headers.
+       */
+      if (write_socket_delayed(csp->cfd, CSUCCEED,
+            strlen(CSUCCEED), get_write_delay(csp)) != 0)
+      {
+         log_error(LOG_LEVEL_ERROR, "Sending SUCCEED to client failed");
+         return;
+      }
+
+      ret = create_client_ssl_connection(csp);
+      if (ret != 0)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Failed to open a secure connection with the client");
+         return;
+      }
+      if (JB_ERR_OK != process_encrypted_request_headers(csp))
+      {
+         close_client_ssl_connection(csp);
+         return;
+      }
+      /*
+       * We have an encrypted request. Check if one of the crunchers now
+       * wants it (for example because the previously invisible path was
+       * required to match).
+       */
+      if (crunch_response_triggered(csp, crunchers_all))
+      {
+         /*
+          * Yes. The client got the crunch response and we're done here.
+          */
+         return;
+      }
+   }
+#endif
+
+   /* If we need to apply client body filters, buffer the whole request now. */
+   if (csp->expected_client_content_length != 0 &&
+      (client_body_filters_enabled(csp->action) ||
+         client_body_taggers_enabled(csp->action)) &&
+      can_buffer_request_body(csp))
+   {
+      int content_modified;
+      size_t modified_content_length;
+
+#ifdef FEATURE_HTTPS_INSPECTION
+      if (client_use_ssl(csp) && read_https_request_body(csp))
+      {
+         log_error(LOG_LEVEL_ERROR, "Failed to buffer the encrypted "
+            "request body to apply filters or taggers.");
+         log_error(LOG_LEVEL_CLF,
+            "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd);
+
+         ssl_send_data_delayed(&(csp->ssl_client_attr),
+            (const unsigned char *)CLIENT_BODY_BUFFER_ERROR_RESPONSE,
+            strlen(CLIENT_BODY_BUFFER_ERROR_RESPONSE),
+            get_write_delay(csp));
+
+         return;
+      }
+      else
+#endif
+      if (read_http_request_body(csp))
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Failed to buffer the request body to apply filters or taggers,");
+         log_error(LOG_LEVEL_CLF,
+            "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, csp->http->cmd);
+
+         write_socket_delayed(csp->cfd, CLIENT_BODY_BUFFER_ERROR_RESPONSE,
+            strlen(CLIENT_BODY_BUFFER_ERROR_RESPONSE), get_write_delay(csp));
+
+         return;
+      }
+      if (client_body_taggers_enabled(csp->action))
+      {
+         execute_client_body_taggers(csp, csp->expected_client_content_length);
+         if (crunch_response_triggered(csp, crunchers_all))
+         {
+            /*
+             * Yes. The client got the crunch response and we're done here.
+             */
+            return;
+         }
+      }
+      if (client_body_filters_enabled(csp->action))
+      {
+         modified_content_length = csp->expected_client_content_length;
+         content_modified = execute_client_body_filters(csp,
+            &modified_content_length);
+         if ((content_modified == 1) &&
+            (modified_content_length != csp->expected_client_content_length) &&
+            update_client_headers(csp, modified_content_length))
+         {
+            /* XXX: Send error response */
+            log_error(LOG_LEVEL_HEADER, "Error updating client headers");
+            return;
+         }
+      }
+      csp->expected_client_content_length = 0;
+   }
+
    log_applied_actions(csp->action);
+
+   /* decide how to route the HTTP request */
+   fwd = forward_url(csp, http);
+
+   freez(csp->headers->first->str);
+   build_request_line(csp, fwd, &csp->headers->first->str);
+
    if (fwd->forward_host)
    {
       log_error(LOG_LEVEL_CONNECT, "via [%s]:%d to: %s",
@@ -4271,51 +4495,7 @@ static void chat(struct client_state *csp)
          mark_connection_closed(&csp->server_connection);
       }
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
-#ifdef FEATURE_HTTPS_INSPECTION
-      if (client_use_ssl(csp) && !use_ssl_tunnel)
-      {
-         int ret;
-         /*
-          * Creating a SSL proxy.
-          *
-          * By sending the CSUCCEED message we're lying to the client as
-          * the connection hasn't actually been established yet. We don't
-          * establish the connection until we have seen and parsed the
-          * encrypted client headers.
-          */
-         if (write_socket_delayed(csp->cfd, CSUCCEED,
-               strlen(CSUCCEED), get_write_delay(csp)) != 0)
-         {
-            log_error(LOG_LEVEL_ERROR, "Sending SUCCEED to client failed");
-            return;
-         }
 
-         ret = create_client_ssl_connection(csp);
-         if (ret != 0)
-         {
-            log_error(LOG_LEVEL_ERROR,
-               "Failed to open a secure connection with the client");
-            return;
-         }
-         if (JB_ERR_OK != process_encrypted_request(csp))
-         {
-            close_client_ssl_connection(csp);
-            return;
-         }
-         /*
-          * We have an encrypted request. Check if one of the crunchers now
-          * wants it (for example because the previously invisible path was
-          * required to match).
-          */
-         if (crunch_response_triggered(csp, crunchers_all))
-         {
-            /*
-             * Yes. The client got the crunch response and we're done here.
-             */
-            return;
-         }
-      }
-#endif
       /*
        * Connecting to destination server
        */
@@ -4500,7 +4680,13 @@ static void chat(struct client_state *csp)
 #endif
            ))
    {
-      if (send_http_request(csp))
+      int status = send_http_request(csp);
+      if (status == 2)
+      {
+         /* The request got crunched, a response has been delivered. */
+         return;
+      }
+      if (status != 0)
       {
          rsp = error_response(csp, "connect-failed");
          if (rsp)
@@ -4794,7 +4980,8 @@ static void serve(struct client_state *csp)
             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,
+               csp->server_connection.sfd, (csp->server_connection.host != NULL) ?
+               csp->server_connection.host : csp->http->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),
@@ -4810,6 +4997,22 @@ static void serve(struct client_state *csp)
 #endif
             close_socket(csp->server_connection.sfd);
             mark_connection_closed(&csp->server_connection);
+#ifdef FEATURE_HTTPS_INSPECTION
+            if (continue_chatting && client_use_ssl(csp))
+            {
+               /*
+                * Close the client socket as well as Privoxy currently
+                * can't establish a new server connection when the client
+                * socket is reused and would drop the connection in
+                * continue_https_chat() anyway.
+                */
+               continue_chatting = 0;
+               csp->flags &= ~CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE;
+               log_error(LOG_LEVEL_CONNECT,
+                  "Client socket %d is no longer usable. "
+                  "The server socket has been closed.", csp->cfd);
+            }
+#endif
          }
       }
 
@@ -5146,9 +5349,12 @@ static void initialize_mutexes(void)
 #ifdef FEATURE_CLIENT_TAGS
    privoxy_mutex_init(&client_tags_mutex);
 #endif
+#ifdef FEATURE_STATISTICS
+   privoxy_mutex_init(&block_statistics_mutex);
+#endif
 #ifdef FEATURE_EXTENDED_STATISTICS
    privoxy_mutex_init(&filter_statistics_mutex);
-   privoxy_mutex_init(&block_statistics_mutex);
+   privoxy_mutex_init(&block_reason_statistics_mutex);
 #endif
 
    /*
@@ -5727,15 +5933,18 @@ static jb_socket bind_port_helper(const char *haddr, int hport, int backlog)
                "can't bind to %s:%d: There may be another Privoxy "
                "or some other proxy running on port %d",
                bind_address, hport, hport);
+            exit(-1);
 
          case -2:
             log_error(LOG_LEVEL_FATAL,
                "can't bind to %s:%d: The hostname is not resolvable",
                bind_address, hport);
+            exit(-1);
 
          default:
             log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: %E",
                bind_address, hport);
+            exit(-1);
       }
 
       /* shouldn't get here */
@@ -6005,7 +6214,7 @@ static void listen_loop(void)
       if ((0 != config->max_client_connections)
          && (active_threads >= config->max_client_connections))
       {
-         log_error(LOG_LEVEL_CONNECT,
+         log_error(LOG_LEVEL_ERROR,
             "Rejecting connection from %s. Maximum number of connections reached.",
             csp->ip_addr_str);
          write_socket_delayed(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
@@ -6041,7 +6250,7 @@ static void listen_loop(void)
          }
 #endif
 
-#if defined(_WIN32) && !defined(_CYGWIN) && !defined(SELECTED_ONE_OPTION)
+#if defined(_WIN32) && !defined(SELECTED_ONE_OPTION)
 #define SELECTED_ONE_OPTION
          child_id = _beginthread(
             (void (*)(void *))serve,
@@ -6171,14 +6380,11 @@ static void listen_loop(void)
 
    /* NOTREACHED unless FEATURE_GRACEFUL_TERMINATION is defined */
 
-#ifdef FEATURE_HTTPS_INSPECTION
-   /* Clean up.  Aim: free all memory (no leaks) */
-   ssl_release();
-#endif
-
 #ifdef FEATURE_GRACEFUL_TERMINATION
 
-   log_error(LOG_LEVEL_INFO, "Graceful termination requested");
+   log_error(LOG_LEVEL_INFO, "Graceful termination requested.");
+
+   close_ports_helper(bfds);
 
    unload_current_config_file();
    unload_current_actions_file();
@@ -6198,7 +6404,8 @@ static void listen_loop(void)
 
       if (i <= 0)
       {
-         log_error(LOG_LEVEL_ERROR, "Graceful termination failed - still some live clients after 1 minute wait.");
+         log_error(LOG_LEVEL_ERROR, "Graceful termination failed "
+            "- still some live clients after 1 minute wait.");
       }
    }
    sweep();
@@ -6208,6 +6415,19 @@ static void listen_loop(void)
    freez(basedir);
 #endif
 
+#ifdef FEATURE_HTTPS_INSPECTION
+   /*
+    * Only release TLS backed resources if there
+    * are no active connections left.
+    */
+   if (clients->next == NULL)
+   {
+      ssl_release();
+   }
+#endif
+
+   log_error(LOG_LEVEL_INFO, "Exiting gracefully.");
+
 #if defined(_WIN32) && !defined(_WIN_CONSOLE)
    /* Cleanup - remove taskbar icon etc. */
    TermLogWindow();