Re-factored 'chat()' to become understandable and maintainable as
authoragotneja <agotneja@users.sourceforge.net>
Fri, 12 Jul 2002 04:26:17 +0000 (04:26 +0000)
committeragotneja <agotneja@users.sourceforge.net>
Fri, 12 Jul 2002 04:26:17 +0000 (04:26 +0000)
a first step in adding Transparent Proxy functionality.

Added several new static functions in jcc.c, and moved some data
parameters up into project.h to allow them to be passed between
the new functions.

src/jcc.c
src/project.h

index 82fc0e6..deb6fa5 100644 (file)
--- a/src/jcc.c
+++ b/src/jcc.c
@@ -1,7 +1,7 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 2.0 2002/06/04 14:34:21 jongfoster Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.91 2002/04/08 20:35:58 swa Exp $";
 /*********************************************************************
  *
- * File        :  $Source: /cvsroot/ijbswa/current/src/jcc.c,v $
+ * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
  *
  * Purpose     :  Main file.  Contains main() method, main loop, and
  *                the main connection-handling function.
@@ -33,22 +33,6 @@ const char jcc_rcs[] = "$Id: jcc.c,v 2.0 2002/06/04 14:34:21 jongfoster Exp $";
  *
  * Revisions   :
  *    $Log: jcc.c,v $
- *    Revision 2.0  2002/06/04 14:34:21  jongfoster
- *    Moving source files to src/
- *
- *    Revision 1.92  2002/05/08 16:00:46  oes
- *    Chat's buffer handling:
- *     - Fixed bug with unchecked out-of-mem conditions
- *       while reading client request & server headers
- *     - No longer predict if the buffer limit will be exceeded
- *       in the next read -- check add_to_iob's new
- *       return code. If buffer couldn't be extended
- *       (policy or out-of-mem) while
- *       - reading from client: abort
- *       - reading server headers: send error page
- *       - buffering server body for filter: flush,
- *         and if that fails: send error page
- *
  *    Revision 1.91  2002/04/08 20:35:58  swa
  *    fixed JB spelling
  *
@@ -642,6 +626,15 @@ int g_terminate = 0;
 
 static void listen_loop(void);
 static void chat(struct client_state *csp);
+static jb_err relay_server_traffic( struct client_state *csp ) ;
+static jb_err read_client_headers( struct client_state *csp, struct http_request *http ) ;
+static jb_err process_client_headers( struct client_state *csp, struct http_request *http ) ;
+static jb_err intercept_page( struct client_state *csp, struct http_request *http );
+static jb_err open_forwarding_connection( struct client_state *csp );
+static jb_err send_client_headers_to_server( struct client_state *csp, struct http_request *http,  char* hdr );
+static jb_err is_connect_request_allowed( struct client_state *csp ) ;
+
+
 #ifdef AMIGA
 void serve(struct client_state *csp);
 #else /* ifndef AMIGA */
@@ -666,9 +659,7 @@ const char *pidfile = NULL;
 int received_hup_signal = 0;
 #endif /* defined unix */
 
-/**
- * The vanilla wafer.
- */
+/* The vanilla wafer. */
 static const char VANILLA_WAFER[] =
    "NOTICE=TO_WHOM_IT_MAY_CONCERN_"
    "Do_not_send_me_any_copyrighted_information_other_than_the_"
@@ -678,7 +669,6 @@ static const char VANILLA_WAFER[] =
    "Take_notice_that_I_refuse_to_be_bound_by_any_license_condition_"
    "(copyright_or_otherwise)_applying_to_any_cookie._";
 
-
 /**
  * HTTP header sent when doing HTTPS tunnelling ("CONNECT" method).
  */
@@ -701,7 +691,6 @@ static const char CHEADER[] =
 static const char CFORBIDDEN[] =
    "HTTP/1.0 403 Connection not allowable\r\nX-Hint: If you read this message interactively, then you know why this happens ,-)\r\n\r\n";
 
-
 #if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
 /*********************************************************************
  *
@@ -762,1628 +751,1814 @@ static void sig_handler(int the_signal)
  * 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     :  void
  *
  *********************************************************************/
 static void chat(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
- */
-#ifdef FEATURE_TOGGLE
-#   define IS_TOGGLED_ON_AND (csp->flags & CSP_FLAG_TOGGLED_ON) &&
-#else /* ifndef FEATURE_TOGGLE */
-#   define IS_TOGGLED_ON_AND
-#endif /* ndef FEATURE_TOGGLE */
-#ifdef FEATURE_FORCE_LOAD
-#   define IS_NOT_FORCED_AND !(csp->flags & CSP_FLAG_FORCED) &&
-#else /* ifndef FEATURE_FORCE_LOAD */
-#   define IS_NOT_FORCED_AND
-#endif /* def FEATURE_FORCE_LOAD */
 
-#define IS_ENABLED_AND   IS_TOGGLED_ON_AND IS_NOT_FORCED_AND
 
    char buf[BUFFER_SIZE];
-   char *hdr;
-   char *p;
-   char *req;
+   int len; /* for buffer sizes */
+   char *hdr; 
    fd_set rfds;
    int n;
    jb_socket maxfd;
-   int server_body;
-   int ms_iis5_hack = 0;
    int byte_count = 0;
-   const struct forward_spec * fwd;
    struct http_request *http;
-   int len; /* for buffer sizes */
-#ifdef FEATURE_KILL_POPUPS
-   int block_popups;         /* bool, 1==will block popups */
-   int block_popups_now = 0; /* bool, 1==currently blocking popups */
-#endif /* def FEATURE_KILL_POPUPS */
 
-   int pcrs_filter;        /* bool, 1==will filter through pcrs */
-   int gif_deanimate;      /* bool, 1==will deanimate gifs */
+   http = csp->http;
 
-   /* Function that does the content filtering for the current request */
-   char *(*content_filter)() = NULL;
+   if (read_client_headers(csp, http ) != JB_ERR_OK )
+      return;
+   
 
-   /* Skeleton for HTTP response, if we should intercept the request */
-   struct http_response *rsp;
+   /* Process the client headers */
+   if (process_client_headers(csp, http) != JB_ERR_OK)
+      return;
+   
 
-   http = csp->http;
+   /* Check if this connection request is permitted */
+   if (is_connect_request_allowed(csp) != JB_ERR_OK)
+      return ;
 
    /*
-    * Read the client's request.  Note that since we're not using select() we
-    * could get blocked here if a client connected, then didn't say anything!
+    * We have a valid and legit request. Now, check to see if we need to
+    * intercept it.
     */
+   if (intercept_page(csp, http) != JB_ERR_OK)
+      return;
+  
+   /* 
+    * The page passed the intercept routine, so open a forwarding
+    * connection for conversation
+       */
+   if (open_forwarding_connection(csp) != JB_ERR_OK )
+      return ;
 
-   for (;;)
-   {
-      len = read_socket(csp->cfd, buf, sizeof(buf));
-
-      if (len <= 0) break;      /* error! */
-      
-      /*
-       * If there is no memory left for buffering the
-       * request, there is nothing we can do but hang up
-       */
-      if (add_to_iob(csp, buf, len))
-      {
-         return;
-      }
-
-      req = get_header(csp);
 
-      if (req == NULL)
-      {
-         break;    /* no HTTP request! */
-      }
 
-      if (*req == '\0')
-      {
-         continue;   /* more to come! */
-      }
+   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");
+   }
 
-#ifdef FEATURE_FORCE_LOAD
-      /* If this request contains the FORCE_PREFIX,
-       * better get rid of it now and set the force flag --oes
-       */
+   /* FIXME - where should this go, logically ? */
+   list_remove_all(csp->headers);
 
-      if (strstr(req, FORCE_PREFIX))
-      {
-         strclean(req, FORCE_PREFIX);
-         log_error(LOG_LEVEL_FORCE, "Enforcing request \"%s\".\n", req);
-         csp->flags |= CSP_FLAG_FORCED;
-      }
 
-#endif /* def FEATURE_FORCE_LOAD */
+   if (send_client_headers_to_server(csp, http, hdr) != JB_ERR_OK)
+      return ;
 
-      parse_http_request(req, http, csp);
-      freez(req);
-      break;
-   }
 
-   if (http->cmd == NULL)
-   {
-      strcpy(buf, CHEADER);
-      write_socket(csp->cfd, buf, strlen(buf));
+   /* we're finished with the client's header */
+   freez(hdr);
 
-      log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 400 0", csp->ip_addr_str);
+   maxfd = ( csp->cfd > csp->sfd ) ? csp->cfd : csp->sfd;
 
-      return;
-   }
+   /* pass data between the client and server
+    * until one or the other shuts down the connection.
+    */
 
-   /* decide how to route the HTTP request */
+   /* Set the flag for reading headers to False */
+   /* This is used in relay_server_traffic */
+   csp->all_headers_read = 0 ;
 
-   if ((fwd = forward_url(http, csp)) == NULL)
+   for (;;)
    {
-      log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!?  This can't happen!");
-      /* Never get here - LOG_LEVEL_FATAL causes program exit */
-   }
+      jb_err tmp_ret_val = JB_ERR_GENERIC ;
 
-   /* build the http request to send to the server
-    * we have to do one of the following:
-    *
-    * create = use the original HTTP request to create a new
-    *          HTTP request that has either the path component
-    *          without the http://domainspec (w/path) or the
-    *          full orininal URL (w/url)
-    *          Note that the path and/or the HTTP version may
-    *          have been altered by now.
-    *
-    * connect = Open a socket to the host:port of the server
-    *           and short-circuit server and client socket.
-    *
-    * pass =  Pass the request unchanged if forwarding a CONNECT
-    *         request to a parent proxy. Note that we'll be sending
-    *         the CFAIL message ourselves if connecting to the parent
-    *         fails, but we won't send a CSUCCEED message if it works,
-    *         since that would result in a double message (ours and the
-    *         parent's). After sending the request to the parent, we simply
-    *         tunnel.
-    *
-    * here's the matrix:
-    *                        SSL
-    *                    0        1
-    *                +--------+--------+
-    *                |        |        |
-    *             0  | create | connect|
-    *                | w/path |        |
-    *  Forwarding    +--------+--------+
-    *                |        |        |
-    *             1  | create | pass   |
-    *                | w/url  |        |
-    *                +--------+--------+
-    *
-    */
+#ifdef __OS2__
+      /*
+       * FD_ZERO here seems to point to an errant macro which crashes.
+       * So do this by hand for now...
+       */
+      memset(&rfds,0x00,sizeof(fd_set));
+#else
+      FD_ZERO(&rfds);
+#endif
+      FD_SET(csp->cfd, &rfds);
+      FD_SET(csp->sfd, &rfds);
 
-   /*
-    * Determine the actions for this URL
-    */
-#ifdef FEATURE_TOGGLE
-   if (!(csp->flags & CSP_FLAG_TOGGLED_ON))
-   {
-      /* Most compatible set of actions (i.e. none) */
-      init_current_action(csp->action);
-   }
-   else
-#endif /* ndef FEATURE_TOGGLE */
-   {
-      url_actions(http, csp);
-   }
+      n = select((int)maxfd+1, &rfds, NULL, NULL, NULL);
+
+      if (n < 0)
+      {
+         log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
+         return;
+      }
 
+      /* this is the body of the browser's request
+       * just read it and write it.
+       */
 
-   /*
-    * 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(  ( !(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 (FD_ISSET(csp->cfd, &rfds))
       {
-         strcpy(buf, CFORBIDDEN);
-         write_socket(csp->cfd, buf, strlen(buf));
+         len = read_socket(csp->cfd, buf, sizeof(buf));
 
-         log_error(LOG_LEVEL_CONNECT, "Denying suspicious CONNECT request from %s", csp->ip_addr_str);
-         log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 403 0", csp->ip_addr_str);
+         if (len <= 0)
+         {
+            log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+             csp->ip_addr_str, http->ocmd, byte_count);
+            return ;
+         }
 
-         return;
+         if (write_socket(csp->sfd, buf, (size_t)len))
+         {
+            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+            return;
+         }
+         continue;
       }
-   }
 
+      /*
+       * Check if the server wants to talk, and if so, converse.
+       */
 
