Don't provide get_header() with the whole client state
[privoxy.git] / jcc.c
diff --git a/jcc.c b/jcc.c
index be8bd07..06ce04a 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.161 2007/12/04 19:44:22 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.177 2008/05/10 11:51:12 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -6,7 +6,7 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.161 2007/12/04 19:44:22 fabiankeil Exp $"
  * Purpose     :  Main file.  Contains main() method, main loop, and
  *                the main connection-handling function.
  *
- * Copyright   :  Written by and Copyright (C) 2001-2007 the SourceForge
+ * Copyright   :  Written by and Copyright (C) 2001-2008 the SourceForge
  *                Privoxy team. http://www.privoxy.org/
  *
  *                Based on the Internet Junkbuster originally written
@@ -33,6 +33,66 @@ const char jcc_rcs[] = "$Id: jcc.c,v 1.161 2007/12/04 19:44:22 fabiankeil Exp $"
  *
  * Revisions   :
  *    $Log: jcc.c,v $
+ *    Revision 1.177  2008/05/10 11:51:12  fabiankeil
+ *    Make the "read the rest of the headers" loop a bit more readable.
+ *
+ *    Revision 1.176  2008/05/10 11:37:57  fabiankeil
+ *    - Instead of logging when the IIS5 hack is enabled, log when it fails.
+ *    - Remove useless comment.
+ *
+ *    Revision 1.175  2008/05/09 18:53:59  fabiankeil
+ *    Fix comment grammar.
+ *
+ *    Revision 1.174  2008/05/07 18:05:53  fabiankeil
+ *    Remove the pointless buffer in client_protocol_is_unsupported().
+ *
+ *    Revision 1.173  2008/05/06 15:09:00  fabiankeil
+ *    Least-effort fix for bug #1821930 (reported by Lee):
+ *    If the response doesn't look like HTTP,
+ *    tell the client and log the problem.
+ *
+ *    Revision 1.172  2008/04/16 16:38:21  fabiankeil
+ *    Don't pass the whole csp structure to flush_socket()
+ *    when it only needs a file descriptor and a buffer.
+ *
+ *    Revision 1.171  2008/03/27 18:27:25  fabiankeil
+ *    Remove kill-popups action.
+ *
+ *    Revision 1.170  2008/03/06 16:33:46  fabiankeil
+ *    If limit-connect isn't used, don't limit CONNECT requests to port 443.
+ *
+ *    Revision 1.169  2008/03/04 18:30:39  fabiankeil
+ *    Remove the treat-forbidden-connects-like-blocks action. We now
+ *    use the "blocked" page for forbidden CONNECT requests by default.
+ *
+ *    Revision 1.168  2008/03/02 12:25:25  fabiankeil
+ *    Also use shiny new connect_port_is_forbidden() in jcc.c.
+ *
+ *    Revision 1.167  2008/02/23 16:57:12  fabiankeil
+ *    Rename url_actions() to get_url_actions() and let it
+ *    use the standard parameter ordering.
+ *
+ *    Revision 1.166  2008/02/23 16:33:43  fabiankeil
+ *    Let forward_url() use the standard parameter ordering
+ *    and mark its second parameter immutable.
+ *
+ *    Revision 1.165  2008/02/02 19:36:56  fabiankeil
+ *    Remove the "Listening ... for local connections only" log message.
+ *    Whether or not remote connections are able to reach Privoxy is up
+ *    to the operating system.
+ *
+ *    Revision 1.164  2007/12/16 18:32:46  fabiankeil
+ *    Prevent the log messages for CONNECT requests to unacceptable
+ *    ports from printing the limit-connect argument as [null] if
+ *    limit-connect hasn't been explicitly enabled.
+ *
+ *    Revision 1.163  2007/12/13 01:47:11  david__schmidt
+ *    Make sure all console-mode apps get a usage() instance
+ *
+ *    Revision 1.162  2007/12/06 17:54:57  fabiankeil
+ *    Reword NO_SERVER_DATA_RESPONSE to make it harder
+ *    to misunderstand what the message is all about.
+ *
  *    Revision 1.161  2007/12/04 19:44:22  fabiankeil
  *    Unbreak trustfile which previously didn't work without
  *    FEATURE_TOGGLE. Fixes BR#1843585, reported by Lee.
