Initial keep-alive support for the client socket.
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index 320d28f..5991a72 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.222 2009/02/08 12:56:51 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.245 2009/04/24 15:29:43 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -33,6 +33,97 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.222 2009/02/08 12:56:51 fabiankeil Exp $"
  *
  * Revisions   :
  *    $Log: jcc.c,v $
+ *    Revision 1.245  2009/04/24 15:29:43  fabiankeil
+ *    Allow to limit the number of of client connections.
+ *
+ *    Revision 1.244  2009/04/17 11:34:34  fabiankeil
+ *    Style cosmetics for the IPv6 code.
+ *
+ *    Revision 1.243  2009/04/17 11:27:49  fabiankeil
+ *    Petr Pisar's privoxy-3.0.12-ipv6-3.diff.
+ *
+ *    Revision 1.242  2009/04/11 10:44:47  fabiankeil
+ *    Update a comment. We're not in Kansas anymore.
+ *
+ *    Revision 1.241  2009/04/11 10:37:23  fabiankeil
+ *    When dropping connections due to ACL, don't leak csp->ip_addr_str.
+ *
+ *    Revision 1.240  2009/04/09 10:12:54  fabiankeil
+ *    Fix two cases in which an invalid server response would result
+ *    in the client connection being closed without sending an error
+ *    message first.
+ *
+ *    Revision 1.239  2009/04/07 11:43:50  fabiankeil
+ *    If the server rudely resets the connection directly after sending the
+ *    headers, pass the mess to the client instead of sending an incorrect
+ *    connect-failed message. Fixes #2698674 reported by mybugaccount.
+ *
+ *    Revision 1.238  2009/03/27 14:42:30  fabiankeil
+ *    Correct the status code for CONNECTION_TIMEOUT_RESPONSE.
+ *
+ *    Revision 1.237  2009/03/27 14:32:04  fabiankeil
+ *    If spawning a child in listen_loop() fails, send a real
+ *    HTTP response to the client and continue listening for
+ *    new connections without artificial delay.
+ *
+ *    Revision 1.236  2009/03/25 17:30:24  fabiankeil
+ *    In serve(), keep the client socket open until we marked the
+ *    server socket as unused. This should increase the chances
+ *    that we reuse the connection for the client's next request
+ *    to the same destination.
+ *
+ *    Revision 1.235  2009/03/18 21:01:20  fabiankeil
+ *    Comment fix. Spotted by Roland.
+ *
+ *    Revision 1.234  2009/03/18 20:48:42  fabiankeil
+ *    If the --no-daemon option is used, enable LOG_LEVEL_INFO
+ *    before the config file has been parsed (as we always did).
+ *
+ *    Revision 1.233  2009/03/13 14:10:07  fabiankeil
+ *    Fix some more harmless warnings on amd64.
+ *
+ *    Revision 1.232  2009/03/08 19:29:16  fabiankeil
+ *    Reinitialize the timeout structure every time before passing
+ *    it to select(). Apparently some implementations mess with it.
+ *    Probably fixes #2669131 reported by cyberpatrol.
+ *
+ *    Revision 1.231  2009/03/08 14:19:23  fabiankeil
+ *    Fix justified (but harmless) compiler warnings
+ *    on platforms where sizeof(int) < sizeof(long).
+ *
+ *    Revision 1.230  2009/03/07 13:09:17  fabiankeil
+ *    Change csp->expected_content and_csp->expected_content_length from
+ *    size_t to unsigned long long to reduce the likelihood of integer
+ *    overflows that would let us close the connection prematurely.
+ *    Bug found while investigating #2669131, reported by cyberpatrol.
+ *
+ *    Revision 1.229  2009/03/07 11:17:01  fabiankeil
+ *    Fix compiler warning.
+ *
+ *    Revision 1.228  2009/03/06 20:30:13  fabiankeil
+ *    Log unsigned values as such.
+ *
+ *    Revision 1.227  2009/03/02 19:18:11  fabiankeil
+ *    Streamline parse_http_request()'s prototype. As
+ *    cparser pointed out it doesn't actually use csp.
+ *
+ *    Revision 1.226  2009/03/01 18:28:24  fabiankeil
+ *    Help clang understand that we aren't dereferencing
+ *    NULL pointers here.
+ *
+ *    Revision 1.225  2009/02/19 18:09:32  fabiankeil
+ *    Unbreak build without FEATURE_CONNECTION_KEEP_ALIVE.
+ *    Noticed by David.
+ *
+ *    Revision 1.224  2009/02/14 15:32:04  fabiankeil
+ *    Add the request URL to the timeout message in chat().
+ *    Suggested by Lee.
+ *
+ *    Revision 1.223  2009/02/09 21:21:16  fabiankeil
+ *    Now that init_log_module() is called earlier, call show_version()
+ *    later on from main() directly so it doesn't get called for --help
+ *    or --version.
+ *
  *    Revision 1.222  2009/02/08 12:56:51  fabiankeil
  *    Call initialize_mutexes() before init_log_module() again.
  *    Broken since r220, might be the cause of Lee's #2579448.