-   /*
-    * 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 (FD_ISSET(csp->sfd, &rfds))
+         tmp_ret_val = relay_server_traffic(csp) ;
 
-      if (http->ver == NULL)
-      {
-         log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version");
-      }
+         
+      if (tmp_ret_val == JB_ERR_OK)
+         continue ;
+      else
+         return; 
    }
+   /* We should never get here! */
+   log_error(LOG_LEVEL_FATAL, "chat() : logic error in for() loop" ) ;
+} /* END chat() */
 
-   /* 
-    * Save a copy of the original request for logging
-    */
-   http->ocmd = strdup(http->cmd);
 
-   if (http->ocmd == NULL)
+/*********************************************************************
+ *
+ * Function    :  serve
+ *
+ * Description :  This is little more than chat.  We only "serve" to
+ *                to close any socket that chat may have opened.
+ *
+ * Parameters  :
+ *          1  :  csp = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  N/A
+ *
+ *********************************************************************/
+#ifdef AMIGA
+void serve(struct client_state *csp)
+#else /* ifndef AMIGA */
+static void serve(struct client_state *csp)
+#endif /* def AMIGA */
+{
+   chat(csp);
+   close_socket(csp->cfd);
+
+   if (csp->sfd != JB_INVALID_SOCKET)
    {
-      log_error(LOG_LEVEL_FATAL, "Out of memory copying HTTP request line");
+      close_socket(csp->sfd);
    }
 
-   /*
-    * (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);
+   csp->flags &= ~CSP_FLAG_ACTIVE;
 
-      http->cmd = strdup(http->gpc);
-      string_append(&http->cmd, " ");
+}
 
-      if (fwd->forward_host)
-      {
-         string_append(&http->cmd, http->url);
+
+#ifdef __BEOS__
+/*********************************************************************
+ *
+ * Function    :  server_thread
+ *
+ * Description :  We only exist to call `serve' in a threaded environment.
+ *
+ * Parameters  :
+ *          1  :  data = Current client state (buffers, headers, etc...)
+ *
+ * Returns     :  Always 0.
+ *
+ *********************************************************************/
+static int32 server_thread(void *data)
+{
+   serve((struct client_state *) data);
+   return 0;
+
+}
+#endif
+
+
+/*********************************************************************
+ *
+ * Function    :  usage
+ *
+ * Description :  Print usage info & exit.
+ *
+ * Parameters  :  Pointer to argv[0] for identifying ourselves
+ *
+ * Returns     :  No. ,-)
+ *
+ *********************************************************************/
+void usage(const char *myname)
+{
+   printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n"
+           "Usage: %s [--help] [--version] [--no-daemon] [--pidfile pidfile] [--user user[.group]] [configfile]\n"
+           "Aborting.\n", myname);
+   exit(2);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  main
+ *
+ * Description :  Load the config file and start the listen loop.
+ *                This function is a lot more *sane* with the `load_config'
+ *                and `listen_loop' functions; although it stills does
+ *                a *little* too much for my taste.
+ *
+ * Parameters  :
+ *          1  :  argc = Number of parameters (including $0).
+ *          2  :  argv = Array of (char *)'s to the parameters.
+ *
+ * Returns     :  1 if : can't open config file, unrecognized directive,
+ *                stats requested in multi-thread mode, can't open the
+ *                log file, can't open the jar file, listen port is invalid,
+ *                any load fails, and can't bind port.
+ *
+ *                Else main never returns, the process must be signaled
+ *                to terminate execution.  Or, on Windows, use the
+ *                "File", "Exit" menu option.
+ *
+ *********************************************************************/
+#ifdef __MINGW32__
+int real_main(int argc, const char *argv[])
+#else
+int main(int argc, const char *argv[])
+#endif
+{
+   int argc_pos = 0;
+#ifdef unix
+   struct passwd *pw = NULL;
+   struct group *grp = NULL;
+   char *p;
+#endif
+
+   Argc = argc;
+   Argv = argv;
+
+   configfile =
+#if !defined(_WIN32)
+   "config"
+#else
+   "config.txt"
+#endif
+      ;
+
+   /*
+    * Parse the command line arguments
+    */
+   while (++argc_pos < argc)
+   {
+#if !defined(_WIN32) || defined(_WIN_CONSOLE)
+
+      if (strcmp(argv[argc_pos], "--help") == 0)
+      {
+         usage(argv[0]);
       }
+
+      else if(strcmp(argv[argc_pos], "--version") == 0)
+      {
+         printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n");
+         exit(0);
+      }
+
+      else if (strcmp(argv[argc_pos], "--no-daemon" ) == 0)
+      {
+         no_daemon = 1;
+      }
+#if defined(unix)
+      else if (strcmp(argv[argc_pos], "--pidfile" ) == 0)
+      {
+         if (++argc_pos == argc) usage(argv[0]);
+         pidfile = strdup(argv[argc_pos]);
+      }
+
+      else if (strcmp(argv[argc_pos], "--user" ) == 0)
+      {
+         if (++argc_pos == argc) usage(argv[argc_pos]);
+
+         if ((NULL != (p = strchr(argv[argc_pos], '.'))) && *(p + 1) != '0')
+         {
+            *p++ = '\0';
+            if (NULL == (grp = getgrnam(p)))
+            {
+               log_error(LOG_LEVEL_FATAL, "Group %s not found.", p);
+            }
+         }
+
+         if (NULL == (pw = getpwnam(argv[argc_pos])))
+         {
+            log_error(LOG_LEVEL_FATAL, "User %s not found.", argv[argc_pos]);
+         }
+
+         if (p != NULL) *--p = '\0';
+      }
+#endif /* defined(unix) */
       else
+#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
       {
-         string_append(&http->cmd, http->path);
+         configfile = argv[argc_pos];
       }
 
-      string_append(&http->cmd, " ");
-      string_append(&http->cmd, http->ver);
+   } /* -END- while (more arguments) */
+
+#if defined(unix)
+   if ( *configfile != '/' )
+   {
+      char *abs_file;
+
+      /* make config-filename absolute here */
+      if ( !(basedir = getcwd( NULL, 1024 )))
+      {
+         perror("get working dir failed");
+         exit( 1 );
+      }
+
+      if ( !(abs_file = malloc( strlen( basedir ) + strlen( configfile ) + 5 )))
+      {
+         perror("malloc failed");
+         exit( 1 );
+      }
+      strcpy( abs_file, basedir );
+      strcat( abs_file, "/" );
+      strcat( abs_file, configfile );
+      configfile = abs_file;
+   }
+#endif /* defined unix */
+
+
+   files->next = NULL;
+
+#ifdef AMIGA
+   InitAmiga();
+#elif defined(_WIN32)
+   InitWin32();
+#endif
+
+   /*
+    * Unix signal handling
+    *
+    * Catch the abort, interrupt and terminate signals for a graceful exit
+    * Catch the hangup signal so the errlog can be reopened.
+    * Ignore the broken pipe and child signals
+    *  FIXME: Isn't ignoring the default for SIGCHLD anyway and why ignore SIGPIPE? 
+    */
+#if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
+{
+   int idx;
+   const int catched_signals[] = { SIGABRT, SIGTERM, SIGINT, SIGHUP, 0 };
+   const int ignored_signals[] = { SIGPIPE, SIGCHLD, 0 };
+
+   for (idx = 0; catched_signals[idx] != 0; idx++)
+   {
+      if (signal(catched_signals[idx], sig_handler) == SIG_ERR)
+      {
+         log_error(LOG_LEVEL_FATAL, "Can't set signal-handler for signal %d: %E", catched_signals[idx]);
+      }
+   }
+
+   for (idx = 0; ignored_signals[idx] != 0; idx++)
+   {
+      if (signal(ignored_signals[idx], SIG_IGN) == SIG_ERR)
+      {
+         log_error(LOG_LEVEL_FATAL, "Can't set ignore-handler for signal %d: %E", ignored_signals[idx]);
+      }
+   }
+
+}
+#else /* ifdef _WIN32 */
+# ifdef _WIN_CONSOLE
+   /*
+    * We *are* in a windows console app.
+    * Print a verbose messages about FAQ's and such
+    */
+   printf(win32_blurb);
+# endif /* def _WIN_CONSOLE */
+#endif /* def _WIN32 */
+
+
+   /* Initialize the CGI subsystem */
+   cgi_init_error_messages();
+
+   /*
+    * If runnig on unix and without the --nodaemon
+    * option, become a daemon. I.e. fork, detach
+    * from tty and get process group leadership
+    */
+#if defined(unix)
+{
+   pid_t pid = 0;
+#if 0
+   int   fd;
+#endif
+
+   if (!no_daemon)
+   {
+      pid  = fork();
+
+      if ( pid < 0 ) /* error */
+      {
+         perror("fork");
+         exit( 3 );
+      }
+      else if ( pid != 0 ) /* parent */
+      {
+         int status;
+         pid_t wpid;
+         /*
+          * must check for errors
+          * child died due to missing files aso
+          */
+         sleep( 1 );
+         wpid = waitpid( pid, &status, WNOHANG );
+         if ( wpid != 0 )
+         {
+            exit( 1 );
+         }
+         exit( 0 );
+      }
+      /* child */
+#if 1
+      /* Should be more portable, but not as well tested */
+      setsid();
+#else /* !1 */
+#ifdef __FreeBSD__
+      setpgrp(0,0);
+#else /* ndef __FreeBSD__ */
+      setpgrp();
+#endif /* ndef __FreeBSD__ */
+      fd = open("/dev/tty", O_RDONLY);
+      if ( fd )
+      {
+         /* no error check here */
+         ioctl( fd, TIOCNOTTY,0 );
+         close ( fd );
+      }
+#endif /* 1 */
+      /* FIXME: should close stderr (fd 2) here too, but the test
+       * for existence
+       * and load config file is done in listen_loop() and puts
+       * some messages on stderr there.
+       */
+
+      close( 0 );
+      close( 1 );
+      chdir("/");
+
+   } /* -END- if (!no_daemon) */
+
+   /*
+    * As soon as we have written the PID file, we can switch
+    * to the user and group ID indicated by the --user option
+    */
+   write_pid_file();
+   
+   if (NULL != pw)
+   {
+      if (((NULL != grp) && setgid(grp->gr_gid)) || (setgid(pw->pw_gid)))
+      {
+         log_error(LOG_LEVEL_FATAL, "Cannot setgid(): Insufficient permissions.");
+      }
+      if (setuid(pw->pw_uid))
+      {
+         log_error(LOG_LEVEL_FATAL, "Cannot setuid(): Insufficient permissions.");
+      }
+   }
+}
+#endif /* defined unix */
+
+   listen_loop();
+
+   /* NOTREACHED */
+   return(-1);
+
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  bind_port_helper
+ *
+ * Description :  Bind the listen port.  Handles logging, and aborts
+ *                on failure.
+ *
+ * Parameters  :
+ *          1  :  config = Privoxy configuration.  Specifies port
+ *                         to bind to.
+ *
+ * Returns     :  Port that was opened.
+ *
+ *********************************************************************/
+static jb_socket bind_port_helper(struct configuration_spec * config)
+{
+   int result;
+   jb_socket bfd;
 
-      if (http->cmd == NULL)
-      {
-         log_error(LOG_LEVEL_FATAL, "Out of memory rewiting SSL command");
-      }
+   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)
+   {
+      log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses",
+                config->hport);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s",
+                config->hport, config->haddr);
    }
-   enlist(csp->headers, http->cmd);
 
+   result = bind_port(config->haddr, config->hport, &bfd);
 
-   /*
-    * If the user has not supplied any wafers, and the user has not
-    * told us to suppress the vanilla wafer, then send the vanilla wafer.
-    */
-   if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER])
-       && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0))
+   if (result < 0)
    {
-      enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER);
+      switch(result)
+      {
+         case -3 :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: "
+               "There may be another Privoxy or some other "
+               "proxy running on port %d",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY",
+                      config->hport, config->hport);
+
+         case -2 :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " 
+               "The hostname is not resolvable",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+
+         default :
+            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: because %E",
+               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+      }
+
+      /* shouldn't get here */
+      return JB_INVALID_SOCKET;
    }
 
