Added img.bluehost.com per Actionsfile tracker.
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index a7856bf..caee73a 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.123 2007/02/21 18:42:10 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.131 2007/04/22 13:24:50 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -33,6 +33,36 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.123 2007/02/21 18:42:10 fabiankeil Exp $"
  *
  * Revisions   :
  *    $Log: jcc.c,v $
+ *    Revision 1.131  2007/04/22 13:24:50  fabiankeil
+ *    Make HTTP snippets static (again). Add a Content-Type for those
+ *    with content so the browser doesn't guess it based on the URL.
+ *
+ *    Revision 1.130  2007/04/19 13:47:34  fabiankeil
+ *    Move crunching and request line rebuilding out of chat().
+ *
+ *    Revision 1.129  2007/04/15 16:39:20  fabiankeil
+ *    Introduce tags as alternative way to specify which
+ *    actions apply to a request. At the moment tags can be
+ *    created based on client and server headers.
+ *
+ *    Revision 1.128  2007/03/25 16:55:54  fabiankeil
+ *    Don't CLF-log CONNECT requests twice.
+ *
+ *    Revision 1.127  2007/03/20 13:53:17  fabiankeil
+ *    Log the source address for ACL-related connection drops.
+ *
+ *    Revision 1.126  2007/03/17 15:20:05  fabiankeil
+ *    New config option: enforce-blocks.
+ *
+ *    Revision 1.125  2007/03/09 14:12:00  fabiankeil
+ *    - Move null byte check into separate function.
+ *    - Don't confuse the client with error pages
+ *      if a CONNECT request was already confirmed.
+ *
+ *    Revision 1.124  2007/02/23 14:59:54  fabiankeil
+ *    Speed up NULL byte escaping and only log the complete
+ *    NULL byte requests with header debugging enabled.
+ *
  *    Revision 1.123  2007/02/21 18:42:10  fabiankeil
  *    Answer requests that contain NULL bytes with
  *    a custom response instead of waiting for more
@@ -933,50 +963,57 @@ static const char VANILLA_WAFER[] =
    "(copyright_or_otherwise)_applying_to_any_cookie._";
 
 /* HTTP snipplets. */
-const char CSUCCEED[] =
+const static char CSUCCEED[] =
    "HTTP/1.0 200 Connection established\n"
    "Proxy-Agent: Privoxy/" VERSION "\r\n\r\n";
 
-const char CHEADER[] =
+const static char CHEADER[] =
    "HTTP/1.0 400 Invalid header received from browser\r\n"
+   "Proxy-Agent: Privoxy " VERSION "\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Invalid header received from browser.\r\n";
 
-const char CFORBIDDEN[] =
+const static char CFORBIDDEN[] =
    "HTTP/1.0 403 Connection not allowable\r\n"
    "Proxy-Agent: Privoxy " VERSION "\r\n"
    "X-Hint: If you read this message interactively, then you know why this happens ,-)\r\n"
    "Connection: close\r\n\r\n";
 
-const char FTP_RESPONSE[] =
+const static char FTP_RESPONSE[] =
    "HTTP/1.0 400 Invalid request received from browser\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Invalid request. Privoxy doesn't support FTP.\r\n";
 
-const char GOPHER_RESPONSE[] =
+const static char GOPHER_RESPONSE[] =
    "HTTP/1.0 400 Invalid request received from browser\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Invalid request. Privoxy doesn't support gopher.\r\n";
 
 /* XXX: should be a template */
-const char MISSING_DESTINATION_RESPONSE[] =
+const static char MISSING_DESTINATION_RESPONSE[] =
    "HTTP/1.0 400 Bad request received from browser\r\n"
    "Proxy-Agent: Privoxy " VERSION "\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Bad request. Privoxy was unable to extract the destination.\r\n";
 
 /* XXX: should be a template */
-const char NO_SERVER_DATA_RESPONSE[] =
+const static char NO_SERVER_DATA_RESPONSE[] =
    "HTTP/1.0 502 Server or forwarder response empty\r\n"
    "Proxy-Agent: Privoxy " VERSION "\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Empty server or forwarder response.\r\n"
    "The connection was closed without sending any data.\r\n";
 
 /* XXX: should be a template */