@@ -1416,9 +1507,16 @@ static const char MESSED_UP_REQUEST_RESPONSE[] =
    "Connection: close\r\n\r\n"
    "Bad request. Messed up with header filters.\r\n";
 
+static const char TOO_MANY_CONNECTIONS_RESPONSE[] =
+   "HTTP/1.0 503 Too many open connections\r\n"
+   "Proxy-Agent: Privoxy " VERSION "\r\n"
+   "Content-Type: text/plain\r\n"
+   "Connection: close\r\n\r\n"
+   "Maximum number of open connections reached.\r\n";
+
 /* XXX: should be a template */
 static const char CONNECTION_TIMEOUT_RESPONSE[] =
-   "HTTP/1.0 502 Connection timeout\r\n"
+   "HTTP/1.0 504 Connection timeout\r\n"
    "Proxy-Agent: Privoxy " VERSION "\r\n"
    "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
@@ -1874,7 +1972,7 @@ static void send_crunch_response(const struct client_state *csp, struct http_res
 
       /* 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 %d",
+      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 */
@@ -1931,7 +2029,7 @@ static int request_contains_null_bytes(const struct client_state *csp, char *buf
       } 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);
+         "(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);
 
@@ -2100,7 +2198,7 @@ static jb_err change_request_destination(struct client_state *csp)
 
    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);
+   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.",
@@ -2137,9 +2235,10 @@ static jb_err change_request_destination(struct client_state *csp)
  *                FALSE otherwise.
  *
  *********************************************************************/