+   config->need_bind = 0;
 
-#ifdef FEATURE_KILL_POPUPS
-   block_popups               = ((csp->action->flags & ACTION_NO_POPUPS) != 0);
-#endif /* def FEATURE_KILL_POPUPS */
+   return bfd;
+}
 
-   pcrs_filter                = (csp->rlist != NULL) &&  /* There are expressions to be used */
-                                (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER]));
 
-   gif_deanimate              = ((csp->action->flags & ACTION_DEANIMATE) != 0);
+/*********************************************************************
+ *
+ * Function    :  listen_loop
+ *
+ * Description :  bind the listen port and enter a "FOREVER" listening loop.
+ *
+ * Parameters  :  N/A
+ *
+ * Returns     :  Never.
+ *
+ *********************************************************************/
+static void listen_loop(void)
+{
+   struct client_state *csp = NULL;
+   jb_socket bfd;
+   struct configuration_spec * config;
+
+   config = load_config();
 
-   /* grab the rest of the client's headers */
+   bfd = bind_port_helper(config);
 
+#ifdef FEATURE_GRACEFUL_TERMINATION
+   while (!g_terminate)
+#else
    for (;;)
+#endif
    {
-      if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
+#if !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
+      while (waitpid(-1, NULL, WNOHANG) > 0)
       {
-         len = read_socket(csp->cfd, buf, sizeof(buf));
-         if (len <= 0)
-         {
-            log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
-            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))
-         {
-            return;
-         }
+         /* zombie children */
+      }
+#endif /* !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) */
+
+      /*
+       * Free data that was used by died threads
+       */
+      sweep();
+
+#if defined(unix)
+      /*
+       * Re-open the errlog after HUP signal
+       */
+      if (received_hup_signal)
+      {
+         init_error_log(Argv[0], config->logfile, config->debug);
+         received_hup_signal = 0;
+      }
+#endif
+
+#ifdef __OS2__
+#ifdef FEATURE_COOKIE_JAR
+      /*
+       * Need a workaround here: we have to fclose() the jarfile, or we die because it's
+       * already open.  I think unload_configfile() is not being run, which should do
+       * this work.  Until that can get resolved, we'll use this workaround.
+       */
+       if (csp)
+         if(csp->config)
+           if (csp->config->jar)
+           {
+             fclose(csp->config->jar);
+             csp->config->jar = NULL;
+           }
+#endif /* FEATURE_COOKIE_JAR */
+#endif /* __OS2__ */
+
+      if ( NULL == (csp = (struct client_state *) zalloc(sizeof(*csp))) )
+      {
+         log_error(LOG_LEVEL_FATAL, "malloc(%d) for csp failed: %E", sizeof(*csp));
          continue;
       }
 
-      if (p == NULL) break;
+      csp->flags |= CSP_FLAG_ACTIVE;
+      csp->sfd    = JB_INVALID_SOCKET;
 
-      enlist(csp->headers, p);
-      freez(p);
-   }
-   /*
-    * We have a request. Now, check to see if we need to
-    * intercept it, i.e. If ..
-    */
+      csp->config = config = load_config();
 
-   if (
-       /* a CGI call was detected and answered */
-       (NULL != (rsp = dispatch_cgi(csp)))
+      if ( config->need_bind )
+      {
+         /*
+          * Since we were listening to the "old port", we will not see
+          * a "listen" param change until the next IJB 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.
+          *
+          * Which-ever is correct, we will serve 1 more page via the
+          * old settings.  This should probably be a "show-proxy-args"
+          * request.  This should not be a so common of an operation
+          * that this will hurt people's feelings.
+          */
 
-       /* or we are enabled and... */
-       || (IS_ENABLED_AND (
+         close_socket(bfd);
 
-            /* ..the request was blocked */
-          ( NULL != (rsp = block_url(csp)))
+         bfd = bind_port_helper(config);
+      }
 
-          /* ..or untrusted */
-#ifdef FEATURE_TRUST
-          || ( NULL != (rsp = trust_url(csp)))
-#endif /* def FEATURE_TRUST */
+      log_error(LOG_LEVEL_CONNECT, "accept connection ... ");
 
-          /* ..or a fast redirect kicked in */
-#ifdef FEATURE_FAST_REDIRECTS
-          || (((csp->action->flags & ACTION_FAST_REDIRECTS) != 0) &&
-                (NULL != (rsp = redirect_url(csp))))
-#endif /* def FEATURE_FAST_REDIRECTS */
-          ))
-      )
-   {
-      /* Write the answer to the client */
-      if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-       || write_socket(csp->cfd, rsp->body, rsp->content_length))
+      if (!accept_connection(csp, bfd))
       {
-         log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+         log_error(LOG_LEVEL_CONNECT, "accept failed: %E");
+
+#ifdef AMIGA
+         if(!childs)
+         {
+            exit(1);
+         }
+#endif
+         freez(csp);
+         continue;
+      }
+      else
+      {
+         log_error(LOG_LEVEL_CONNECT, "OK");
       }
 
-#ifdef FEATURE_STATISTICS
-      /* Count as a rejected request */
-      csp->flags |= CSP_FLAG_REJECTED;
-#endif /* def FEATURE_STATISTICS */
+#ifdef FEATURE_TOGGLE
+      if (g_bToggleIJB)
+      {
+         csp->flags |= CSP_FLAG_TOGGLED_ON;
+      }
+#endif /* def FEATURE_TOGGLE */
 
-      /* Log (FIXME: All intercept reasons apprear as "crunch" with Status 200) */
-      log_error(LOG_LEVEL_GPC, "%s%s crunch!", http->hostport, http->path);
-      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", csp->ip_addr_str, http->ocmd);
+      if (run_loader(csp))
+      {
+         log_error(LOG_LEVEL_FATAL, "a loader failed - must exit");
+         /* Never get here - LOG_LEVEL_FATAL causes program exit */
+      }
 
-      /* Clean up and return */
-      free_http_response(rsp);
-      return;
-   }
+#ifdef FEATURE_ACL
+      if (block_acl(NULL,csp))
+      {
+         log_error(LOG_LEVEL_CONNECT, "Connection dropped due to ACL");
+         close_socket(csp->cfd);
+         freez(csp);
+         continue;
+      }
+#endif /* def FEATURE_ACL */
 
-   log_error(LOG_LEVEL_GPC, "%s%s", http->hostport, http->path);
+      /* add it to the list of clients */
+      csp->next = clients->next;
+      clients->next = csp;
 
-   if (fwd->forward_host)
-   {
-      log_error(LOG_LEVEL_CONNECT, "via %s:%d to: %s",
-               fwd->forward_host, fwd->forward_port, http->hostport);
-   }
-   else
-   {
-      log_error(LOG_LEVEL_CONNECT, "to %s", http->hostport);
-   }
+      if (config->multi_threaded)
+      {
+         int child_id;
 
-   /* here we connect to the server, gateway, or the forwarder */
+/* this is a switch () statment in the C preprocessor - ugh */
+#undef SELECTED_ONE_OPTION
 
-   csp->sfd = forwarded_connect(fwd, http, csp);
+/* Use Pthreads in preference to native code */
+#if defined(FEATURE_PTHREAD) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         {
+            pthread_t the_thread;
+            pthread_attr_t attrs;
 
-   if (csp->sfd == JB_INVALID_SOCKET)
-   {
-      log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
-                http->hostport);
+            pthread_attr_init(&attrs);
+            pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
+            child_id = (pthread_create(&the_thread, &attrs,
+               (void*)serve, csp) ? -1 : 0);
+            pthread_attr_destroy(&attrs);
+         }
+#endif
 
-      if (errno == EINVAL)
-      {
-         rsp = error_response(csp, "no-such-domain", errno);
+#if defined(_WIN32) && !defined(_CYGWIN) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         child_id = _beginthread(
+            (void (*)(void *))serve,
+            64 * 1024,
+            csp);
+#endif
 
-         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 404 0",
-                   csp->ip_addr_str, http->ocmd);
-      }
-      else
-      {
-         rsp = error_response(csp, "connect-failed", errno);
+#if defined(__OS2__) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         child_id = _beginthread(
+            (void(* _Optlink)(void*))serve,
+            NULL,
+            64 * 1024,
+            csp);
+#endif
 
-         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
-                   csp->ip_addr_str, http->ocmd);
-      }
+#if defined(__BEOS__) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         {
+            thread_id tid = spawn_thread
+               (server_thread, "server", B_NORMAL_PRIORITY, csp);
 
+            if ((tid >= 0) && (resume_thread(tid) == B_OK))
+            {
+               child_id = (int) tid;
+            }
+            else
+            {
+               child_id = -1;
+            }
+         }
+#endif
 
-      /* Write the answer to the client */
-      if(rsp)
-      {
-         if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-          || write_socket(csp->cfd, rsp->body, rsp->content_length))
+#if defined(AMIGA) && !defined(SELECTED_ONE_OPTION)
+#define SELECTED_ONE_OPTION
+         csp->cfd = ReleaseSocket(csp->cfd, -1);
+         if((child_id = (int)CreateNewProcTags(
+            NP_Entry, (ULONG)server_thread,
+            NP_Output, Output(),
+            NP_CloseOutput, FALSE,
+            NP_Name, (ULONG)"privoxy child",
+            NP_StackSize, 200*1024,
+            TAG_DONE)))
          {
-            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+            childs++;
+            ((struct Task *)child_id)->tc_UserData = csp;
+            Signal((struct Task *)child_id, SIGF_SINGLE);
+            Wait(SIGF_SINGLE);
          }
-      }
+#endif
 
-      free_http_response(rsp);
-      return;
-   }
+#if !defined(SELECTED_ONE_OPTION)
+         child_id = fork();
 
-   log_error(LOG_LEVEL_CONNECT, "OK");
+         /* This block is only needed when using fork().
+          * When using threads, the server thread was
+          * created and run by the call to _beginthread().
+          */
+         if (child_id == 0)   /* child */
+         {
+            serve(csp);
+            _exit(0);
 
-   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");
-   }
+         }
+         else if (child_id > 0) /* parent */
+         {
+            /* in a fork()'d environment, the parent's
+             * copy of the client socket and the CSP
+             * are not used.
+             */
 
-   list_remove_all(csp->headers);
+#if !defined(_WIN32) && defined(__CYGWIN__)
+            wait( NULL );
+#endif /* !defined(_WIN32) && defined(__CYGWIN__) */
+            close_socket(csp->cfd);
+            csp->flags &= ~CSP_FLAG_ACTIVE;
+         }
+#endif
 
-   if (fwd->forward_host || (http->ssl == 0))
-   {
-      /* write the client's (modified) header to the server
-       * (along with anything else that may be in the buffer)
-       */
+#undef SELECTED_ONE_OPTION
+/* end of cpp switch () */
 
-      if (write_socket(csp->sfd, hdr, strlen(hdr))
-       || (flush_socket(csp->sfd, csp) <  0))
-      {
-         log_error(LOG_LEVEL_CONNECT, "write header to: %s failed: %E",
-                    http->hostport);
+         if (child_id < 0) /* failed */
+         {
+            char buf[BUFFER_SIZE];
 
-         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
-                   csp->ip_addr_str, http->ocmd);
+            log_error(LOG_LEVEL_ERROR, "can't fork: %E");
 
-         rsp = error_response(csp, "connect-failed", errno);
+            sprintf(buf , "Privoxy: can't fork: errno = %d", errno);
 
-         if(rsp)
-         {
-            if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-             || write_socket(csp->cfd, rsp->body, rsp->content_length))
-            {
-               log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
-            }
+            write_socket(csp->cfd, buf, strlen(buf));
+            close_socket(csp->cfd);
+            csp->flags &= ~CSP_FLAG_ACTIVE;
+            sleep(5);
+            continue;
          }
-
-         free_http_response(rsp);
-         freez(hdr);
-         return;
       }