@@ -1012,7 +1072,6 @@ http://www.fabiankeil.de/sourcecode/privoxy/
 #include "filters.h"
 #include "loaders.h"
 #include "parsers.h"
-#include "killpopup.h"
 #include "miscutil.h"
 #include "errlog.h"
 #include "jbsockets.h"
@@ -1053,7 +1112,7 @@ static void build_request_line(struct client_state *csp, const struct forward_sp
 static jb_err change_request_destination(struct client_state *csp);
 static void chat(struct client_state *csp);
 static void serve(struct client_state *csp);
-#if defined(unix)
+#if !defined(_WIN32) || defined(_WIN_CONSOLE)
 static void usage(const char *myname);
 #endif
 static void initialize_mutexes(void);
@@ -1118,12 +1177,6 @@ static const char CHEADER[] =
    "Connection: close\r\n\r\n"
    "Invalid header received from client.\r\n";
 
-static const 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";
-
 static const char FTP_RESPONSE[] =
    "HTTP/1.0 400 Invalid request received from client\r\n"
    "Content-Type: text/plain\r\n"
@@ -1153,6 +1206,14 @@ static const char NO_SERVER_DATA_RESPONSE[] =
    "Empty server or forwarder response.\r\n"
    "The connection has been closed but Privoxy didn't receive any data.\r\n";
 
+/* XXX: should be a template */
+static const char INVALID_SERVER_HEADERS_RESPONSE[] =
+   "HTTP/1.0 502 Server or forwarder response invalid\r\n"
+   "Proxy-Agent: Privoxy " VERSION "\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";
+
 #if 0
 /* XXX: should be a template */
 static const char NULL_BYTE_RESPONSE[] =
@@ -1277,8 +1338,6 @@ static void sig_handler(int the_signal)
  *********************************************************************/
 static int client_protocol_is_unsupported(const struct client_state *csp, char *req)
 {
-   char buf[BUFFER_SIZE];
-
    /*
     * If it's a FTP or gopher request, we don't support it.
     *
@@ -1294,21 +1353,26 @@ static int client_protocol_is_unsupported(const struct client_state *csp, char *
     */
    if (!strncmpic(req, "GET ftp://", 10) || !strncmpic(req, "GET gopher://", 13))
    {
+      const char *response = NULL;
+      const char *protocol = NULL;
+
       if (!strncmpic(req, "GET ftp://", 10))
       {
-         strlcpy(buf, FTP_RESPONSE, sizeof(buf));
-         log_error(LOG_LEVEL_ERROR, "%s tried to use Privoxy as FTP proxy: %s",
-            csp->ip_addr_str, req);
+         response = FTP_RESPONSE;
+         protocol = "FTP";
       }
       else
       {
-         strlcpy(buf, GOPHER_RESPONSE, sizeof(buf));
-         log_error(LOG_LEVEL_ERROR, "%s tried to use Privoxy as gopher proxy: %s",
-            csp->ip_addr_str, req);
+         response = GOPHER_RESPONSE;
+         protocol = "GOPHER";
       }
-      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, req);
+      log_error(LOG_LEVEL_ERROR,
+         "%s tried to use Privoxy as %s proxy: %s",
+         csp->ip_addr_str, protocol, req);
+      log_error(LOG_LEVEL_CLF,
+         "%s - - [%T] \"%s\" 400 0", csp->ip_addr_str, req);
       freez(req);
-      write_socket(csp->cfd, buf, strlen(buf));
+      write_socket(csp->cfd, response, strlen(response));
 
       return TRUE;
    }
@@ -1416,7 +1480,7 @@ static jb_err get_server_headers(struct client_state *csp)
    int continue_hack_in_da_house = 0;
    char * header;
 
-   while (((header = get_header(csp)) != NULL) || continue_hack_in_da_house)
+   while (((header = get_header(csp->iob)) != NULL) || continue_hack_in_da_house)
    {
       if (header == NULL)
       {
@@ -1467,7 +1531,6 @@ static jb_err get_server_headers(struct client_state *csp)
          return JB_ERR_PARSE;
       }
 
-      /* Enlist header */
       if (JB_ERR_MEMORY == enlist(csp->headers, header))
       {
          /*
@@ -1890,9 +1953,6 @@ static void chat(struct client_state *csp)
    struct http_request *http;
    int len; /* for buffer sizes (and negative error codes) */
    jb_err err;
-#ifdef FEATURE_KILL_POPUPS
-   int block_popups_now = 0; /* bool, 1==currently blocking popups */
-#endif /* def FEATURE_KILL_POPUPS */
 
    /* Function that does the content filtering for the current request */
    filter_function_ptr content_filter = NULL;
@@ -1928,7 +1988,7 @@ static void chat(struct client_state *csp)
          return;
       }
 
-      req = get_header(csp);
+      req = get_header(csp->iob);
 
    } while ((NULL != req) && ('\0' == *req));
 
@@ -2016,8 +2076,20 @@ static void chat(struct client_state *csp)
    init_list(headers);
    for (;;)
    {
-      if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
+      p = get_header(csp->iob);
+
+      if (p == NULL)
+      {
+         /* There are no additional headers to read. */
+         break;
+      }
+
+      if (*p == '\0')
       {
+         /*
+          * We didn't receive a complete header
+          * line yet, get the rest of it.
+          */
          len = read_socket(csp->cfd, buf, sizeof(buf) - 1);
          if (len <= 0)
          {
@@ -2026,23 +2098,25 @@ static void chat(struct client_state *csp)
             return;
          }
          
-         /*
-          * If there is no memory left for buffering the
-          * request, there is nothing we can do but hang up
-          */
          if (add_to_iob(csp, buf, len))
          {
+            /*
+             * If there is no memory left for buffering the
+             * request, there is nothing we can do but hang up
+             */
             destroy_list(headers);
             return;
          }
-         continue;
       }
-
-      if (p == NULL) break;
-
-      enlist(headers, p);
-      freez(p);
-
+      else
+      {
+         /*
+          * We were able to read a complete
+          * header and can finaly enlist it.
+          */
+         enlist(headers, p);
+         freez(p);
+      }
    }
 
    if (http->host == NULL)
@@ -2078,7 +2152,7 @@ static void chat(struct client_state *csp)
    else
 #endif /* ndef FEATURE_TOGGLE */
    {
-      url_actions(http, csp);
+      get_url_actions(csp, http);
    }
 
    /* 
@@ -2123,7 +2197,8 @@ static void chat(struct client_state *csp)
    }
 
    /* decide how to route the HTTP request */
-   if (NULL == (fwd = forward_url(http, csp)))
+   fwd = forward_url(csp, http);
+   if (NULL == fwd)
    {
       log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!?  This can't happen!");
       /* Never get here - LOG_LEVEL_FATAL causes program exit */
@@ -2166,50 +2241,16 @@ static void chat(struct client_state *csp)
     *
     */
 
-   /*
-    * Check if a CONNECT request is allowable:
-    * In the absence of a +limit-connect action, allow only port 443.
-    * If there is an action, allow whatever matches the specificaton.
-    */
-   if(http->ssl)
+   if (http->ssl && connect_port_is_forbidden(csp))
    {
-      if(  ( !(csp->action->flags & ACTION_LIMIT_CONNECT) && csp->http->port != 443)
-           || (csp->action->flags & ACTION_LIMIT_CONNECT
-              && !match_portlist(csp->action->string[ACTION_STRING_LIMIT_CONNECT], csp->http->port)) )
-      {
-         if (csp->action->flags & ACTION_TREAT_FORBIDDEN_CONNECTS_LIKE_BLOCKS)
-         {
-            /*
-             * The response may confuse some clients,
-             * but makes unblocking easier.
-             *
-             * XXX: It seems to work with all major browsers,
-             * so we should consider returning a body by default someday ... 
-             */
-            log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. "
-               "limit-connect{%s} doesn't allow CONNECT requests to port %d.",
-               csp->ip_addr_str, csp->action->string[ACTION_STRING_LIMIT_CONNECT],
-               csp->http->port);
-            csp->action->flags |= ACTION_BLOCK;
-            http->ssl = 0;
-         }
-         else
-         {
-            write_socket(csp->cfd, CFORBIDDEN, strlen(CFORBIDDEN));
-            log_error(LOG_LEVEL_INFO, "Request from %s denied. "
-               "limit-connect{%s} doesn't allow CONNECT requests to port %d.",
-               csp->ip_addr_str, csp->action->string[ACTION_STRING_LIMIT_CONNECT],
-               csp->http->port);
-            assert(NULL != csp->http->ocmd);
-            log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 403 0", csp->ip_addr_str, csp->http->ocmd);
-
-            list_remove_all(csp->headers);
-            /*
-             * XXX: For consistency we might want to log a crunch message here.
-             */
-            return;
-         }
-      }
+      const char *acceptable_connect_ports =
+         csp->action->string[ACTION_STRING_LIMIT_CONNECT];
+      assert(NULL != acceptable_connect_ports);
+      log_error(LOG_LEVEL_INFO, "Request from %s marked for blocking. "
+         "limit-connect{%s} doesn't allow CONNECT requests to port %d.",
+         csp->ip_addr_str, acceptable_connect_ports, csp->http->port);
+      csp->action->flags |= ACTION_BLOCK;
+      http->ssl = 0;
    }
 
    if (http->ssl == 0)
@@ -2307,7 +2348,7 @@ static void chat(struct client_state *csp)
        */
 
       if (write_socket(csp->sfd, hdr, strlen(hdr))
-       || (flush_socket(csp->sfd, csp) <  0))
+       || (flush_socket(csp->sfd, csp->iob) <  0))
       {
          log_error(LOG_LEVEL_CONNECT, "write header to: %s failed: %E",
                     http->hostport);
@@ -2450,14 +2491,6 @@ static void chat(struct client_state *csp)
           */
          buf[len] = '\0';
 
-#ifdef FEATURE_KILL_POPUPS
-         /* Filter the popups on this read. */
-         if (block_popups_now)
-         {
-            filter_popups(buf, csp);
-         }
-#endif /* def FEATURE_KILL_POPUPS */
-
          /* Normally, this would indicate that we've read
           * as much as the server has sent us and we can
           * close the client connection.  However, Microsoft
@@ -2537,9 +2570,6 @@ static void chat(struct client_state *csp)
              * This is NOT the body, so
              * Let's pretend the server just sent us a blank line.
              */
-            log_error(LOG_LEVEL_INFO,
-               "Malformerd HTTP headers detected and MS IIS5 hack enabled. "
-               "Expect an invalid response or even no response at all.");
             snprintf(buf, sizeof(buf), "\r\n");
             len = (int)strlen(buf);
 
@@ -2589,7 +2619,7 @@ static void chat(struct client_state *csp)
                   hdrlen = strlen(hdr);
 
                   if (write_socket(csp->cfd, hdr, hdrlen)
-                   || ((flushed = flush_socket(csp->cfd, csp)) < 0)
+                   || ((flushed = flush_socket(csp->cfd, csp->iob)) < 0)
                    || (write_socket(csp->cfd, buf, (size_t)len)))
                   {
                      log_error(LOG_LEVEL_CONNECT, "Flush header and buffers to client failed: %E");
@@ -2652,6 +2682,8 @@ static void chat(struct client_state *csp)
                    * and there isn't anything
                    * we can do about it.
                    */
+                  log_error(LOG_LEVEL_INFO,
+                     "MS IIS5 hack didn't produce valid headers.");
                   break;
                }
                else
@@ -2675,6 +2707,29 @@ static void chat(struct client_state *csp)
                return;
             }
 