-static int server_response_is_complete(struct client_state *csp, size_t content_length)
+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);
+   int content_length_known = !!(csp->flags & CSP_FLAG_CONTENT_LENGTH_SET);
 
    if (!strcmpic(csp->http->gpc, "HEAD"))
    {
@@ -2163,8 +2262,105 @@ static int server_response_is_complete(struct client_state *csp, size_t content_
    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()
+{
+   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
@@ -2308,7 +2504,7 @@ static jb_err receive_client_request(struct client_state *csp)
    }
 #endif /* def FEATURE_FORCE_LOAD */
 
-   err = parse_http_request(req, http, csp);
+   err = parse_http_request(req, http);
    freez(req);
    if (JB_ERR_OK != err)
    {
@@ -2517,12 +2713,12 @@ static void chat(struct client_state *csp)
    jb_socket maxfd;
    int server_body;
    int ms_iis5_hack = 0;
-   size_t byte_count = 0;
+   unsigned long long 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 = 0; /* for buffer sizes (and negative error codes) */
+   long len = 0; /* for buffer sizes (and negative error codes) */
 
    /* Function that does the content filtering for the current request */
    filter_function_ptr content_filter = NULL;
@@ -2532,8 +2728,6 @@ static void chat(struct client_state *csp)
    struct timeval timeout;
 
    memset(buf, 0, sizeof(buf));
-   memset(&timeout, 0, sizeof(timeout));
-   timeout.tv_sec = csp->config->socket_timeout;
 
    http = csp->http;
 
@@ -2552,6 +2746,7 @@ static void chat(struct client_state *csp)
    {
       log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!?  This can't happen!");
       /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      return;
    }
 
    /*
@@ -2628,7 +2823,7 @@ static void chat(struct client_state *csp)
 
    if (fwd->forward_host)
    {
-      log_error(LOG_LEVEL_CONNECT, "via %s:%d to: %s",
+      log_error(LOG_LEVEL_CONNECT, "via [%s]:%d to: %s",
          fwd->forward_host, fwd->forward_port, http->hostport);
    }
    else
@@ -2638,41 +2833,66 @@ static void chat(struct client_state *csp)
 
    /* here we connect to the server, gateway, or the forwarder */
 
-   while ((csp->sfd = forwarded_connect(fwd, http, csp))
-      && (errno == EINVAL)
-      && (forwarded_connect_retries++ < max_forwarded_connect_retries))
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+   if ((csp->sfd != JB_INVALID_SOCKET)
+      && socket_is_still_usable(csp->sfd)
+      && connection_destination_matches(&csp->server_connection, http, fwd))
    {
-      log_error(LOG_LEVEL_ERROR,
-         "failed request #%u to connect to %s. Trying again.",
-         forwarded_connect_retries, http->hostport);
+      log_error(LOG_LEVEL_CONNECT,
+         "Reusing server socket %u. Opened for %s.",
+         csp->sfd, csp->server_connection.host);
    }
-
-   if (csp->sfd == JB_INVALID_SOCKET)
+   else
    {
-      if (fwd->type != SOCKS_NONE)
+      if (csp->sfd != JB_INVALID_SOCKET)
       {
-         /* Socks error. */
-         rsp = error_response(csp, "forwarding-failed", errno);
-      }
-      else if (errno == EINVAL)
-      {
-         rsp = error_response(csp, "no-such-domain", errno);
+         log_error(LOG_LEVEL_CONNECT,
+            "Closing server socket %u. Opened for %s.",
+            csp->sfd, csp->server_connection.host);
+         close_socket(csp->sfd);
+         mark_connection_closed(&csp->server_connection);
       }
-      else
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+      while ((csp->sfd = forwarded_connect(fwd, http, csp))
+         && (errno == EINVAL)
+         && (forwarded_connect_retries++ < max_forwarded_connect_retries))
       {
-         rsp = error_response(csp, "connect-failed", errno);
-         log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
-            http->hostport);
+         log_error(LOG_LEVEL_ERROR,
+            "failed request #%u to connect to %s. Trying again.",
+            forwarded_connect_retries, http->hostport);
       }
 
-      /* Write the answer to the client */
-      if (rsp != NULL)
+      if (csp->sfd == JB_INVALID_SOCKET)
       {
-         send_crunch_response(csp, rsp);
-      }
+         if (fwd->type != SOCKS_NONE)
+         {
+            /* Socks error. */
+            rsp = error_response(csp, "forwarding-failed", errno);
+         }
+         else if (errno == EINVAL)
+         {
+            rsp = error_response(csp, "no-such-domain", errno);
+         }
+         else
+         {
+            rsp = error_response(csp, "connect-failed", errno);
+            log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
+               http->hostport);
+         }
 
-      return;
+         /* Write the answer to the client */
+         if (rsp != NULL)
+         {
+            send_crunch_response(csp, rsp);
+         }
+
+         return;
+      }
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
+      save_connection_destination(csp->sfd, http, fwd, &csp->server_connection);
    }
+#endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
 
    hdr = list_to_text(csp->headers);
    if (hdr == NULL)
@@ -2755,15 +2975,15 @@ static void chat(struct client_state *csp)
          log_error(LOG_LEVEL_CONNECT,
             "Looks like we read the last chunk together with "
             "the server headers. We better stop reading.");