-   }
-   else
-   {
-      /*
-       * We're running an SSL tunnel and we're not forwarding,
-       * 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 2\n",
-                csp->ip_addr_str, http->ocmd);
-
-      if (write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1))
+      else
       {
-         freez(hdr);
-         return;
+         serve(csp);
       }
-      IOB_RESET(csp);
    }
 
-   /* we're finished with the client's header */
-   freez(hdr);
-
-   maxfd = ( csp->cfd > csp->sfd ) ? csp->cfd : csp->sfd;
+   /* NOTREACHED unless FEATURE_GRACEFUL_TERMINATION is defined */
 
-   /* pass data between the client and server
-    * until one or the other shuts down the connection.
-    */
+   /* Clean up.  Aim: free all memory (no leaks) */
+#ifdef FEATURE_GRACEFUL_TERMINATION
 
-   server_body = 0;
+   log_error(LOG_LEVEL_ERROR, "Graceful termination requested");
 
-   for (;;)
-   {
-#ifdef __OS2__
-      /*
-       * FD_ZERO here seems to point to an errant macro which crashes.
-       * So do this by hand for now...
-       */
-      memset(&rfds,0x00,sizeof(fd_set));
-#else
-      FD_ZERO(&rfds);
+   unload_current_config_file();
+   unload_current_actions_file();
+   unload_current_re_filterfile();
+#ifdef FEATURE_TRUST
+   unload_current_trust_file();
 #endif
-      FD_SET(csp->cfd, &rfds);
-      FD_SET(csp->sfd, &rfds);
 
-      n = select((int)maxfd+1, &rfds, NULL, NULL, NULL);
+   if (config->multi_threaded)
+   {
+      int i = 60;
+      do
+      {
+         sleep(1);
+         sweep();
+      } while ((clients->next != NULL) && (--i > 0));
 
-      if (n < 0)
+      if (i <= 0)
       {
-         log_error(LOG_LEVEL_ERROR, "select() failed!: %E");
-         return;
+         log_error(LOG_LEVEL_ERROR, "Graceful termination failed - still some live clients after 1 minute wait.");
       }
+   }
+   sweep();
+   sweep();
 
-      /* this is the body of the browser's request
-       * just read it and write it.
-       */
+#if defined(unix)
+   free(basedir);
+#endif
+#if defined(_WIN32) && !defined(_WIN_CONSOLE)
+   /* Cleanup - remove taskbar icon etc. */
+   TermLogWindow();
+#endif
 
-      if (FD_ISSET(csp->cfd, &rfds))
-      {
-         len = read_socket(csp->cfd, buf, sizeof(buf));
+   exit(0);
+#endif /* FEATURE_GRACEFUL_TERMINATION */
 
-         if (len <= 0)
-         {
-            break; /* "game over, man" */
-         }
+}
 
-         if (write_socket(csp->sfd, buf, (size_t)len))
-         {
-            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
-            return;
-         }
-         continue;
-      }
+/*********************************************************************
+ *
+ * Function    :  relay_server_traffic
+ *
+ * Description :  receive traffic from the server and relay it
+ *                to the client, with some processing
+ *
+ * Parameters  :
+ *          1  :  client_state structure
+ *                         
+ *
+ * Returns     :  JB_ERR_OK if there may be more work
+ *             :  other values for errors
+ *
+ **********************************************************************/
+
+static jb_err relay_server_traffic( struct client_state *csp )
+{
+#ifdef FEATURE_KILL_POPUPS
+   int block_popups;         /* bool, 1==will block popups */
+   int block_popups_now = 0; /* bool, 1==currently blocking popups */
+#endif /* def FEATURE_KILL_POPUPS */
+   
+  char buf[BUFFER_SIZE];
+  int len; /* for buffer sizes */
+  int ms_iis5_hack = 0;
+  int byte_count = 0;
+  char *hdr;
+  char *p;
 
-      /*
-       * The server wants to talk.  It could be the header or the body.
-       * If `hdr' is null, then it's the header otherwise it's the body.
-       * FIXME: Does `hdr' really mean `host'? No.
-       */
+  /* Skeleton for HTTP response, if we should intercept the request */
+  struct http_response *rsp;
 
+  int pcrs_filter;        /* bool, 1==will filter through pcrs */
+  int gif_deanimate;      /* bool, 1==will deanimate gifs */
 
-      if (FD_ISSET(csp->sfd, &rfds))
-      {
-         fflush( 0 );
-         len = read_socket(csp->sfd, buf, sizeof(buf) - 1);
+#ifdef FEATURE_KILL_POPUPS
+   block_popups               = ((csp->action->flags & ACTION_NO_POPUPS) != 0);
+#endif /* def FEATURE_KILL_POPUPS */
 
-         if (len < 0)
-         {
-            log_error(LOG_LEVEL_ERROR, "read from: %s failed: %E", http->host);
+   pcrs_filter                = (csp->rlist != NULL) &&  /* There are expressions to be used */
+                                (!list_is_empty(csp->action->multi[ACTION_MULTI_FILTER]));
 
-            log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
-                      csp->ip_addr_str, http->ocmd);
+   gif_deanimate              = ((csp->action->flags & ACTION_DEANIMATE) != 0);
 
-            rsp = error_response(csp, "connect-failed", errno);
 
-            if(rsp)
-            {
-               if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-                || write_socket(csp->cfd, rsp->body, rsp->content_length))
-               {
-                  log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
-               }
-            }
+  fflush (0);
+  len = read_socket (csp->sfd, buf, sizeof (buf) - 1);
 
-            free_http_response(rsp);
-            return;
-         }
+  if (len < 0)
+    {
+      log_error (LOG_LEVEL_ERROR, "read from: %s failed: %E", csp->http->host);
 
-         /* Add a trailing zero.  This lets filter_popups
-          * use string operations.
-          */
-         buf[len] = '\0';
+      log_error (LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                csp->ip_addr_str, csp->http->ocmd);
 
-#ifdef FEATURE_KILL_POPUPS
-         /* Filter the popups on this read. */
-         if (block_popups_now)
-         {
-            filter_popups(buf, csp);
-         }
-#endif /* def FEATURE_KILL_POPUPS */
+      rsp = error_response (csp, "connect-failed", errno);
 
-         /* 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
-          * in its wisdom has released IIS/5 with a bug that
-          * prevents it from sending the trailing \r\n in
-          * a 302 redirect header (and possibly other headers).
-          * To work around this if we've haven't parsed
-          * a full header we'll append a trailing \r\n
-          * and see if this now generates a valid one.
-          *
-          * This hack shouldn't have any impacts.  If we've
-          * already transmitted the header or if this is a
-          * SSL connection, then we won't bother with this
-          * hack.  So we only work on partially received
-          * headers.  If we append a \r\n and this still
-          * doesn't generate a valid header, then we won't
-          * transmit anything to the client.
-          */
-         if (len == 0)
+      if (rsp)
+      {
+         if (write_socket (csp->cfd, rsp->head, rsp->head_length)
+            || write_socket (csp->cfd, rsp->body, rsp->content_length))
          {
+            log_error (LOG_LEVEL_ERROR, "write to: %s failed: %E",
+                       csp->http->host);
+         }
+      }
 
-            if (server_body || http->ssl)
-            {
-               /*
-                * If we have been buffering up the document,
-                * now is the time to apply content modification
-                * and send the result to the client.
-                */
-               if (content_filter)
-               {
-                  /*
-                   * If the content filter fails, use the original
-                   * buffer and length.
-                   * (see p != NULL ? p : csp->iob->cur below)
-                   */
-                  if (NULL == (p = (*content_filter)(csp)))
-                  {
-                     csp->content_length = csp->iob->eod - csp->iob->cur;
-                  }
-
-                  hdr = sed(server_patterns, add_server_headers, csp);
-                  if (hdr == NULL)
-                  {
-                     /* FIXME Should handle error properly */
-                     log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
-                  }
-
-                  if (write_socket(csp->cfd, hdr, strlen(hdr))
-                   || write_socket(csp->cfd, p != NULL ? p : csp->iob->cur, csp->content_length))
-                  {
-                     log_error(LOG_LEVEL_ERROR, "write modified content to client failed: %E");
-                     return;
-                  }
-
-                  freez(hdr);
-                  if (NULL != p) {
-                     freez(p);
-                  }
-               }
-
-               break; /* "game over, man" */
-            }
+      free_http_response (rsp);
+      return JB_ERR_GENERIC;
+    }
 
-            /*
-             * This is NOT the body, so
-             * Let's pretend the server just sent us a blank line.
-             */
-            len = sprintf(buf, "\r\n");
+  /* Add a trailing zero.  This lets filter_popups
+   * use string operations.
+   */
+  buf[len] = '\0';
 
-            /*
-             * Now, let the normal header parsing algorithm below do its
-             * job.  If it fails, we'll exit instead of continuing.
-             */
+#ifdef FEATURE_KILL_POPUPS
+  /* Filter the popups on this read. */
+  if (block_popups_now)
+    {
+      filter_popups (buf, csp);
+    }
+#endif /* def FEATURE_KILL_POPUPS */
 
-            ms_iis5_hack = 1;
-         }
 
+  /* 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
+   * in its wisdom has released IIS/5 with a bug that
+   * prevents it from sending the trailing \r\n in
+   * a 302 redirect header (and possibly other headers).
+   * To work around this if we've haven't parsed
+   * a full header we'll append a trailing \r\n
+   * and see if this now generates a valid one.
+   *
+   * This hack shouldn't have any impacts.  If we've
+   * already transmitted the header or if this is a
+   * SSL connection, then we won't bother with this
+   * hack.  So we only work on partially received
+   * headers.  If we append a \r\n and this still
+   * doesn't generate a valid header, then we won't
+   * transmit anything to the client.
+   */
+  if (len == 0)
+    {
+
+      if (csp->all_headers_read || csp->http->ssl)
+      {
          /*
-          * If this is an SSL connection or we're in the body
-          * of the server document, just write it to the client,
-          * unless we need to buffer the body for later content-filtering
+          * If we have been buffering up the document,
+          * now is the time to apply content modification
+          * and send the result to the client.
           */
-
-         if (server_body || http->ssl)
+         if (csp->content_filter)
          {
-            if (content_filter)
+            /*
+             * If the content filter fails, use the original
+             * buffer and length.
+             * (see p != NULL ? p : csp->iob->cur below)
+             */
+            if (NULL == (p = (*csp->content_filter) (csp)))
             {
-               /*
-                * If there is no memory left for buffering the content, or the buffer limit
-                * has been reached, switch to non-filtering mode, i.e. make & write the
-                * header, flush the iob and buf, and get out of the way.
-                */
-               if (add_to_iob(csp, buf, len))
-               {
-                  size_t hdrlen;
-                  int flushed;
-
-                  log_error(LOG_LEVEL_ERROR, "Flushing header and buffers. Stepping back from filtering.");
-
-                  hdr = sed(server_patterns, add_server_headers, csp);
-                  if (hdr == NULL)
-                  {
-                     /* 
-                      * Memory is too tight to even generate the header.
-                      * Send our static "Out-of-memory" page.
-                      */
-                     log_error(LOG_LEVEL_ERROR, "Out of memory while trying to flush.");
-                     rsp = cgi_error_memory();
-
-                     if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-                         || write_socket(csp->cfd, rsp->body, rsp->content_length))
-                     {
-                        log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
-                     }
-                     return;
-                  }
-
-                  hdrlen = strlen(hdr);
-
-                  if (write_socket(csp->cfd, hdr, hdrlen)
-                   || ((flushed = flush_socket(csp->cfd, csp)) < 0)
-                   || (write_socket(csp->cfd, buf, len)))
-                  {
-                     log_error(LOG_LEVEL_CONNECT, "Flush header and buffers to client failed: %E");
-
-                     freez(hdr);
-                     return;
-                  }
-
-                  byte_count += hdrlen + flushed + len;
-                  freez(hdr);
-                  content_filter = NULL;
-                  server_body = 1;
-
-               }
+               csp->content_length = csp->iob->eod - csp->iob->cur;
             }
-            else
+
+            hdr = sed (server_patterns, add_server_headers, csp);
+            if (hdr == NULL)
             {
-               if (write_socket(csp->cfd, buf, (size_t)len))
-               {
-                  log_error(LOG_LEVEL_ERROR, "write to client failed: %E");
-                  return;
-               }
+               /* FIXME Should handle error properly */
+               log_error (LOG_LEVEL_FATAL,
+                  "Out of memory parsing server header");
+
             }
-            byte_count += len;
-            continue;
-         }
-         else
-         {
-            /* we're still looking for the end of the
-             * server's header ... (does that make header
-             * parsing an "out of body experience" ?
-             */
 
-            /* 
-             * buffer up the data we just read.  If that fails, 
-             * there's little we can do but send our static
-             * out-of-memory page.
-             */
-            if (add_to_iob(csp, buf, len))
+            if (write_socket (csp->cfd, hdr, strlen (hdr))
+               || write_socket (csp->cfd, p != NULL ? p : csp->iob->cur,
+                              csp->content_length))
             {
-               log_error(LOG_LEVEL_ERROR, "Out of memory while looking for end of server headers.");
-               rsp = cgi_error_memory();
-               
-               if (write_socket(csp->cfd, rsp->head, rsp->head_length)
-                   || write_socket(csp->cfd, rsp->body, rsp->content_length))
-               {
-                  log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
-               }
-               return;
+               log_error (LOG_LEVEL_ERROR,
+                  "write modified content to client failed: %E");
+               return JB_ERR_GENERIC;
             }
 
-            /* get header lines from the iob */
-
-            while ((p = get_header(csp)) != NULL)
+            freez (hdr);
+            if (NULL != p)
             {
-               if (*p == '\0')
-               {
-                  /* see following note */
-                  break;
-               }
-               enlist(csp->headers, p);
-               freez(p);
+               freez (p);
             }
+         }
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+            csp->ip_addr_str, csp->http->ocmd, byte_count);
+         return JB_ERR_GENERIC;
+      }
 