+            assert(csp->headers->first->str);
+            assert(!http->ssl);
+            if (strncmpic(csp->headers->first->str, "HTTP", 4))
+            {
+               /*
+                * It doesn't look like a HTTP response:
+                * tell the client and log the problem.
+                */
+               if (strlen(csp->headers->first->str) > 30)
+               {
+                  csp->headers->first->str[30] = '\0';
+               }
+               log_error(LOG_LEVEL_ERROR,
+                  "Invalid server or forwarder response. Starts with: %s",
+                  csp->headers->first->str);
+               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));
+               free_http_request(http);
+               return;
+            }
+
             /* we have now received the entire header.
              * filter it and send the result to the client
              */
@@ -2704,20 +2759,6 @@ static void chat(struct client_state *csp)
 
             if (!http->ssl) /* We talk plaintext */
             {
-
-#ifdef FEATURE_KILL_POPUPS
-               /* Start blocking popups if appropriate. */
-               if ((csp->content_type & CT_TEXT) &&               /* It's a text / * MIME-Type */
-                   (csp->action->flags & ACTION_NO_POPUPS) != 0)  /* Policy allows */
-               {
-                  block_popups_now = 1;
-                  /*
-                   * Filter the part of the body that came in the same read
-                   * as the last headers:
-                   */
-                  filter_popups(csp->iob->cur, csp);
-               }
-#endif /* def FEATURE_KILL_POPUPS */
                content_filter = get_filter_function(csp);
             }
             /*
@@ -2731,7 +2772,7 @@ static void chat(struct client_state *csp)
                 */
 
                if (write_socket(csp->cfd, hdr, strlen(hdr))
-                || ((len = flush_socket(csp->cfd, csp)) < 0))
+                || ((len = flush_socket(csp->cfd, csp->iob)) < 0))
                {
                   log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
 
@@ -2758,6 +2799,8 @@ 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;
             }
          }