-         byte_count = (size_t)(csp->iob->eod - csp->iob->cur);
+         byte_count = (unsigned long long)(csp->iob->eod - csp->iob->cur);
          csp->expected_content_length = byte_count;
          csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET;
       }
       if (server_body && server_response_is_complete(csp, byte_count))
       {
          log_error(LOG_LEVEL_CONNECT,
-            "Done reading from server. Expected content length: %d. "
-            "Actual content length: %d. Most recently received: %d.",
+            "Done reading from server. Expected content length: %llu. "
+            "Actual content length: %llu. Most recently received: %d.",
             csp->expected_content_length, byte_count, len);
          len = 0;
          /*
@@ -2774,11 +2994,14 @@ static void chat(struct client_state *csp)
       }
 #endif  /* FEATURE_CONNECTION_KEEP_ALIVE */
 
+      timeout.tv_sec = csp->config->socket_timeout;
+      timeout.tv_usec = 0;
       n = select((int)maxfd+1, &rfds, NULL, NULL, &timeout);
 
       if (n == 0)
       {
-         log_error(LOG_LEVEL_ERROR, "Didn't receive data in time.");
+         log_error(LOG_LEVEL_ERROR,
+            "Didn't receive data in time: %s", http->url);
          if ((byte_count == 0) && (http->ssl == 0))
          {
             write_socket(csp->cfd, CONNECTION_TIMEOUT_RESPONSE,
@@ -2797,6 +3020,9 @@ static void chat(struct client_state *csp)
       /*
        * This is the body of the browser's request,
        * just read and write it.
+       *
+       * XXX: Make sure the client doesn't use pipelining
+       * behind Privoxy's back.
        */
       if (FD_ISSET(csp->cfd, &rfds))
       {
@@ -2857,14 +3083,11 @@ static void chat(struct client_state *csp)
                mark_server_socket_tainted(csp);
                return;
             }
-
-            rsp = error_response(csp, "connect-failed", errno);
-            if (rsp)
-            {
-               send_crunch_response(csp, rsp);
-            }
-
-            return;
+            /*
+             * XXX: Consider handling the cases above the same.
+             */
+            mark_server_socket_tainted(csp);
+            len = 0;
          }
 
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
@@ -2876,7 +3099,7 @@ static void chat(struct client_state *csp)
                log_error(LOG_LEVEL_CONNECT,
                   "Looks like we reached the end of the last chunk. "
                   "We better stop reading.");
-               csp->expected_content_length = byte_count + (size_t)len;
+               csp->expected_content_length = byte_count + (unsigned long long)len;
                csp->flags |= CSP_FLAG_CONTENT_LENGTH_SET;
             }
          }
@@ -2945,7 +3168,8 @@ static void chat(struct client_state *csp)
                   }
 
                   if (write_socket(csp->cfd, hdr, strlen(hdr))
-                   || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length))
+                   || write_socket(csp->cfd,
+                         ((p != NULL) ? p : csp->iob->cur), (size_t)csp->content_length))
                   {
                      log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E");
                      freez(hdr);
@@ -2993,7 +3217,7 @@ static void chat(struct client_state *csp)
                if (add_to_iob(csp, buf, len))
                {
                   size_t hdrlen;
-                  int flushed;
+                  long flushed;
 
                   log_error(LOG_LEVEL_INFO,
                      "Flushing header and buffers. Stepping back from filtering.");
@@ -3029,7 +3253,7 @@ static void chat(struct client_state *csp)
                    * we just flushed. len will be added a few lines below,
                    * hdrlen doesn't matter for LOG_LEVEL_CLF.
                    */
-                  byte_count = (size_t)flushed;
+                  byte_count = (unsigned long long)flushed;
                   freez(hdr);
                   content_filter = NULL;
                   server_body = 1;
@@ -3044,7 +3268,7 @@ static void chat(struct client_state *csp)
                   return;
                }
             }