-            /* NOTE: there are no "empty" headers so
-             * if the pointer `p' is not NULL we must
-             * assume that we reached the end of the
-             * buffer before we hit the end of the header.
-             */
+      /*
+       * This is NOT the body, so
+       * Let's pretend the server just sent us a blank line.
+       */
+      len = sprintf (buf, "\r\n");
 
-            if (p)
-            {
-               if (ms_iis5_hack)
-               {
-                  /* Well, we tried our MS IIS/5
-                   * hack and it didn't work.
-                   * The header is incomplete
-                   * and there isn't anything
-                   * we can do about it.
-                   */
-                  break;
-               }
-               else
-               {
-                  /* Since we have to wait for
-                   * more from the server before
-                   * we can parse the headers
-                   * we just continue here.
-                   */
-                  continue;
-               }
-            }
+      /*
+       * Now, let the normal header parsing algorithm below do its
+       * job.  If it fails, we'll exit instead of continuing.
+       */
 
-            /* we have now received the entire header.
-             * filter it and send the result to the client
-             */
+      ms_iis5_hack = 1;
+    }
+
+  /*
+   * If this is an SSL connection or we're in the body
+   * of the server document, just write it to the client,
+   * unless we need to buffer the body for later content-filtering
+   */
+
+  if (csp->all_headers_read || csp->http->ssl)
+    {
+      if (csp->content_filter)
+      {
+         add_to_iob (csp, buf, len);
 
-            hdr = sed(server_patterns, add_server_headers, csp);
+         /*
+          * If the buffer limit will be reached on the next read,
+          * switch to non-filtering mode, i.e. make & write the
+          * header, flush the socket and get out of the way.
+          */
+         if (((size_t) (csp->iob->eod - csp->iob->buf)) +
+              (size_t) BUFFER_SIZE > csp->config->buffer_limit)
+         {
+            size_t hdrlen;
+
+            log_error (LOG_LEVEL_ERROR,
+                        "Buffer size limit reached! Flushing and stepping back.");
+
+            hdr = sed (server_patterns, add_server_headers, csp);
             if (hdr == NULL)
             {
                /* FIXME Should handle error properly */
-               log_error(LOG_LEVEL_FATAL, "Out of memory parsing server header");
+               log_error (LOG_LEVEL_FATAL,
+                  "Out of memory parsing server header");
             }
 
-#ifdef FEATURE_KILL_POPUPS
-            /* Start blocking popups if appropriate. */
+            hdrlen = strlen (hdr);
+            byte_count += hdrlen;
 
-            if ((csp->content_type & CT_TEXT) &&  /* It's a text / * MIME-Type */
-                !http->ssl    &&                  /* We talk plaintext */
-                block_popups)                     /* Policy allows */
+            if (write_socket (csp->cfd, hdr, hdrlen)
+               || ((len = flush_socket (csp->cfd, csp)) < 0))
             {
-               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);
-            }
+               log_error (LOG_LEVEL_CONNECT,
+                  "write header to client failed: %E");
 
-#endif /* def FEATURE_KILL_POPUPS */
+               freez (hdr);
+               return JB_ERR_GENERIC;
+            }
 
-            /* Buffer and pcrs filter this if appropriate. */
+            freez (hdr);
+            byte_count += len;
 
-            if ((csp->content_type & CT_TEXT) &&  /* It's a text / * MIME-Type */
-                !http->ssl    &&                  /* We talk plaintext */
-                pcrs_filter)                      /* Policy allows */
-            {
-               content_filter = pcrs_filter_response;
-            }
+            csp->content_filter = NULL;
+            csp->all_headers_read = 1;
 
-            /* Buffer and gif_deanimate this if appropriate. */
+         }
+      }
+      else
+      {
+         if (write_socket (csp->cfd, buf, (size_t) len))
+         {
+            log_error (LOG_LEVEL_ERROR, "write to client failed: %E");
+            return JB_ERR_GENERIC;
+         }
+      }
+      byte_count += len;
+      return JB_ERR_OK ;
+    }
+  else
+    {
+      /* we're still looking for the end of the
+       * server's header ... (does that make header
+       * parsing an "out of body experience" ?
+       */
 
-            if ((csp->content_type & CT_GIF)  &&  /* It's a image/gif MIME-Type */
-                !http->ssl    &&                  /* We talk plaintext */
-                gif_deanimate)                    /* Policy allows */
-            {
-               content_filter = gif_deanimate_response;
-            }
+      /* buffer up the data we just read */
+      add_to_iob (csp, buf, len);
 
-            /*
-             * Only write if we're not buffering for content modification
-             */
-            if (!content_filter)
-            {
-               /* write the server's (modified) header to
-                * the client (along with anything else that
-                * may be in the buffer)
-                */
-
-               if (write_socket(csp->cfd, hdr, strlen(hdr))
-                || ((len = flush_socket(csp->cfd, csp)) < 0))
-               {
-                  log_error(LOG_LEVEL_CONNECT, "write header to client failed: %E");
-
-                  /* the write failed, so don't bother
-                   * mentioning it to the client...
-                   * it probably can't hear us anyway.
-                   */
-                  freez(hdr);
-                  return;
-               }
-
-               byte_count += len;
-            }
+      /* get header lines from the iob */
 
-            /* we're finished with the server's header */
+      while ((p = get_header (csp)) != NULL)
+      {
+         if (*p == '\0')
+         {
+            /* see following note */
+            log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
+               csp->ip_addr_str, csp->http->ocmd, byte_count);
+            return JB_ERR_GENERIC;
+         }
+         enlist (csp->headers, p);
+         freez (p);
+      }
 
-            freez(hdr);
-            server_body = 1;
+      /* NOTE: there are no "empty" headers so
+       * if the pointer `p' is not NULL we must
+       * assume that we reached the end of the
+       * buffer before we hit the end of the header.
+       */
 
-            /* If this was a MS IIS/5 hack then it means
-             * the server has already closed the
-             * connection.  Nothing more to read.  Time
-             * to bail.
+      if (p)
+      {
+         if (ms_iis5_hack)
+         {
+            /* Well, we tried our MS IIS/5
+             * hack and it didn't work.
+             * The header is incomplete
+             * and there isn't anything
+             * we can do about it.
              */
-            if (ms_iis5_hack)
-            {
-               break;
-            }
+            log_error(LOG_LEVEL_CLF, "Incomplete Server Header: %s - - [%T] \"%s\" 200 %d",
+                csp->ip_addr_str, csp->http->ocmd, byte_count);
+            return JB_ERR_GENERIC;
          }
-         continue;
+         else
+         {
+            /* Since we have to wait for
+             * more from the server before
+             * we can parse the headers
+             * we just continue here.
+             */
+            return JB_ERR_OK;
+         }
+      }      
+      
+      /* we have now received the entire header.
+       * filter it and send the result to the client
+       */
+
+      hdr = sed (server_patterns, add_server_headers, csp);
+      if (hdr == NULL)
+      {
+         /* FIXME Should handle error properly */
+         log_error (LOG_LEVEL_FATAL, "Out of memory parsing server header");
       }
 
-      return; /* huh? we should never get here */
-   }
+         
 
-   log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 %d",
-             csp->ip_addr_str, http->ocmd, byte_count);
-}
+#ifdef FEATURE_KILL_POPUPS
+      /* Start blocking popups if appropriate. */
 
+      if ((csp->content_type & CT_TEXT) &&   /* It's a text / * MIME-Type */
+          !csp->http->ssl &&                 /* We talk plaintext */
+          block_popups)                      /* 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);
+      }
 
-/*********************************************************************
- *
- * Function    :  serve
- *
- * Description :  This is little more than chat.  We only "serve" to
- *                to close any socket that chat may have opened.
- *
- * Parameters  :
- *          1  :  csp = Current client state (buffers, headers, etc...)
- *
- * Returns     :  N/A
- *
- *********************************************************************/
-#ifdef AMIGA
-void serve(struct client_state *csp)
-#else /* ifndef AMIGA */
-static void serve(struct client_state *csp)
-#endif /* def AMIGA */
-{
-   chat(csp);
-   close_socket(csp->cfd);
+#endif /* def FEATURE_KILL_POPUPS */
 
-   if (csp->sfd != JB_INVALID_SOCKET)
-   {
-      close_socket(csp->sfd);
-   }
+        /* Buffer and pcrs filter this if appropriate. */
 
-   csp->flags &= ~CSP_FLAG_ACTIVE;
+      if ((csp->content_type & CT_TEXT) &&       /* It's a text / * MIME-Type */
+          !csp->http->ssl &&                     /* We talk plaintext */
+          pcrs_filter)                           /* Policy allows */
+      {
+         csp->content_filter = pcrs_filter_response;
+      }
 
-}
+      /* Buffer and gif_deanimate this if appropriate. */
 
+      if ((csp->content_type & CT_GIF) &&        /* It's a image/gif MIME-Type */
+          !csp->http->ssl &&                     /* We talk plaintext */
+          gif_deanimate)                               /* Policy allows */
+      {
+         csp->content_filter = gif_deanimate_response;
+      }
+      /*
+       * Only write if we're not buffering for content modification
+       */
+      if (!csp->content_filter)
+      {
+         /* write the server's (modified) header to
+          * the client (along with anything else that
+          * may be in the buffer)
+          */
+         if (write_socket (csp->cfd, hdr, strlen (hdr))
+             || ((len = flush_socket (csp->cfd, csp)) < 0))
+         {
+            log_error (LOG_LEVEL_CONNECT,
+               "write header to client failed: %E");
 
-#ifdef __BEOS__
-/*********************************************************************
- *
- * Function    :  server_thread
- *
- * Description :  We only exist to call `serve' in a threaded environment.
- *
- * Parameters  :
- *          1  :  data = Current client state (buffers, headers, etc...)
- *
- * Returns     :  Always 0.
- *
- *********************************************************************/
-static int32 server_thread(void *data)
-{
-   serve((struct client_state *) data);
-   return 0;
+            /* the write failed, so don't bother
+             * mentioning it to the client...
+             * it probably can't hear us anyway.
+             */
+            freez (hdr);
+            return JB_ERR_GENERIC;
+         }
 
-}
-#endif
+         byte_count += len;
+      }
 