-const char NULL_BYTE_RESPONSE[] =
+const static char NULL_BYTE_RESPONSE[] =
    "HTTP/1.0 400 Bad request received from browser\r\n"
    "Proxy-Agent: Privoxy " VERSION "\r\n"
+   "Content-Type: text/plain\r\n"
    "Connection: close\r\n\r\n"
    "Bad request. Null byte(s) before end of request.\r\n";
 
@@ -1398,35 +1435,84 @@ void send_crunch_response(struct client_state *csp, struct http_response *rsp)
 
 /*********************************************************************
  *
- * Function    :  chat
+ * Function    :  request_contains_null_bytes
  *
- * Description :  Once a connection to the client has been accepted,
- *                this function is called (via serve()) to handle the
- *                main business of the communication.  When this
- *                function returns, the caller must close the client
- *                socket handle.
+ * Description :  Checks for NULL bytes in the request and sends
+ *                an error message to the client if any were found.
  *
- *                FIXME: chat is nearly thousand lines long.
- *                Ridiculous.
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *          2  :  buf = Data from the client's request to check.
+ *          3  :  len = The data length.
+ *
+ * Returns     :  TRUE if the request contained one or more NULL bytes, or
+ *                FALSE otherwise.
+ *
+ *********************************************************************/
+int request_contains_null_bytes(const struct client_state *csp, char *buf, int len)
+{
+   size_t c_len; /* Request lenght when treated as C string */
+
+   c_len = strlen(buf);
+
+   if (c_len < len)
+   {
+      /*
+       * Null byte(s) found. Log the request,
+       * return an error response and hang up.
+       */
+      size_t tmp_len = c_len;
+
+      do
+      {
+        /*
+         * Replace NULL byte(s) with '°' characters
+         * so the request can be logged as string.
+         * XXX: Is there a better replacement character?
+         */
+         buf[tmp_len]='°';
+         tmp_len += strlen(buf+tmp_len);
+      } while (tmp_len < len);
+
+      log_error(LOG_LEVEL_ERROR, "%s\'s request contains at least one NULL byte "
+         "(length=%d, strlen=%d).", csp->ip_addr_str, len, c_len);
+      log_error(LOG_LEVEL_HEADER, 
+         "Offending request data with NULL bytes turned into \'°\' characters: %s", buf);
+
+      strcpy(buf, NULL_BYTE_RESPONSE);
+      write_socket(csp->cfd, buf, strlen(buf));
+
+      /* XXX: Log correct size */
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str);
+
+      return TRUE;
+   }
+
+   return FALSE;
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  crunch_response_triggered
+ *
+ * Description :  Checks if the request has to be crunched,
+ *                and delivers the crunch response if necessary.
  *
  * Parameters  :
  *          1  :  csp = Current client state (buffers, headers, etc...)
  *
- * Returns     :  On success, the number of bytes written are returned (zero
- *                indicates nothing was written).  On error, -1 is returned,
- *                and errno is set appropriately.  If count is zero and the
- *                file descriptor refers to a regular file, 0 will be
- *                returned without causing any other effect.  For a special
- *                file, the results are not portable.
+ * Returns     :  TRUE if the request was answered with a crunch response
+ *                FALSE otherwise.
  *
  *********************************************************************/