-            byte_count += (size_t)len;
+            byte_count += (unsigned long long)len;
             continue;
          }
          else
@@ -3076,9 +3300,14 @@ static void chat(struct client_state *csp)
                    * The header is incomplete and there isn't anything
                    * we can do about it.
                    */
-                  log_error(LOG_LEVEL_INFO,
-                     "MS IIS5 hack didn't produce valid headers.");
-                  break;
+                  log_error(LOG_LEVEL_ERROR, "Invalid server headers. "
+                     "Applying the MS IIS5 hack didn't help.");
+                  log_error(LOG_LEVEL_CLF,
+                     "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
+                  write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
+                     strlen(INVALID_SERVER_HEADERS_RESPONSE));
+                  mark_server_socket_tainted(csp);
+                  return;
                }
                else
                {
@@ -3086,11 +3315,11 @@ static void chat(struct client_state *csp)
                    * Since we have to wait for more from the server before
                    * we can parse the headers we just continue here.
                    */
-                  int header_offset = csp->iob->cur - header_start;
+                  long header_offset = csp->iob->cur - header_start;
                   assert(csp->iob->cur >= header_start);
-                  byte_count += (size_t)(len - header_offset);
+                  byte_count += (unsigned long long)(len - header_offset);
                   log_error(LOG_LEVEL_CONNECT, "Continuing buffering headers. "
-                     "byte_count: %d. header_offset: %d. len: %d.",
+                     "byte_count: %llu. header_offset: %d. len: %d.",
                      byte_count, header_offset, len);
                   continue;
                }
@@ -3190,7 +3419,7 @@ static void chat(struct client_state *csp)
                   return;
                }
 
-               byte_count += (size_t)len;
+               byte_count += (unsigned long long)len;
             }
             else
             {
@@ -3198,9 +3427,9 @@ static void chat(struct client_state *csp)
                 * XXX: the header lenght should probably
                 * be calculated by get_server_headers().
                 */
-               int header_length = csp->iob->cur - header_start;
+               long header_length = csp->iob->cur - header_start;
                assert(csp->iob->cur > header_start);
-               byte_count += (size_t)(len - header_length);
+               byte_count += (unsigned long long)(len - header_length);
             }
 
             /* we're finished with the server's header */
@@ -3215,9 +3444,15 @@ static void chat(struct client_state *csp)
              */
             if (ms_iis5_hack)
             {
-               log_error(LOG_LEVEL_INFO,
-                  "Closed server connection detected with MS IIS5 hack enabled.");
-               break;
+               log_error(LOG_LEVEL_ERROR,
+                  "Closed server connection detected. "
+                  "Applying the MS IIS5 hack didn't help.");
+               log_error(LOG_LEVEL_CLF,
+                  "%s - - [%T] \"%s\" 502 0", csp->ip_addr_str, http->cmd);
+               write_socket(csp->cfd, INVALID_SERVER_HEADERS_RESPONSE,
+                  strlen(INVALID_SERVER_HEADERS_RESPONSE));
+               mark_server_socket_tainted(csp);
+               return;
             }
          }
          continue;
@@ -3235,54 +3470,29 @@ static void chat(struct client_state *csp)
       csp->content_length = byte_count;
    }
 
+#ifdef FEATURE_CONNECTION_KEEP_ALIVE
    if ((csp->flags & CSP_FLAG_CONTENT_LENGTH_SET)
       && (csp->expected_content_length != byte_count))
    {
       log_error(LOG_LEVEL_CONNECT,
-         "Received %d bytes while expecting %d.",
+         "Received %llu bytes while expecting %llu.",
          byte_count, csp->expected_content_length);
       mark_server_socket_tainted(csp);
    }
+#endif
 
-   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %llu",
       csp->ip_addr_str, http->ocmd, csp->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()