+      /* we're finished with the server's header */
 
-/*********************************************************************
- *
- * Function    :  usage
- *
- * Description :  Print usage info & exit.
- *
- * Parameters  :  Pointer to argv[0] for identifying ourselves
- *
- * Returns     :  No. ,-)
- *
- *********************************************************************/
-void usage(const char *myname)
-{
-   printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n"
-           "Usage: %s [--help] [--version] [--no-daemon] [--pidfile pidfile] [--user user[.group]] [configfile]\n"
-           "Aborting.\n", myname);
-   exit(2);
+      freez (hdr);
+      csp->all_headers_read = 1;
 
-}
+      /* If this was a MS IIS/5 hack then it means
+       * the server has already closed the
+       * connection.  Nothing more to read.  Time
+       * to bail.
+       */
+      if (ms_iis5_hack)
+      {
+         return JB_ERR_GENERIC ;
+      }
+    }
+   return JB_ERR_OK ;
+} /* END relay_server_traffic() */
 
 
 /*********************************************************************
  *
- * Function    :  main
+ * Function    :  read_client_headers
  *
- * Description :  Load the config file and start the listen loop.
- *                This function is a lot more *sane* with the `load_config'
- *                and `listen_loop' functions; although it stills does
- *                a *little* too much for my taste.
+ * Description :  read all the client headers, and fill in the
+ *                http* structure
  *
  * Parameters  :
- *          1  :  argc = Number of parameters (including $0).
- *          2  :  argv = Array of (char *)'s to the parameters.
- *
- * Returns     :  1 if : can't open config file, unrecognized directive,
- *                stats requested in multi-thread mode, can't open the
- *                log file, can't open the jar file, listen port is invalid,
- *                any load fails, and can't bind port.
- *
- *                Else main never returns, the process must be signaled
- *                to terminate execution.  Or, on Windows, use the
- *                "File", "Exit" menu option.
+ *          1  :  client_state structure
+ *          2  :  http_request structure
+ *                         
  *
+ * Returns     :  JB_ERR_OK  - headers read and parsed ok
+ *             :  other values - an error occurred.
  *********************************************************************/
-#ifdef __MINGW32__
-int real_main(int argc, const char *argv[])
-#else
-int main(int argc, const char *argv[])
-#endif
-{
-   int argc_pos = 0;
-#ifdef unix
-   struct passwd *pw = NULL;
-   struct group *grp = NULL;
-   char *p;
-#endif
 
-   Argc = argc;
-   Argv = argv;
+static jb_err read_client_headers( struct client_state *csp, struct http_request *http )
+{
 
-   configfile =
-#if !defined(_WIN32)
-   "config"
-#else
-   "config.txt"
-#endif
-      ;
+   char *p;
+   char *req;
+   char buf[BUFFER_SIZE];
+   int len; /* for buffer sizes */
 
    /*
-    * Parse the command line arguments
-    */
-   while (++argc_pos < argc)
+    * Read the client's request.  Note that since we're not using select() we
+    * could get blocked here if a client connected, then didn't say anything!
+    *
+    * This will read all the client headers.
+   */
+
+   req = NULL ;
+   for (;;)
    {
-#if !defined(_WIN32) || defined(_WIN_CONSOLE)
+      len = read_socket(csp->cfd, buf, sizeof(buf));
 
-      if (strcmp(argv[argc_pos], "--help") == 0)
-      {
-         usage(argv[0]);
-      }
+      if (len <= 0) break;      /* error! */
+
+      add_to_iob(csp, buf, len);
+
+      req = get_header(csp);
 
-      else if(strcmp(argv[argc_pos], "--version") == 0)
+      if (req == NULL)
       {
-         printf("Privoxy version " VERSION " (" HOME_PAGE_URL ")\n");
-         exit(0);
+         break;    /* no HTTP request! */
       }
 
-      else if (strcmp(argv[argc_pos], "--no-daemon" ) == 0)
-      {
-         no_daemon = 1;
-      }
-#if defined(unix)
-      else if (strcmp(argv[argc_pos], "--pidfile" ) == 0)
+      if (*req == '\0')
       {
-         if (++argc_pos == argc) usage(argv[0]);
-         pidfile = strdup(argv[argc_pos]);
+         continue;   /* more to come! */
       }
 
-      else if (strcmp(argv[argc_pos], "--user" ) == 0)
-      {
-         if (++argc_pos == argc) usage(argv[argc_pos]);
+      /* When we get here we have read one header which is enough */
+      /* to check whether the connection is allowed */
+      break; 
+   }
 
-         if ((NULL != (p = strchr(argv[argc_pos], '.'))) && *(p + 1) != '0')
-         {
-            *p++ = '\0';
-            if (NULL == (grp = getgrnam(p)))
-            {
-               log_error(LOG_LEVEL_FATAL, "Group %s not found.", p);
-            }
-         }
 
-         if (NULL == (pw = getpwnam(argv[argc_pos])))
-         {
-            log_error(LOG_LEVEL_FATAL, "User %s not found.", argv[argc_pos]);
-         }
+   for(;;) 
+   {
 
-         if (p != NULL) *--p = '\0';
-      }
-#endif /* defined(unix) */
-      else
-#endif /* defined(_WIN32) && !defined(_WIN_CONSOLE) */
+     if ( ( ( p = get_header(csp) ) != NULL) && ( *p == '\0' ) )
       {
-         configfile = argv[argc_pos];
+         len = read_socket(csp->cfd, buf, sizeof(buf));
+         if (len <= 0)
+         {
+            log_error(LOG_LEVEL_ERROR, "read from client failed: %E");
+            return JB_ERR_GENERIC;
+         }
+         add_to_iob(csp, buf, len);
+         continue;
       }
 
-   } /* -END- while (more arguments) */
+      if (p == NULL) break;
 
-#if defined(unix)
-   if ( *configfile != '/' )
+      enlist(csp->headers, p);
+      freez(p);
+
+   }
+
+   parse_http_request(req, http, csp);
+   freez(req);
+
+   if (http->cmd == NULL)
    {
-      char *abs_file;
+      strcpy(buf, CHEADER);
+      write_socket(csp->cfd, buf, strlen(buf));
 
-      /* make config-filename absolute here */
-      if ( !(basedir = getcwd( NULL, 1024 )))
-      {
-         perror("get working dir failed");
-         exit( 1 );
-      }
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 400 0", csp->ip_addr_str);
 
-      if ( !(abs_file = malloc( strlen( basedir ) + strlen( configfile ) + 5 )))
-      {
-         perror("malloc failed");
-         exit( 1 );
-      }
-      strcpy( abs_file, basedir );
-      strcat( abs_file, "/" );
-      strcat( abs_file, configfile );
-      configfile = abs_file;
+      return JB_ERR_GENERIC;
    }
-#endif /* defined unix */
 
+   return JB_ERR_OK ;
 
-   files->next = NULL;
 
-#ifdef AMIGA
-   InitAmiga();
-#elif defined(_WIN32)
-   InitWin32();
-#endif
+} /* END read_client_headers */
 
-   /*
-    * Unix signal handling
-    *
-    * Catch the abort, interrupt and terminate signals for a graceful exit
-    * Catch the hangup signal so the errlog can be reopened.
-    * Ignore the broken pipe and child signals
-    *  FIXME: Isn't ignoring the default for SIGCHLD anyway and why ignore SIGPIPE? 
-    */
-#if !defined(_WIN32) && !defined(__OS2__) && !defined(AMIGA)
+
+/*********************************************************************
+ *
+ * Function    :  process_client_headers
+ *
+ * Description :  Process client headers
+ * FIXME: Add description of what we do here!
+ *
+ * Parameters  :
+ *          1  :  client_state structure
+ *          2  :  http_request structure
+ *                         
+ *
+ * Returns     :  JB_ERR_OK  - headers processed ok
+ *             :  other values - an error occurred.
+ *********************************************************************/
+
+static jb_err process_client_headers( struct client_state *csp, struct http_request *http )
 {
-   int idx;
-   const int catched_signals[] = { SIGABRT, SIGTERM, SIGINT, SIGHUP, 0 };
-   const int ignored_signals[] = { SIGPIPE, SIGCHLD, 0 };
 
-   for (idx = 0; catched_signals[idx] != 0; idx++)
-   {
-      if (signal(catched_signals[idx], sig_handler) == SIG_ERR)
+
+#ifdef FEATURE_FORCE_LOAD
+      /* If this request contains the FORCE_PREFIX,
+       * better get rid of it now and set the force flag --oes
+       * Changed to use the http structure rather than the req field --jaa
+       */
+
+      if (strstr(http->url, FORCE_PREFIX))
       {
-         log_error(LOG_LEVEL_FATAL, "Can't set signal-handler for signal %d: %E", catched_signals[idx]);
+         strclean(http->url, FORCE_PREFIX);
+         log_error(LOG_LEVEL_FORCE, "Enforcing request \"%s\".\n", http->url);
+         csp->flags |= CSP_FLAG_FORCED;
       }
-   }
 
-   for (idx = 0; ignored_signals[idx] != 0; idx++)
+#endif /* def FEATURE_FORCE_LOAD */
+
+
+   /* decide how to route the HTTP request */
+
+   if ((http->fwd = forward_url(http, csp)) == NULL)
    {
-      if (signal(ignored_signals[idx], SIG_IGN) == SIG_ERR)
-      {
-         log_error(LOG_LEVEL_FATAL, "Can't set ignore-handler for signal %d: %E", ignored_signals[idx]);
-      }
+      log_error(LOG_LEVEL_FATAL, "gateway spec is NULL!?!?  This can't happen!");
+      /* Never get here - LOG_LEVEL_FATAL causes program exit */
    }
 
-}
-#else /* ifdef _WIN32 */
-# ifdef _WIN_CONSOLE
+   /* build the http request to send to the server
+    * we have to do one of the following:
+    *
+    * create = use the original HTTP request to create a new
+    *          HTTP request that has either the path component
+    *          without the http://domainspec (w/path) or the
+    *          full orininal URL (w/url)
+    *          Note that the path and/or the HTTP version may
+    *          have been altered by now.
+    *
+    * connect = Open a socket to the host:port of the server
+    *           and short-circuit server and client socket.
+    *
+    * pass =  Pass the request unchanged if forwarding a CONNECT
+    *         request to a parent proxy. Note that we'll be sending
+    *         the CFAIL message ourselves if connecting to the parent
+    *         fails, but we won't send a CSUCCEED message if it works,
+    *         since that would result in a double message (ours and the
+    *         parent's). After sending the request to the parent, we simply
+    *         tunnel.
+    *
+    * here's the matrix:
+    *                        SSL
+    *                    0        1
+    *                +--------+--------+
+    *                |        |        |
+    *             0  | create | connect|
+    *                | w/path |        |
+    *  Forwarding    +--------+--------+
+    *                |        |        |
+    *             1  | create | pass   |
+    *                | w/url  |        |
+    *                +--------+--------+
+    *
+    */
+
    /*
-    * We *are* in a windows console app.
-    * Print a verbose messages about FAQ's and such
+    * Determine the actions for this URL
     */
-   printf(win32_blurb);
-# endif /* def _WIN_CONSOLE */
-#endif /* def _WIN32 */
+#ifdef FEATURE_TOGGLE
+   if (!(csp->flags & CSP_FLAG_TOGGLED_ON))
+   {
+      /* Most compatible set of actions (i.e. none) */
+      init_current_action(csp->action);
+   }
+   else
+#endif /* ndef FEATURE_TOGGLE */
+   {
+      url_actions(http, csp);
+   }
+
+
 
 
-   /* Initialize the CGI subsystem */
-   cgi_init_error_messages();
 
    /*
-    * If runnig on unix and without the --nodaemon
-    * option, become a daemon. I.e. fork, detach
-    * from tty and get process group leadership
+    * Downgrade http version from 1.1 to 1.0 if +downgrade
+    * action applies
     */