-static void chat(struct client_state *csp)
+int crunch_response_triggered(struct client_state *csp)
 {
 /*
- * This next lines are a little ugly, but they simplifies the if statements
- * below.  Basically if TOGGLE, then we want the if to test if the
- * CSP_FLAG_TOGGLED_ON flag ist set, else we don't.  And if FEATURE_FORCE_LOAD,
- * then we want the if to test for CSP_FLAG_FORCED , else we don't
+ * This next lines are a little ugly, but they simplify the if statements
+ * below. Basically if TOGGLE, then we want the if to test if the
+ * CSP_FLAG_TOGGLED_ON flag ist set, else we don't. And if FEATURE_FORCE_LOAD,
+ * then we want the if to test for CSP_FLAG_FORCED, else we don't.
  */
 #ifdef FEATURE_TOGGLE
 #   define IS_TOGGLED_ON_AND (csp->flags & CSP_FLAG_TOGGLED_ON) &&
@@ -1441,6 +1527,137 @@ static void chat(struct client_state *csp)
 
 #define IS_ENABLED_AND   IS_TOGGLED_ON_AND IS_NOT_FORCED_AND
 
+   struct http_response *rsp = NULL;
+
+   if (
+       /* We may not forward the request by rfc2616 sect 14.31 */
+       (NULL != (rsp = direct_response(csp)))
+
+       /* or we are enabled and... */
+       || (IS_ENABLED_AND (
+
+            /* ..the request was blocked */
+          ( NULL != (rsp = block_url(csp)))
+
+          /* ..or untrusted */
+#ifdef FEATURE_TRUST
+          || ( NULL != (rsp = trust_url(csp)))
+#endif /* def FEATURE_TRUST */
+
+          /* ..or a redirect kicked in */
+          || ( NULL != (rsp = redirect_url(csp)))
+          ))
+       /*
+        * .. or a CGI call was detected and answered.
+        *
+        * This check comes last to give the user the power
+        * to deny acces to some (or all) of the cgi pages.
+        */
+       || (NULL != (rsp = dispatch_cgi(csp)))
+
+                )
+   {
+      /* Deliver, log and free the interception response. */
+      send_crunch_response(csp, rsp);
+#ifdef FEATURE_STATISTICS
+      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
+ *
+ * Returns     :  Nothing. Terminates in case of memory problems.
+ *
+ *********************************************************************/
+void build_request_line(struct client_state *csp, const struct forward_spec *fwd)
+{
+   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.
+    * XXX: If a http forwarder is used and the HTTP version
+    * wasn't downgraded, we don't have to rebuild anything.
+    */
+   freez(http->cmd);
+
+   http->cmd = strdup(http->gpc);
+   string_append(&http->cmd, " ");
+
+   if (fwd->forward_host)
+   {
+      string_append(&http->cmd, http->url);
+   }
+   else
+   {
+      string_append(&http->cmd, http->path);
+   }
+   string_append(&http->cmd, " ");
+   string_append(&http->cmd, http->ver);
+
+   if (http->cmd == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "Out of memory writing HTTP command");
+   }
+   log_error(LOG_LEVEL_HEADER, "New HTTP Request-Line: %s", http->cmd);
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  chat
+ *
+ * Description :  Once a connection to the client has been accepted,
+ *                this function is called (via serve()) to handle the
+ *                main business of the communication.  When this
+ *                function returns, the caller must close the client
+ *                socket handle.
+ *
+ *                FIXME: chat is nearly thousand lines long.
+ *                Ridiculous.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Nothing.
+ *
+ *********************************************************************/
+static void chat(struct client_state *csp)
+{
    char buf[BUFFER_SIZE];
    char *hdr;
    char *p;
@@ -1486,43 +1703,13 @@ static void chat(struct client_state *csp)
 
    for (;;)
    {
-      size_t c_len; /* Request lenght when treated as C string */
-
       len = read_socket(csp->cfd, buf, sizeof(buf)-1);
 
       if (len <= 0) break;      /* error! */
 
-      /* Naughty NULL bytes inside the request? */
-      c_len = strlen(buf);
-      if (c_len < len)
+      if (request_contains_null_bytes(csp, buf, len))
       {
-         /*
-          * Yes. Log the request, return an
-          * error response and hang up.
-          */
-         size_t tmp_len = c_len;
-
-         do
-         {
-           /*
-            * Replace NULL byte(s) with line break(s)
-            * so the request can be logged as string.
-            */
-            buf[tmp_len]='\n';
-            tmp_len += strlen(buf+tmp_len);
-         } while (tmp_len < len);
-
-         log_error(LOG_LEVEL_ERROR, "%s\'s request contains NULL byte(s) "
-            "(length=%d, strlen=%d).", csp->ip_addr_str, len, c_len);
-         log_error(LOG_LEVEL_HEADER, 
-            "Denied request with NULL byte(s) turned into line break(s):\n%s", buf);
-
-         strcpy(buf, NULL_BYTE_RESPONSE);
-         write_socket(csp->cfd, buf, strlen(buf));
-
-         /* XXX: Log correct size */
-         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"Invalid request\" 400 0", csp->ip_addr_str);
-
+         /* NULL bytes found and dealt with, just hang up. */
          return;
       }
 
@@ -1559,15 +1746,23 @@ static void chat(struct client_state *csp)
       }
 
 #ifdef FEATURE_FORCE_LOAD
-      /* If this request contains the FORCE_PREFIX,
-       * better get rid of it now and set the force flag --oes
+      /*
+       * If this request contains the FORCE_PREFIX and blocks
+       * aren't enforced, get rid of it and set the force flag.
        */
-
       if (strstr(req, FORCE_PREFIX))
       {
-         strclean(req, FORCE_PREFIX);
-         log_error(LOG_LEVEL_FORCE, "Enforcing request \"%s\".\n", req);
-         csp->flags |= CSP_FLAG_FORCED;
+         if (csp->config->feature_flags & RUNTIME_FEATURE_ENFORCE_BLOCKS)
+         {
+            log_error(LOG_LEVEL_FORCE,
+               "Ignored force prefix in request: \"%s\".", req);
+         }
+         else
+         {
+            strclean(req, FORCE_PREFIX);
+            log_error(LOG_LEVEL_FORCE, "Enforcing request: \"%s\".", req);
+            csp->flags |= CSP_FLAG_FORCED;
+         }
       }
 
 #endif /* def FEATURE_FORCE_LOAD */
@@ -1634,7 +1829,7 @@ static void chat(struct client_state *csp)
       /*
        * If we still don't know the request destination,
        * the request is invalid or the client uses
-       * Privoxy without it's knowledge.
+       * Privoxy without its knowledge.
        */
       if (JB_ERR_OK != get_request_destination_elsewhere(csp, headers))
       {
@@ -1744,23 +1939,6 @@ static void chat(struct client_state *csp)
    }
 
 
-   /*
-    * Downgrade http version from 1.1 to 1.0 if +downgrade
-    * action applies
-    */
-   if ( (http->ssl == 0)
-     && (!strcmpic(http->ver, "HTTP/1.1"))
-     && (csp->action->flags & ACTION_DOWNGRADE))
-   {
-      freez(http->ver);
-      http->ver = strdup("HTTP/1.0");
-
-      if (http->ver == NULL)
-      {
-         log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version");
-      }
-   }
-
    /* 
     * Save a copy of the original request for logging
     */
@@ -1773,31 +1951,10 @@ static void chat(struct client_state *csp)
 
    /*
     * (Re)build the HTTP request for non-SSL requests.
-    * If forwarding, use the whole URL, else, use only the path.
     */
    if (http->ssl == 0)
    {
-      freez(http->cmd);
-
-      http->cmd = strdup(http->gpc);
-      string_append(&http->cmd, " ");
-
-      if (fwd->forward_host)
-      {
-         string_append(&http->cmd, http->url);
-      }
-      else
-      {
-         string_append(&http->cmd, http->path);
-      }
-      string_append(&http->cmd, " ");
-      string_append(&http->cmd, http->ver);
-
-      if (http->cmd == NULL)
-      {
-         log_error(LOG_LEVEL_FATAL, "Out of memory writing HTTP command");
-      }
-      log_error(LOG_LEVEL_HEADER, "New HTTP Request-Line: %s", http->cmd);
+      build_request_line(csp, fwd);
    }
    enlist(csp->headers, http->cmd);
 
@@ -1815,6 +1972,13 @@ static void chat(struct client_state *csp)
       enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER);
    }
 