-{
-   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    :  serve
  *
  * Description :  This is little more than chat.  We only "serve" to
- *                to close any socket that chat may have opened.
+ *                to close (or remember) any socket that chat may have
+ *                opened.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
@@ -3296,37 +3506,85 @@ void serve(struct client_state *csp)
 static void serve(struct client_state *csp)
 #endif /* def AMIGA */
 {
-   chat(csp);
-   close_socket(csp->cfd);
-
-   if (csp->sfd != JB_INVALID_SOCKET)
-   {
 #ifdef FEATURE_CONNECTION_KEEP_ALIVE
-      static int monitor_thread_running = 0;
+   int continue_chatting = 0;
+   do
+   {
+      chat(csp);
 
-      if ((csp->config->feature_flags & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
-       && (csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE))
+      continue_chatting = (csp->config->feature_flags
+         & RUNTIME_FEATURE_CONNECTION_KEEP_ALIVE)
+         && (csp->flags & CSP_FLAG_SERVER_CONNECTION_KEEP_ALIVE)
+         && (csp->cfd != JB_INVALID_SOCKET)
+         && (csp->sfd != JB_INVALID_SOCKET)
+         && socket_is_still_usable(csp->sfd);
+
+      /*
+       * Get the csp in a mostly vergin state again.
+       * XXX: Should be done elsewhere.
+       */
+      csp->content_type = 0;
+      csp->content_length = 0;
+      csp->expected_content_length = 0;
+      list_remove_all(csp->headers);
+      freez(csp->iob->buf);
+      memset(csp->iob, 0, sizeof(csp->iob));
+      freez(csp->error_message);
+      free_http_request(csp->http);
+      destroy_list(csp->headers);
+      destroy_list(csp->tags);
+      free_current_action(csp->action);
+      if (NULL != csp->fwd)
       {
-         remember_connection(csp->sfd, csp->http, forward_url(csp, csp->http));
-         privoxy_mutex_lock(&connection_reuse_mutex);
-         if (!monitor_thread_running)
+         unload_forward_spec(csp->fwd);
+         csp->fwd = NULL;
+      }
+
+      /* XXX: Store per-connection flags someplace else. */
+      csp->flags = CSP_FLAG_ACTIVE | (csp->flags & CSP_FLAG_TOGGLED_ON);
+
+      if (continue_chatting)
+      {
+         log_error(LOG_LEVEL_CONNECT,
+            "Waiting for the next client request. "
+            "Keeping the server socket %d to %s open.",
+            csp->sfd, csp->server_connection.host);
+
+         if ((csp->flags & CSP_FLAG_CLIENT_CONNECTION_KEEP_ALIVE)
+            && data_is_available(csp->cfd, csp->config->keep_alive_timeout)
+            && socket_is_still_usable(csp->cfd))
+         {
+            log_error(LOG_LEVEL_CONNECT, "Client request arrived in "
+               "time or the client closed the connection.");
+         }
+         else
          {
-            monitor_thread_running = 1;
-            privoxy_mutex_unlock(&connection_reuse_mutex);
-            wait_for_alive_connections();
-            privoxy_mutex_lock(&connection_reuse_mutex);
-            monitor_thread_running = 0;
+            log_error(LOG_LEVEL_CONNECT,
+               "No additional client request received in time. "
+               "Closing server socket %d, initially opened for %s.",
+               csp->sfd, csp->server_connection.host);
+            break;
          }
-         privoxy_mutex_unlock(&connection_reuse_mutex);
       }
-      else
+      else if (csp->sfd != JB_INVALID_SOCKET)
       {
-         forget_connection(csp->sfd);
-         close_socket(csp->sfd);
+         log_error(LOG_LEVEL_CONNECT,
+            "The connection on server socket %d to %s isn't reusable. "
+            "Closing.", csp->sfd, csp->server_connection.host);
       }
+   } while (continue_chatting);
 #else
-      close_socket(csp->sfd);
+   chat(csp);
 #endif /* def FEATURE_CONNECTION_KEEP_ALIVE */
+
+   if (csp->sfd != JB_INVALID_SOCKET)
+   {
+      close_socket(csp->sfd);
+   }
+
+   if (csp->cfd != JB_INVALID_SOCKET)
+   {
+      close_socket(csp->cfd);
    }
 
    csp->flags &= ~CSP_FLAG_ACTIVE;
@@ -3637,6 +3895,7 @@ int main(int argc, const char *argv[])
 
       else if (strcmp(argv[argc_pos], "--no-daemon" ) == 0)
       {
+         set_debug_level(LOG_LEVEL_FATAL | LOG_LEVEL_ERROR | LOG_LEVEL_INFO);
          no_daemon = 1;
       }
 
@@ -3852,8 +4111,8 @@ int main(int argc, const char *argv[])
       }
 #endif /* 1 */
       /*
-       * stderr (fd 2) will be closed later on, when the
-       * log file has been parsed.
+       * stderr (fd 2) will be closed later on,
+       * when the config file has been parsed.
        */
 
       close( 0 );
@@ -4064,7 +4323,8 @@ static void listen_loop(void)
 {
    struct client_state *csp = NULL;
    jb_socket bfd;
-   struct configuration_spec * config;
+   struct configuration_spec *config;
+   unsigned int active_threads = 0;
 
    config = load_config();
 
@@ -4094,7 +4354,7 @@ static void listen_loop(void)
       /*
        * Free data that was used by died threads
        */
-      sweep();
+      active_threads = sweep();
 
 #if defined(unix)
       /*
@@ -4125,7 +4385,7 @@ static void listen_loop(void)
       {
          /*
           * Since we were listening to the "old port", we will not see
-          * a "listen" param change until the next IJB request.  So, at
+          * a "listen" param change until the next request.  So, at
           * least 1 more request must be made for us to find the new
           * setting.  I am simply closing the old socket and binding the
           * new one.
@@ -4179,11 +4439,26 @@ static void listen_loop(void)
       {
          log_error(LOG_LEVEL_CONNECT, "Connection from %s dropped due to ACL", csp->ip_addr_str);
          close_socket(csp->cfd);
+         freez(csp->ip_addr_str);
          freez(csp);
          continue;
       }
 #endif /* def FEATURE_ACL */
 
+      if ((0 != config->max_client_connections)
+         && (active_threads >= config->max_client_connections))
+      {
+         log_error(LOG_LEVEL_CONNECT,
+            "Rejecting connection from %s. Maximum number of connections reached.",
+            csp->ip_addr_str);
+         write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
+            strlen(TOO_MANY_CONNECTIONS_RESPONSE));
+         close_socket(csp->cfd);
+         freez(csp->ip_addr_str);
+         freez(csp);
+         continue;
+      }
+
       /* add it to the list of clients */
       csp->next = clients->next;
       clients->next = csp;
@@ -4350,19 +4625,19 @@ static void listen_loop(void)
 #undef SELECTED_ONE_OPTION
 /* end of cpp switch () */
 
-         if (child_id < 0) /* failed */
+         if (child_id < 0)
          {
-            char buf[BUFFER_SIZE];
-
-            log_error(LOG_LEVEL_ERROR, "can't fork: %E");
-
-            snprintf(buf , sizeof(buf), "Privoxy: can't fork: errno = %d", errno);
-
-            write_socket(csp->cfd, buf, strlen(buf));
+            /*
+             * Spawning the child failed, assume it's because
+             * there are too many children running already.
+             * XXX: If you assume ...
+             */
+            log_error(LOG_LEVEL_ERROR,
+               "Unable to take any additional connections: %E");
+            write_socket(csp->cfd, TOO_MANY_CONNECTIONS_RESPONSE,
+               strlen(TOO_MANY_CONNECTIONS_RESPONSE));
             close_socket(csp->cfd);
             csp->flags &= ~CSP_FLAG_ACTIVE;
-            sleep(5);
-            continue;
          }
       }
       else