@@ -2836,7 +2879,7 @@ static int32 server_thread(void *data)
 #endif
 
 
-#if defined(unix)
+#if !defined(_WIN32) || defined(_WIN_CONSOLE)
 /*********************************************************************
  *
  * Function    :  usage
@@ -2865,7 +2908,7 @@ static void usage(const char *myname)
    exit(2);
 
 }
-#endif /* defined(unix) */
+#endif /* #if !defined(_WIN32) || defined(_WIN_CONSOLE) */
 
 
 /*********************************************************************
@@ -2988,7 +3031,7 @@ int main(int argc, const char *argv[])
     * Parse the command line arguments
     *
     * XXX: simply printing usage information in case of
-    * invalid arguments isn't particular user friendly.
+    * invalid arguments isn't particularly user friendly.
     */
    while (++argc_pos < argc)
    {
@@ -3394,16 +3437,7 @@ static jb_socket bind_port_helper(struct configuration_spec * config)
    int result;
    jb_socket bfd;
 
-   if ( (config->haddr != NULL)
-     && (config->haddr[0] == '1')
-     && (config->haddr[1] == '2')
-     && (config->haddr[2] == '7')
-     && (config->haddr[3] == '.') )
-   {
-      log_error(LOG_LEVEL_INFO, "Listening on port %d for local connections only",
-                config->hport);
-   }
-   else if (config->haddr == NULL)
+   if (config->haddr == NULL)
    {
       log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses",
                 config->hport);