-#if defined(unix)
-{
-   pid_t pid = 0;
-#if 0
-   int   fd;
-#endif
-
-   if (!no_daemon)
+   if ( (http->ssl == 0)
+     && (!strcmpic(http->ver, "HTTP/1.1"))
+     && (csp->action->flags & ACTION_DOWNGRADE))
    {
-      pid  = fork();
+      freez(http->ver);
+      http->ver = strdup("HTTP/1.0");
 
-      if ( pid < 0 ) /* error */
-      {
-         perror("fork");
-         exit( 3 );
-      }
-      else if ( pid != 0 ) /* parent */
-      {
-         int status;
-         pid_t wpid;
-         /*
-          * must check for errors
-          * child died due to missing files aso
-          */
-         sleep( 1 );
-         wpid = waitpid( pid, &status, WNOHANG );
-         if ( wpid != 0 )
-         {
-            exit( 1 );
-         }
-         exit( 0 );
-      }
-      /* child */
-#if 1
-      /* Should be more portable, but not as well tested */
-      setsid();
-#else /* !1 */
-#ifdef __FreeBSD__
-      setpgrp(0,0);
-#else /* ndef __FreeBSD__ */
-      setpgrp();
-#endif /* ndef __FreeBSD__ */
-      fd = open("/dev/tty", O_RDONLY);
-      if ( fd )
+      if (http->ver == NULL)
       {
-         /* no error check here */
-         ioctl( fd, TIOCNOTTY,0 );
-         close ( fd );
+         log_error(LOG_LEVEL_FATAL, "Out of memory downgrading HTTP version");
       }
-#endif /* 1 */
-      /* FIXME: should close stderr (fd 2) here too, but the test
-       * for existence
-       * and load config file is done in listen_loop() and puts
-       * some messages on stderr there.
-       */
+   }
 
-      close( 0 );
-      close( 1 );
-      chdir("/");
+   /* 
+    * Save a copy of the original request for logging
+    */
+   http->ocmd = strdup(http->cmd);
 
-   } /* -END- if (!no_daemon) */
+   if (http->ocmd == NULL)
+   {
+      log_error(LOG_LEVEL_FATAL, "Out of memory copying HTTP request line");
+   }
 
    /*
-    * As soon as we have written the PID file, we can switch
-    * to the user and group ID indicated by the --user option
+    * (Re)build the HTTP request for non-SSL requests.
+    * If forwarding, use the whole URL, else, use only the path.
     */
-   write_pid_file();
-   
-   if (NULL != pw)
+   if (http->ssl == 0)
    {
-      if (((NULL != grp) && setgid(grp->gr_gid)) || (setgid(pw->pw_gid)))
+      freez(http->cmd);
+
+      http->cmd = strdup(http->gpc);
+      string_append(&http->cmd, " ");
+
+      if (http->fwd->forward_host)
       {
-         log_error(LOG_LEVEL_FATAL, "Cannot setgid(): Insufficient permissions.");
+         string_append(&http->cmd, http->url);
       }
-      if (setuid(pw->pw_uid))
+      else
       {
-         log_error(LOG_LEVEL_FATAL, "Cannot setuid(): Insufficient permissions.");
+         string_append(&http->cmd, http->path);
       }
-   }
-}
-#endif /* defined unix */
 
-   listen_loop();
+      string_append(&http->cmd, " ");
+      string_append(&http->cmd, http->ver);
 
-   /* NOTREACHED */
-   return(-1);
+      if (http->cmd == NULL)
+      {
+         log_error(LOG_LEVEL_FATAL, "Out of memory rewiting SSL command");
+      }
+   }
+   enlist_first(csp->headers, http->cmd);
 
-}
 
+   /*
+    * If the user has not supplied any wafers, and the user has not
+    * told us to suppress the vanilla wafer, then send the vanilla wafer.
+    */
+   if (list_is_empty(csp->action->multi[ACTION_MULTI_WAFER])
+       && ((csp->action->flags & ACTION_VANILLA_WAFER) != 0))
+   {
+      enlist(csp->action->multi[ACTION_MULTI_WAFER], VANILLA_WAFER);
+   }
+
+   return JB_ERR_OK ;
+} /* process_client_headers */
 
 /*********************************************************************
  *
- * Function    :  bind_port_helper
+ * Function    :  intercept_page
  *
- * Description :  Bind the listen port.  Handles logging, and aborts
- *                on failure.
+ * Description :  Check whether to intercept page
  *
  * Parameters  :
- *          1  :  config = Privoxy configuration.  Specifies port
- *                         to bind to.
- *
- * Returns     :  Port that was opened.
+ *          1  :  client_state structure
+ *          2  :  http_request structure
+ *                         
  *
+ * Returns     :  JB_ERR_OK  - Do NOT intercept this page
+ *             :  JB_ERR_INTERCEPT - intercept this page
+ *             :  all other values - an error occurred.  
  *********************************************************************/
-static jb_socket bind_port_helper(struct configuration_spec * config)
+static jb_err intercept_page( struct client_state *csp, struct http_request *http )
 {
-   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)
-   {
-      log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses",
-                config->hport);
-   }
-   else
-   {
-      log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s",
-                config->hport, config->haddr);
-   }
+   /* Skeleton for HTTP response, if we should intercept the request */
+   struct http_response *rsp;
 
-   result = bind_port(config->haddr, config->hport, &bfd);
 
-   if (result < 0)
+/*
+ * These 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
+ */
+#ifdef FEATURE_TOGGLE
+#   define IS_TOGGLED_ON_AND (csp->flags & CSP_FLAG_TOGGLED_ON) &&
+#else /* ifndef FEATURE_TOGGLE */
+#   define IS_TOGGLED_ON_AND
+#endif /* ndef FEATURE_TOGGLE */
+#ifdef FEATURE_FORCE_LOAD
+#   define IS_NOT_FORCED_AND !(csp->flags & CSP_FLAG_FORCED) &&
+#else /* ifndef FEATURE_FORCE_LOAD */
+#   define IS_NOT_FORCED_AND
+#endif /* def FEATURE_FORCE_LOAD */
+
+#define IS_ENABLED_AND   IS_TOGGLED_ON_AND IS_NOT_FORCED_AND
+
+   if (
+       /* a CGI call was detected and answered */
+       (NULL != (rsp = dispatch_cgi(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 fast redirect kicked in */
+#ifdef FEATURE_FAST_REDIRECTS
+          || (((csp->action->flags & ACTION_FAST_REDIRECTS) != 0) &&
+                (NULL != (rsp = redirect_url(csp))))
+#endif /* def FEATURE_FAST_REDIRECTS */
+          ))
+      )
    {
-      switch(result)
+      /* Write the answer to the client */
+      if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+       || write_socket(csp->cfd, rsp->body, rsp->content_length))
       {
-         case -3 :
-            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: "
-               "There may be another Privoxy or some other "
-               "proxy running on port %d",
-               (NULL != config->haddr) ? config->haddr : "INADDR_ANY",
-                      config->hport, config->hport);
+         log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+      }
 
-         case -2 :
-            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: " 
-               "The hostname is not resolvable",
-               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+#ifdef FEATURE_STATISTICS
+      /* Count as a rejected request */
+      csp->flags |= CSP_FLAG_REJECTED;
+#endif /* def FEATURE_STATISTICS */
 
-         default :
-            log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: because %E",
-               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
-      }
+      /* Log (FIXME: All intercept reasons apprear as "crunch" with Status 200) */
+      log_error(LOG_LEVEL_GPC, "%s%s crunch!", http->hostport, http->path);
+      log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 200 3", csp->ip_addr_str, http->ocmd);
 
-      /* shouldn't get here */
-      return JB_INVALID_SOCKET;
+      /* Clean up and return */
+      free_http_response(rsp);
+      return JB_ERR_INTERCEPT;
    }
 
-   config->need_bind = 0;
-
-   return bfd;
+   /* When we get here the request doesn't need to be intercepted */
+   return JB_ERR_OK ;
 }
 
-
 /*********************************************************************
  *
- * Function    :  listen_loop
- *
- * Description :  bind the listen port and enter a "FOREVER" listening loop.
- *
- * Parameters  :  N/A
+ * Function    :  open_forwarding_connection
  *
- * Returns     :  Never.
+ * Description :  Check whether to intercept page
  *
+ * Parameters  :
+ *          1  :  client_state structure
+ *          2  :  http_request structure
+ *                         
+ * Returns     :  JB_ERR_OK  - Connection opened ok
+ *             :  all other values - an error occurred.  
  *********************************************************************/
-static void listen_loop(void)
+static jb_err open_forwarding_connection(struct client_state *csp )
 {
-   struct client_state *csp = NULL;
-   jb_socket bfd;
-   struct configuration_spec * config;
-
-   config = load_config();
-
-   bfd = bind_port_helper(config);
+   struct http_response *rsp;
+   
+   log_error(LOG_LEVEL_GPC, "%s%s", csp->http->hostport, csp->http->path);
 
-#ifdef FEATURE_GRACEFUL_TERMINATION
-   while (!g_terminate)
-#else
-   for (;;)
-#endif
+   if (csp->http->fwd->forward_host)
    {
-#if !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) && !defined(__OS2__)
-      while (waitpid(-1, NULL, WNOHANG) > 0)
-      {
-         /* zombie children */
-      }
-#endif /* !defined(FEATURE_PTHREAD) && !defined(_WIN32) && !defined(__BEOS__) && !defined(AMIGA) */
-
-      /*
-       * Free data that was used by died threads
-       */
-      sweep();
-
-#if defined(unix)
-      /*
-       * Re-open the errlog after HUP signal
-       */
-      if (received_hup_signal)
-      {
-         init_error_log(Argv[0], config->logfile, config->debug);
-         received_hup_signal = 0;
-      }
-#endif
-
-#ifdef __OS2__
-#ifdef FEATURE_COOKIE_JAR
-      /*
-       * Need a workaround here: we have to fclose() the jarfile, or we die because it's
-       * already open.  I think unload_configfile() is not being run, which should do
-       * this work.  Until that can get resolved, we'll use this workaround.
-       */
-       if (csp)
-         if(csp->config)
-           if (csp->config->jar)
-           {
-             fclose(csp->config->jar);
-             csp->config->jar = NULL;
-           }
-#endif /* FEATURE_COOKIE_JAR */
-#endif /* __OS2__ */
-
-      if ( NULL == (csp = (struct client_state *) zalloc(sizeof(*csp))) )
-      {
-         log_error(LOG_LEVEL_FATAL, "malloc(%d) for csp failed: %E", sizeof(*csp));
-         continue;
-      }
-
-      csp->flags |= CSP_FLAG_ACTIVE;
-      csp->sfd    = JB_INVALID_SOCKET;
-
-      csp->config = config = load_config();
-
-      if ( config->need_bind )
-      {
-         /*
-          * Since we were listening to the "old port", we will not see
-          * a "listen" param change until the next IJB 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.
-          *
-          * Which-ever is correct, we will serve 1 more page via the
-          * old settings.  This should probably be a "show-proxy-args"
-          * request.  This should not be a so common of an operation
-          * that this will hurt people's feelings.
-          */
-
-         close_socket(bfd);
+      log_error(LOG_LEVEL_CONNECT, "via %s:%d to: %s",
+               csp->http->fwd->forward_host, csp->http->fwd->forward_port, 
+               csp->http->hostport);
+   }
+   else
+   {
+      log_error(LOG_LEVEL_CONNECT, "to %s", csp->http->hostport);
+   }
 
-         bfd = bind_port_helper(config);
-      }
+   /* here we connect to the server, gateway, or the forwarder */
 
-      log_error(LOG_LEVEL_CONNECT, "accept connection ... ");
+   csp->sfd = forwarded_connect(csp->http->fwd, csp->http, csp);
 
-      if (!accept_connection(csp, bfd))
+   if (csp->sfd == JB_INVALID_SOCKET)
+   {
+      log_error(LOG_LEVEL_CONNECT, "connect to: %s failed: %E",
+                csp->http->hostport);
+
+      if (errno == EINVAL)
       {
-         log_error(LOG_LEVEL_CONNECT, "accept failed: %E");
+         rsp = error_response(csp, "no-such-domain", errno);
 
-#ifdef AMIGA
-         if(!childs)
-         {
-            exit(1);
-         }
-#endif
-         freez(csp);
-         continue;
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 404 0",
+                   csp->ip_addr_str, csp->http->ocmd);
       }
       else
       {
-         log_error(LOG_LEVEL_CONNECT, "OK");
-      }
+         rsp = error_response(csp, "connect-failed", errno);
 
-#ifdef FEATURE_TOGGLE
-      if (g_bToggleIJB)
-      {
-         csp->flags |= CSP_FLAG_TOGGLED_ON;
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                   csp->ip_addr_str, csp->http->ocmd);
       }