+   hdr = sed(client_patterns, add_client_headers, csp);
+   if (hdr == NULL)
+   {
+      /* FIXME Should handle error properly */
+      log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header");
+   }
+   csp->flags |= CSP_FLAG_CLIENT_HEADER_PARSING_DONE;
 
 #ifdef FEATURE_KILL_POPUPS
    block_popups               = ((csp->action->flags & ACTION_NO_POPUPS) != 0);
@@ -1828,59 +1992,28 @@ static void chat(struct client_state *csp)
    jpeg_inspect               = ((csp->action->flags & ACTION_JPEG_INSPECT) != 0);
 
    /*
-    * We have a request. Now, check to see if we need to
-    * intercept it, i.e. If ..
+    * We have a request. Check if one of the crunchers wants it.
     */
-
-   if (
-       /* We may not forward the request by rfc2616 sect 14.31 */
-       (NULL != (rsp = direct_response(csp)))
-
-       /* or we are enabled and... */
-       || (IS_ENABLED_AND (
-
-            /* ..the request was blocked */
-          ( NULL != (rsp = block_url(csp)))
-
-          /* ..or untrusted */
-#ifdef FEATURE_TRUST
-          || ( NULL != (rsp = trust_url(csp)))
-#endif /* def FEATURE_TRUST */
-
-          /* ..or a redirect kicked in */
-          || ( NULL != (rsp = redirect_url(csp)))
-          ))
-
-       /*
-        * .. or a CGI call was detected and answered.
-        *
-        * This check comes last to give the user the power
-        * to deny acces to some (or all) of the cgi pages.
-        */
-       || (NULL != (rsp = dispatch_cgi(csp)))
-
-                )
+   if (crunch_response_triggered(csp))
    {
       /*
-       * Deliver, log and free the interception
-       * response. Afterwards we're done here.
+       * Yes. The client got the crunch response
+       * and we are done here after cleaning up.
        */
-      send_crunch_response(csp, rsp);
-#ifdef FEATURE_STATISTICS
-      /* Count as a rejected request */
-      csp->flags |= CSP_FLAG_REJECTED;
-#endif /* def FEATURE_STATISTICS */
+      freez(hdr);
+      list_remove_all(csp->headers);
 
       return;
    }
 