-#endif /* def FEATURE_TOGGLE */
 
-      if (run_loader(csp))
-      {
-         log_error(LOG_LEVEL_FATAL, "a loader failed - must exit");
-         /* Never get here - LOG_LEVEL_FATAL causes program exit */
-      }
 
-#ifdef FEATURE_ACL
-      if (block_acl(NULL,csp))
+      /* Write the answer to the client */
+      if(rsp)
       {
-         log_error(LOG_LEVEL_CONNECT, "Connection dropped due to ACL");
-         close_socket(csp->cfd);
-         freez(csp);
-         continue;
+         if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+          || write_socket(csp->cfd, rsp->body, rsp->content_length))
+         {
+            log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", csp->http->host);
+         }
       }
-#endif /* def FEATURE_ACL */
-
-      /* add it to the list of clients */
-      csp->next = clients->next;
-      clients->next = csp;
 
-      if (config->multi_threaded)
-      {
-         int child_id;
+      free_http_response(rsp);
+      return JB_ERR_GENERIC;
+   }
 
-/* this is a switch () statment in the C preprocessor - ugh */
-#undef SELECTED_ONE_OPTION
+   log_error(LOG_LEVEL_CONNECT, "OK");
+   return JB_ERR_OK ;
 
-/* Use Pthreads in preference to native code */
-#if defined(FEATURE_PTHREAD) && !defined(SELECTED_ONE_OPTION)
-#define SELECTED_ONE_OPTION
-         {
-            pthread_t the_thread;
-            pthread_attr_t attrs;
+} /* END open_forwarding_connection() */
 
-            pthread_attr_init(&attrs);
-            pthread_attr_setdetachstate(&attrs, PTHREAD_CREATE_DETACHED);
-            child_id = (pthread_create(&the_thread, &attrs,
-               (void*)serve, csp) ? -1 : 0);
-            pthread_attr_destroy(&attrs);
-         }
-#endif
 
-#if defined(_WIN32) && !defined(_CYGWIN) && !defined(SELECTED_ONE_OPTION)
-#define SELECTED_ONE_OPTION
-         child_id = _beginthread(
-            (void (*)(void *))serve,
-            64 * 1024,
-            csp);
-#endif
+/*********************************************************************
+ *
+ * Function    :  send_client_headers_to_server
+ *
+ * Description :  send the client headers (possibly modified) to the server
+ *
+ * Parameters  :
+ *          1  :  client_state structure
+ *          2  :  http_request structure
+ *                         
+ *
+ * Returns     :  JB_ERR_OK  - headers sent ok
+ *             :  other values - an error occurred.
+ *********************************************************************/
 
-#if defined(__OS2__) && !defined(SELECTED_ONE_OPTION)
-#define SELECTED_ONE_OPTION
-         child_id = _beginthread(
-            (void(* _Optlink)(void*))serve,
-            NULL,
-            64 * 1024,
-            csp);
-#endif
+static jb_err send_client_headers_to_server( struct client_state *csp, struct http_request *http, char *hdr )
+{
 
-#if defined(__BEOS__) && !defined(SELECTED_ONE_OPTION)
-#define SELECTED_ONE_OPTION
-         {
-            thread_id tid = spawn_thread
-               (server_thread, "server", B_NORMAL_PRIORITY, csp);
+   struct http_response *rsp;
 
-            if ((tid >= 0) && (resume_thread(tid) == B_OK))
-            {
-               child_id = (int) tid;
-            }
-            else
-            {
-               child_id = -1;
-            }
-         }
-#endif
+   if (http->fwd->forward_host || (http->ssl == 0))
+   {
+      /* write the client's (modified) header to the server
+       * (along with anything else that may be in the buffer)
+       */
 
-#if defined(AMIGA) && !defined(SELECTED_ONE_OPTION)
-#define SELECTED_ONE_OPTION
-         csp->cfd = ReleaseSocket(csp->cfd, -1);
-         if((child_id = (int)CreateNewProcTags(
-            NP_Entry, (ULONG)server_thread,
-            NP_Output, Output(),
-            NP_CloseOutput, FALSE,
-            NP_Name, (ULONG)"privoxy child",
-            NP_StackSize, 200*1024,
-            TAG_DONE)))
-         {
-            childs++;
-            ((struct Task *)child_id)->tc_UserData = csp;
-            Signal((struct Task *)child_id, SIGF_SINGLE);
-            Wait(SIGF_SINGLE);
-         }
-#endif
+      if (write_socket(csp->sfd, hdr, strlen(hdr))
+       || (flush_socket(csp->sfd, csp) <  0))
+      {
+         log_error(LOG_LEVEL_CONNECT, "write header to: %s failed: %E",
+                    http->hostport);
 
-#if !defined(SELECTED_ONE_OPTION)
-         child_id = fork();
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \"%s\" 503 0",
+                   csp->ip_addr_str, http->ocmd);
 
-         /* This block is only needed when using fork().
-          * When using threads, the server thread was
-          * created and run by the call to _beginthread().
-          */
-         if (child_id == 0)   /* child */
-         {
-            serve(csp);
-            _exit(0);
+         rsp = error_response(csp, "connect-failed", errno);
 
-         }
-         else if (child_id > 0) /* parent */
+         if(rsp)
          {
-            /* in a fork()'d environment, the parent's
-             * copy of the client socket and the CSP
-             * are not used.
-             */
-
-#if !defined(_WIN32) && defined(__CYGWIN__)
-            wait( NULL );
-#endif /* !defined(_WIN32) && defined(__CYGWIN__) */
-            close_socket(csp->cfd);
-            csp->flags &= ~CSP_FLAG_ACTIVE;
+            if (write_socket(csp->cfd, rsp->head, rsp->head_length)
+             || write_socket(csp->cfd, rsp->body, rsp->content_length))
+            {
+               log_error(LOG_LEVEL_ERROR, "write to: %s failed: %E", http->host);
+            }
          }
-#endif
-
-#undef SELECTED_ONE_OPTION
-/* end of cpp switch () */
-
-         if (child_id < 0) /* failed */
-         {
-            char buf[BUFFER_SIZE];
-
-            log_error(LOG_LEVEL_ERROR, "can't fork: %E");
 
-            sprintf(buf , "Privoxy: can't fork: errno = %d", errno);
-
-            write_socket(csp->cfd, buf, strlen(buf));
-            close_socket(csp->cfd);
-            csp->flags &= ~CSP_FLAG_ACTIVE;
-            sleep(5);
-            continue;
-         }
+         free_http_response(rsp);
+         return JB_ERR_GENERIC;
       }
-      else
+   }
+   else
+   {
+      /*
+       * We're running an SSL tunnel and we're not forwarding,
+       * 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 2\n",
+                csp->ip_addr_str, http->ocmd);
+
+      if (write_socket(csp->cfd, CSUCCEED, sizeof(CSUCCEED)-1))
       {
-         serve(csp);
+         return JB_ERR_OK;
       }
+      IOB_RESET(csp);
    }
 
-   /* NOTREACHED unless FEATURE_GRACEFUL_TERMINATION is defined */
-
-   /* Clean up.  Aim: free all memory (no leaks) */
-#ifdef FEATURE_GRACEFUL_TERMINATION
+   return JB_ERR_OK ;
+} /* END send_client_headers_to_server */
 
-   log_error(LOG_LEVEL_ERROR, "Graceful termination requested");
 
-   unload_current_config_file();
-   unload_current_actions_file();
-   unload_current_re_filterfile();
-#ifdef FEATURE_TRUST
-   unload_current_trust_file();
-#endif
+/*********************************************************************
+ *
+ * Function    :  is_connect_request_allowed
+ *
+ * Description :  send the client headers (possibly modified) to the server
+ *
+ * Parameters  :
+ *          1  :  client_state structure
+ *
+ *                         
+ *
+ * Returns     :  JB_ERR_OK  - connect request allowed
+ *             :  other values - connect request not allowed
+ *********************************************************************/
+static jb_err is_connect_request_allowed( 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 (config->multi_threaded)
+   if(csp->http->ssl)
    {
-      int i = 60;
-      do
+      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)) )
       {
-         sleep(1);
-         sweep();
-      } while ((clients->next != NULL) && (--i > 0));
+         char buf[BUFFER_SIZE];
+         strcpy(buf, CFORBIDDEN);
+         write_socket(csp->cfd, buf, strlen(buf));
 
-      if (i <= 0)
-      {
-         log_error(LOG_LEVEL_ERROR, "Graceful termination failed - still some live clients after 1 minute wait.");
+         log_error(LOG_LEVEL_CONNECT, "Denying suspicious CONNECT request from %s", csp->ip_addr_str);
+         log_error(LOG_LEVEL_CLF, "%s - - [%T] \" \" 403 0", csp->ip_addr_str);
+
+         return JB_ERR_GENERIC;
       }
    }
-   sweep();
-   sweep();
-
-#if defined(unix)
-   free(basedir);
-#endif
-#if defined(_WIN32) && !defined(_WIN_CONSOLE)
-   /* Cleanup - remove taskbar icon etc. */
-   TermLogWindow();
-#endif
-
-   exit(0);
-#endif /* FEATURE_GRACEFUL_TERMINATION */
-
-}
 
+   return JB_ERR_OK ;
+} /* END is_connect_request_allowed */
 
 /*
   Local Variables:
index 0111568..08191d8 100644 (file)
@@ -1,7 +1,7 @@
 #ifndef PROJECT_H_INCLUDED
 #define PROJECT_H_INCLUDED
 /** Version string. */
-#define PROJECT_H_VERSION "$Id: project.h,v 2.0 2002/06/04 14:34:21 jongfoster Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 2.1 2002/06/04 16:35:56 jongfoster Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/src/project.h,v $
@@ -37,6 +37,9 @@
  *
  * Revisions   :
  *    $Log: project.h,v $
+ *    Revision 2.1  2002/06/04 16:35:56  jongfoster
+ *    Moving three variable declarations to jcc.c from project.h
+ *
  *    Revision 2.0  2002/06/04 14:34:21  jongfoster
  *    Moving source files to src/
  *
@@ -531,6 +534,7 @@ typedef int jb_socket;
  */
 typedef int jb_err;
 
+#define JB_ERR_GENERIC   -1 /* General error return value */
 #define JB_ERR_OK         0 /**< Success, no error                        */
 #define JB_ERR_MEMORY     1 /**< Out of memory                            */
 #define JB_ERR_CGI_PARAMS 2 /**< Missing or corrupt CGI parameters        */
@@ -538,7 +542,7 @@ typedef int jb_err;
 #define JB_ERR_PARSE      4 /**< Error parsing file                       */
 #define JB_ERR_MODIFIED   5 /**< File has been modified outside of the  
                                  CGI actions editor.                      */
-
+#define JB_ERR_INTERCEPT  6 /* This page should be intercepted */
 
 /**
  * This macro is used to free a pointer that may be NULL.
@@ -700,6 +704,9 @@ struct http_request
    char  *dbuffer; /**< Buffer with '\0'-delimited domain name.           */
    char **dvec;    /**< List of pointers to the strings in dbuffer.       */
    int    dcount;  /**< How many parts to this domain? (length of dvec)   */
+
+   const struct forward_spec *fwd ;
+
 };
 
 
@@ -1049,6 +1056,10 @@ struct client_state
 
    /** Next thread in linked list. Only read or modify from the main thread! */
    struct client_state *next;
+
+   char *(*content_filter)() ;
+   int all_headers_read ;
+
 };