-   hdr = sed(client_patterns, add_client_headers, csp);
-   if (hdr == NULL)
-   {
-      /* FIXME Should handle error properly */
-      log_error(LOG_LEVEL_FATAL, "Out of memory parsing client header");
-   }
-
+   /*
+    * The headers can't be removed earlier because
+    * they were still needed for the referrer check
+    * in case of CGI crunches.
+    *
+    * XXX: Would it be worth to move the referrer check
+    * into client_referrer() and set a flag if it's trusted?
+    */
    list_remove_all(csp->headers);
 
    log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path);
@@ -1963,9 +2096,6 @@ static void chat(struct client_state *csp)
        * so just send the "connect succeeded" message to the
        * client, flush the rest, and get out of the way.
        */
-      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 0",
-                csp->ip_addr_str, http->ocmd);
-
       if (write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1))
       {
          freez(hdr);
@@ -2046,6 +2176,18 @@ static void chat(struct client_state *csp)
          {
             log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host);
 
+            if (http->ssl && (fwd->forward_host == NULL))
+            {
+               /*
+                * Just hang up. We already confirmed the client's CONNECT
+                * request with status code 200 and unencrypted content is
+                * no longer welcome.
+                */
+               log_error(LOG_LEVEL_ERROR,
+                  "CONNECT already confirmed. Unable to tell the client about the problem.");
+               return;
+            }
+
             rsp = error_response(csp, "connect-failed", errno);
 
             if(rsp)
@@ -2117,6 +2259,12 @@ static void chat(struct client_state *csp)
                      log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
                   }
 
+                  /*
+                   * Shouldn't happen because this was the second sed run
+                   * and tags are only created for the first one.
+                   */
+                  assert(!crunch_response_triggered(csp));
+
                   if (write_socket(csp->cfd, hdr, strlen(hdr))
                    || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length))
                   {
@@ -2184,6 +2332,18 @@ static void chat(struct client_state *csp)
                      return;
                   }
 
+                  if (crunch_response_triggered(csp))
+                  {
+                     /*
+                      * One of the tags created by a server-header
+                      * tagger triggered a crunch. We already
+                      * delivered the crunch response to the client
+                      * and are done here after cleaning up.
+                      */
+                     freez(hdr);
+                     return;
+                  }
+
                   hdrlen = strlen(hdr);
 
                   if (write_socket(csp->cfd, hdr, hdrlen)
@@ -2281,6 +2441,17 @@ static void chat(struct client_state *csp)
                log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
             }
 
+            if (crunch_response_triggered(csp))
+            {
+               /*
+                * One of the tags created by a server-header
+                * tagger triggered a crunch. We already
+                * delivered the crunch response to the client
+                * and are done here after cleaning up.
+                */
+                freez(hdr);
+                return;
+            }
 #ifdef FEATURE_KILL_POPUPS
             /* Start blocking popups if appropriate. */
 
@@ -3145,7 +3316,7 @@ static void listen_loop(void)
 #ifdef FEATURE_ACL
       if (block_acl(NULL,csp))
       {
-         log_error(LOG_LEVEL_CONNECT, "Connection dropped due to ACL");
+         log_error(LOG_LEVEL_CONNECT, "Connection from %s dropped due to ACL", csp->ip_addr_str);
          close_socket(csp->cfd);
          freez(csp);
          continue;