Allow to bind to multiple separate addresses.
authorFabian Keil <fk@fabiankeil.de>
Sun, 17 Jul 2011 13:34:36 +0000 (13:34 +0000)
committerFabian Keil <fk@fabiankeil.de>
Sun, 17 Jul 2011 13:34:36 +0000 (13:34 +0000)
Patch set submitted by Petr Písař in #3354485.

cgi.c
doc/source/p-config.sgml
jbsockets.c
jbsockets.h
jcc.c
loadcfg.c
project.h

diff --git a/cgi.c b/cgi.c
index d4a1f22..a55805b 100644 (file)
--- a/cgi.c
+++ b/cgi.c
@@ -1,4 +1,4 @@
-const char cgi_rcs[] = "$Id: cgi.c,v 1.139 2011/07/08 13:27:56 fabiankeil Exp $";
+const char cgi_rcs[] = "$Id: cgi.c,v 1.140 2011/07/08 13:28:11 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/cgi.c,v $
@@ -2143,6 +2143,7 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    struct map * exports;
    int local_help_exists = 0;
    char *ip_address = NULL;
+   char *port = NULL;
    char *hostname = NULL;
 
    assert(csp);
@@ -2155,12 +2156,12 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
 
    if (csp->config->hostname)
    {
-      get_host_information(csp->cfd, &ip_address, NULL);
+      get_host_information(csp->cfd, &ip_address, &port, NULL);
       hostname = strdup(csp->config->hostname);
    }
    else
    {
-      get_host_information(csp->cfd, &ip_address, &hostname);
+      get_host_information(csp->cfd, &ip_address, &port, &hostname);
    }
 
    err = map(exports, "version", 1, html_encode(VERSION), 0);
@@ -2168,6 +2169,8 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    if (!err) err = map(exports, "time",          1, html_encode(buf), 0);
    if (!err) err = map(exports, "my-ip-address", 1, html_encode(ip_address ? ip_address : "unknown"), 0);
    freez(ip_address);
+   if (!err) err = map(exports, "my-port",       1, html_encode(port ? port : "unkown"), 0);
+   freez(port);
    if (!err) err = map(exports, "my-hostname",   1, html_encode(hostname ? hostname : "unknown"), 0);
    freez(hostname);
    if (!err) err = map(exports, "homepage",      1, html_encode(HOME_PAGE_URL), 0);
@@ -2192,9 +2195,6 @@ struct map *default_exports(const struct client_state *csp, const char *caller)
    if (!err) err = map_block_killer(exports, "can-toggle");
 #endif
 
-   snprintf(buf, sizeof(buf), "%d", csp->config->hport);
-   if (!err) err = map(exports, "my-port", 1, buf, 1);
-
    if(!strcmp(CODE_STATUS, "stable"))
    {
       if (!err) err = map_block_killer(exports, "unstable");
index 4a4a6db..2b46e9c 100644 (file)
@@ -3,7 +3,7 @@
 
  Purpose     :  Used with other docs and files only.
 
- $Id: p-config.sgml,v 2.74 2011/07/08 13:31:17 fabiankeil Exp $
+ $Id: p-config.sgml,v 2.75 2011/07/08 13:31:40 fabiankeil Exp $
 
  Copyright (C) 2001-2010 Privoxy Developers http://www.privoxy.org/
  See LICENSE.
@@ -97,7 +97,7 @@
  Sample Configuration File for Privoxy v&p-version;
 </title>
 <para>
- $Id: p-config.sgml,v 2.74 2011/07/08 13:31:17 fabiankeil Exp $
+ $Id: p-config.sgml,v 2.75 2011/07/08 13:31:40 fabiankeil Exp $
 </para>
 <para>
 Copyright (C) 2001-2010 Privoxy Developers http://www.privoxy.org/
@@ -1234,6 +1234,13 @@ actionsfile
     serve requests from other machines (e.g. on your local network) as well, you
     will need to override the default.
    </para>
+   <para>
+    You can use this statement multiple times to make
+    <application>Privoxy</application> listen on more ports or more
+    <abbrev>IP</abbrev> addresses. Suitable if your operating system does not
+    support sharing <abbrev>IPv6</abbrev> and <abbrev>IPv4</abbrev> protocols
+    on the same socket.
+   </para>
    <para>
     If a hostname is used instead of an IP address, <application>Privoxy</application>
     will try to resolve it to an IP address and if there are multiple, use the first
index b00d514..0ce7507 100644 (file)
@@ -1,4 +1,4 @@
-const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.103 2011/06/23 13:58:22 fabiankeil Exp $";
+const char jbsockets_rcs[] = "$Id: jbsockets.c,v 1.104 2011/07/04 17:47:29 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.c,v $
@@ -923,14 +923,17 @@ int bind_port(const char *hostnam, int portnum, jb_socket *pfd)
  *          1  :  afd = File descriptor returned from accept().
  *          2  :  ip_address = Pointer to return the pointer to
  *                             the ip address string.
- *          3  :  hostname =   Pointer to return the pointer to
+ *          3  :  port =       Pointer to return the pointer to
+ *                             the TCP port string.
+ *          4  :  hostname =   Pointer to return the pointer to
  *                             the hostname or NULL if the caller
  *                             isn't interested in it.
  *
  * Returns     :  void.
  *
  *********************************************************************/
-void get_host_information(jb_socket afd, char **ip_address, char **hostname)
+void get_host_information(jb_socket afd, char **ip_address, char **port,
+                          char **hostname)
 {
 #ifdef HAVE_RFC2553
    struct sockaddr_storage server;
@@ -963,6 +966,7 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
       *hostname = NULL;
    }
    *ip_address = NULL;
+   *port = NULL;
 
    if (!getsockname(afd, (struct sockaddr *) &server, &s_length))
    {
@@ -971,25 +975,37 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
          log_error(LOG_LEVEL_ERROR, "getsockname() truncated server address");
          return;
       }
+      *port = malloc(NI_MAXSERV);
+      if (NULL == *port)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Out of memory while getting the client's port.");
+         return;
+      }
 #ifdef HAVE_RFC2553
       *ip_address = malloc(NI_MAXHOST);
       if (NULL == *ip_address)
       {
          log_error(LOG_LEVEL_ERROR,
             "Out of memory while getting the client's IP address.");
+         freez(*port);
          return;
       }
       retval = getnameinfo((struct sockaddr *) &server, s_length,
-         *ip_address, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
+         *ip_address, NI_MAXHOST, *port, NI_MAXSERV,
+         NI_NUMERICHOST|NI_NUMERICSERV);
       if (retval)
       {
          log_error(LOG_LEVEL_ERROR,
             "Unable to print my own IP address: %s", gai_strerror(retval));
          freez(*ip_address);
+         freez(*port);
          return;
       }
 #else
       *ip_address = strdup(inet_ntoa(server.sin_addr));
+      snprintf(*port, NI_MAXSERV, "%hu", ntohs(server.sin_port));
+      *port[NI_MAXSERV - 1] = '\0';
 #endif /* HAVE_RFC2553 */
       if (NULL == hostname)
       {
@@ -1065,24 +1081,23 @@ void get_host_information(jb_socket afd, char **ip_address, char **hostname)
  *
  * Function    :  accept_connection
  *
- * Description :  Accepts a connection on a socket.  Socket must have
- *                been created using bind_port().
+ * Description :  Accepts a connection on one of more socket.  Sockets
+ *                must have been created using bind_port().
  *
  * Parameters  :
  *          1  :  csp = Client state, cfd, ip_addr_str, and 
  *                ip_addr_long will be set by this routine.
- *          2  :  fd  = file descriptor returned from bind_port
+ *          2  :  fds = File descriptors returned from bind_port
  *
  * Returns     :  when a connection is accepted, it returns 1 (TRUE).
  *                On an error it returns 0 (FALSE).
  *
  *********************************************************************/
-int accept_connection(struct client_state * csp, jb_socket fd)
+int accept_connection(struct client_state * csp, jb_socket fds[])
 {
 #ifdef HAVE_RFC2553
    /* XXX: client is stored directly into csp->tcp_addr */
 #define client (csp->tcp_addr)
-   int retval;
 #else
    struct sockaddr_in client;
 #endif
@@ -1093,9 +1108,66 @@ int accept_connection(struct client_state * csp, jb_socket fd)
 #else
    socklen_t c_length;
 #endif
+   int retval;
+   int i;
+   int max_selected_socket;
+   fd_set selected_fds;
+   jb_socket fd;
 
    c_length = sizeof(client);
 
+   /* Wait for a connection on any socket. Return immediately if no socket is
+    * listening. */
+   FD_ZERO(&selected_fds);
+   max_selected_socket = 0;
+   for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
+   {
+      if (JB_INVALID_SOCKET != fds[i])
+      {
+         FD_SET(fds[i], &selected_fds);
+         if (max_selected_socket < fds[i] + 1)
+         {
+            max_selected_socket = fds[i] + 1;
+         }
+      }
+   }
+   if (0 == max_selected_socket)
+   {
+      return 0;
+   }
+   do
+   {
+      retval = select(max_selected_socket, &selected_fds, NULL, NULL, NULL);
+   } while (retval < 0 && errno == EINTR);
+   if (retval <= 0)
+   {
+      if (0 == retval)
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Waiting on new client failed because select(2) returned 0."
+            " This should not happen.");
+      }
+      else
+      {
+         log_error(LOG_LEVEL_ERROR,
+            "Waiting on new client failed because of problems in select(2): "
+            "%s.", strerror(errno));
+      }
+      return 0;
+   }
+   for (i = 0; i < MAX_LISTENING_SOCKETS && !FD_ISSET(fds[i], &selected_fds);
+         i++);
+   if (i >= MAX_LISTENING_SOCKETS)
+   {
+      log_error(LOG_LEVEL_ERROR,
+            "select(2) reported connected clients (number = %u, "
+            "descriptor boundary = %u), but none found.",
+            retval, max_selected_socket);
+      return 0;
+   }
+   fd = fds[i];
+
+   /* Accept selected connection */
 #ifdef _WIN32
    afd = accept (fd, (struct sockaddr *) &client, &c_length);
    if (afd == JB_INVALID_SOCKET)
index f19968c..6b74b1d 100644 (file)
@@ -1,6 +1,6 @@
 #ifndef JBSOCKETS_H_INCLUDED
 #define JBSOCKETS_H_INCLUDED
-#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.16 2009/05/16 13:27:20 fabiankeil Exp $"
+#define JBSOCKETS_H_VERSION "$Id: jbsockets.h,v 1.17 2010/04/23 11:53:48 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jbsockets.h,v $
@@ -57,8 +57,8 @@ extern int data_is_available(jb_socket fd, int seconds_to_wait);
 extern void close_socket(jb_socket fd);
 
 extern int bind_port(const char *hostnam, int portnum, jb_socket *pfd);
-extern int accept_connection(struct client_state * csp, jb_socket fd);
-extern void get_host_information(jb_socket afd, char **ip_address, char **hostname);
+extern int accept_connection(struct client_state * csp, jb_socket fds[]);
+extern void get_host_information(jb_socket afd, char **ip_address, char **port, char **hostname);
 
 extern unsigned long resolve_hostname_to_ip(const char *host);
 
diff --git a/jcc.c b/jcc.c
index 91b2397..271b1d1 100644 (file)
--- a/jcc.c
+++ b/jcc.c
@@ -1,4 +1,4 @@
-const char jcc_rcs[] = "$Id: jcc.c,v 1.356 2011/07/17 13:30:24 fabiankeil Exp $";
+const char jcc_rcs[] = "$Id: jcc.c,v 1.357 2011/07/17 13:31:02 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/jcc.c,v $
@@ -151,7 +151,9 @@ static void serve(struct client_state *csp);
 static void usage(const char *myname);
 #endif
 static void initialize_mutexes(void);
-static jb_socket bind_port_helper(struct configuration_spec *config);
+static jb_socket bind_port_helper(const char *haddr, int hport);
+static void bind_ports_helper(struct configuration_spec *config, jb_socket sockets[]);
+static void close_ports_helper(jb_socket sockets[]);
 static void listen_loop(void);
 
 #ifdef AMIGA
@@ -3325,29 +3327,30 @@ int main(int argc, char **argv)
  *                on failure.
  *
  * Parameters  :
- *          1  :  config = Privoxy configuration.  Specifies port
- *                         to bind to.
+ *          1  :  haddr = Host addres to bind to. Use NULL to bind to
+ *                        INADDR_ANY.
+ *          2  :  hport = Specifies port to bind to.
  *
  * Returns     :  Port that was opened.
  *
  *********************************************************************/
-static jb_socket bind_port_helper(struct configuration_spec * config)
+static jb_socket bind_port_helper(const char *haddr, int hport)
 {
    int result;
    jb_socket bfd;
 
-   if (config->haddr == NULL)
+   if (haddr == NULL)
    {
       log_error(LOG_LEVEL_INFO, "Listening on port %d on all IP addresses",
-                config->hport);
+                hport);
    }
    else
    {
       log_error(LOG_LEVEL_INFO, "Listening on port %d on IP address %s",
-                config->hport, config->haddr);
+                hport, haddr);
    }
 
-   result = bind_port(config->haddr, config->hport, &bfd);
+   result = bind_port(haddr, hport, &bfd);
 
    if (result < 0)
    {
@@ -3357,29 +3360,96 @@ static jb_socket bind_port_helper(struct configuration_spec * config)
             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);
+               (NULL != haddr) ? haddr : "INADDR_ANY", hport, 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);
+               (NULL != haddr) ? haddr : "INADDR_ANY", hport);
 
          default :
             log_error(LOG_LEVEL_FATAL, "can't bind to %s:%d: %E",
-               (NULL != config->haddr) ? config->haddr : "INADDR_ANY", config->hport);
+               (NULL != haddr) ? haddr : "INADDR_ANY", hport);
       }
 
       /* shouldn't get here */
       return JB_INVALID_SOCKET;
    }
 
-   config->need_bind = 0;
-
    return bfd;
 }
 
 
+/*********************************************************************
+ *
+ * Function    :  bind_ports_helper
+ *
+ * Description :  Bind the listen ports.  Handles logging, and aborts
+ *                on failure.
+ *
+ * Parameters  :
+ *          1  :  config = Privoxy configuration.  Specifies ports
+ *                         to bind to.
+ *          2  :  sockets = Preallocated array of opened sockets
+ *                          corresponding to specification in config.
+ *                          All non-opened sockets will be set to
+ *                          JB_INVALID_SOCKET.
+ *
+ * Returns     :  Nothing. Inspect sockets argument.
+ *
+ *********************************************************************/
+static void bind_ports_helper(struct configuration_spec * config,
+                              jb_socket sockets[])
+{
+   int i;
+
+   config->need_bind = 1;
+
+   for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
+   {
+      sockets[i] = JB_INVALID_SOCKET;
+
+      if (config->hport[i])
+      {
+         sockets[i] = bind_port_helper(config->haddr[i], config->hport[i]);
+         if (JB_INVALID_SOCKET != sockets[i])
+         {
+            config->need_bind = 0;
+         }
+      }
+   }
+}
+
+
+/*********************************************************************
+ *
+ * Function    :  close_ports_helper
+ *
+ * Description :  Close listenings ports.
+ *
+ * Parameters  :
+ *          1  :  sockets = Array of opened and non-opened sockets to
+ *                          close. All sockets will be set to
+ *                          JB_INVALID_SOCKET.
+ *
+ * Returns     :  Nothing.
+ *
+ *********************************************************************/
+static void close_ports_helper(jb_socket sockets[])
+{
+   int i;
+
+   for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
+   {
+      if (JB_INVALID_SOCKET != sockets[i])
+      {
+         close_socket(sockets[i]);
+      }
+      sockets[i] = JB_INVALID_SOCKET;
+   }
+}
+
+
 #ifdef _WIN32
 /* Without this simple workaround we get this compiler warning from _beginthread
  *     warning C4028: formal parameter 1 different from declaration
@@ -3406,7 +3476,7 @@ static void listen_loop(void)
 {
    struct client_states *csp_list = NULL;
    struct client_state *csp = NULL;
-   jb_socket bfd;
+   jb_socket bfds[MAX_LISTENING_SOCKETS];
    struct configuration_spec *config;
    unsigned int active_threads = 0;
 
@@ -3420,7 +3490,7 @@ static void listen_loop(void)
    initialize_reusable_connections();
 #endif /* def FEATURE_CONNECTION_SHARING */
 
-   bfd = bind_port_helper(config);
+   bind_ports_helper(config, bfds);
 
 #ifdef FEATURE_GRACEFUL_TERMINATION
    while (!g_terminate)
@@ -3465,7 +3535,7 @@ static void listen_loop(void)
 
       log_error(LOG_LEVEL_CONNECT, "Listening for new connections ... ");
 
-      if (!accept_connection(csp, bfd))
+      if (!accept_connection(csp, bfds))
       {
          log_error(LOG_LEVEL_CONNECT, "accept failed: %E");
 
@@ -3505,9 +3575,9 @@ static void listen_loop(void)
           * that this will hurt people's feelings.
           */
 
-         close_socket(bfd);
+         close_ports_helper(bfds);
 
-         bfd = bind_port_helper(config);
+         bind_ports_helper(config, bfds);
       }
 
 #ifdef FEATURE_TOGGLE
index ac86848..82ef8d2 100644 (file)
--- a/loadcfg.c
+++ b/loadcfg.c
@@ -1,4 +1,4 @@
-const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.116 2011/07/08 13:29:39 fabiankeil Exp $";
+const char loadcfg_rcs[] = "$Id: loadcfg.c,v 1.117 2011/07/08 13:30:08 fabiankeil Exp $";
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/loadcfg.c,v $
@@ -229,7 +229,10 @@ static void unload_configfile (void * data)
    freez(config->templdir);
    freez(config->hostname);
 
-   freez(config->haddr);
+   for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
+   {
+       freez(config->haddr[i]);
+   }
    freez(config->logfile);
 
    for (i = 0; i < MAX_AF_FILES; i++)
@@ -1066,8 +1069,23 @@ struct configuration_spec * load_config(void)
  * listen-address [ip][:port]
  * *************************************************************************/
          case hash_listen_address :
-            freez(config->haddr);
-            config->haddr = strdup(arg);
+            i = 0;
+            while ((i < MAX_LISTENING_SOCKETS) && (NULL != config->haddr[i]))
+            {
+               i++;
+            }
+
+            if (i >= MAX_LISTENING_SOCKETS)
+            {
+               log_error(LOG_LEVEL_FATAL, "Too many 'listen-address' directives in config file - limit is %d.\n"
+                  "(You can increase this limit by changing MAX_LISTENING_SOCKETS in project.h and recompiling).",
+                  MAX_LISTENING_SOCKETS);
+            }
+            config->haddr[i] = strdup(arg);
+            if (NULL == config->haddr[i])
+            {
+               log_error(LOG_LEVEL_FATAL, "Out of memory while copying listening address");
+            }
             break;
 
 /* *************************************************************************
@@ -1524,39 +1542,45 @@ struct configuration_spec * load_config(void)
    }
 #endif /* def FEATURE_TRUST */
 
-   if ( NULL == config->haddr )
+   if ( NULL == config->haddr[0] )
    {
-      config->haddr = strdup( HADDR_DEFAULT );
+      config->haddr[0] = strdup( HADDR_DEFAULT );
+      if (NULL == config->haddr[0])
+      {
+         log_error(LOG_LEVEL_FATAL, "Out of memory while copying default listening address");
+      }
    }
 
-   if ( NULL != config->haddr )
+   for (i = 0; i < MAX_LISTENING_SOCKETS && NULL != config->haddr[i]; i++ )
    {
-      if ((*config->haddr == '[')
-         && (NULL != (p = strchr(config->haddr, ']')))
+      if ((*config->haddr[i] == '[')
+         && (NULL != (p = strchr(config->haddr[i], ']')))
          && (p[1] == ':')
-         && (0 < (config->hport = atoi(p + 2))))
+         && (0 < (config->hport[i] = atoi(p + 2))))
       {
          *p = '\0';
-         memmove((void *)config->haddr, config->haddr + 1,
-            (size_t)(p - config->haddr));
+         memmove((void *)config->haddr[i], config->haddr[i] + 1,
+            (size_t)(p - config->haddr[i]));
       }
-      else if (NULL != (p = strchr(config->haddr, ':'))
-         && (0 < (config->hport = atoi(p + 1))))
+      else if (NULL != (p = strchr(config->haddr[i], ':'))
+         && (0 < (config->hport[i] = atoi(p + 1))))
       {
          *p = '\0';
       }
       else
       {
-         log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr);
+         log_error(LOG_LEVEL_FATAL, "invalid bind port spec %s", config->haddr[i]);
          /* Never get here - LOG_LEVEL_FATAL causes program exit */
       }
-      if (*config->haddr == '\0')
+      if (*config->haddr[i] == '\0')
       {
          /*
-          * Only the port specified. We stored it in config->hport
+          * Only the port specified. We stored it in config->hport[i]
           * and don't need its text representation anymore.
+          * Use config->hport[i] == 0 to iterate listening addresses since
+          * now.
           */
-         freez(config->haddr);
+         freez(config->haddr[i]);
       }
    }
 
@@ -1600,30 +1624,34 @@ struct configuration_spec * load_config(void)
       struct configuration_spec * oldcfg = (struct configuration_spec *)
                                            current_configfile->f;
       /*
-       * Check if config->haddr,hport == oldcfg->haddr,hport
+       * Check if config->haddr[i],hport[i] == oldcfg->haddr[i],hport[i]
        *
        * The following could be written more compactly as a single,
        * (unreadably long) if statement.
        */
       config->need_bind = 0;
-      if (config->hport != oldcfg->hport)
-      {
-         config->need_bind = 1;
-      }
-      else if (config->haddr == NULL)
+
+      for (i = 0; i < MAX_LISTENING_SOCKETS; i++)
       {
-         if (oldcfg->haddr != NULL)
+         if (config->hport[i] != oldcfg->hport[i])
+         {
+            config->need_bind = 1;
+         }
+         else if (config->haddr[i] == NULL)
+         {
+            if (oldcfg->haddr[i] != NULL)
+            {
+               config->need_bind = 1;
+            }
+         }
+         else if (oldcfg->haddr[i] == NULL)
+         {
+            config->need_bind = 1;
+         }
+         else if (0 != strcmp(config->haddr[i], oldcfg->haddr[i]))
          {
             config->need_bind = 1;
          }
-      }
-      else if (oldcfg->haddr == NULL)
-      {
-         config->need_bind = 1;
-      }
-      else if (0 != strcmp(config->haddr, oldcfg->haddr))
-      {
-         config->need_bind = 1;
       }
 
       current_configfile->unloader = unload_configfile;
index 37cfc41..d44eed4 100644 (file)
--- a/project.h
+++ b/project.h
@@ -1,7 +1,7 @@
 #ifndef PROJECT_H_INCLUDED
 #define PROJECT_H_INCLUDED
 /** Version string. */
-#define PROJECT_H_VERSION "$Id: project.h,v 1.168 2011/07/08 13:27:31 fabiankeil Exp $"
+#define PROJECT_H_VERSION "$Id: project.h,v 1.169 2011/07/08 13:30:08 fabiankeil Exp $"
 /*********************************************************************
  *
  * File        :  $Source: /cvsroot/ijbswa/current/project.h,v $
@@ -843,6 +843,12 @@ struct reusable_connection
  */
 #define MAX_AF_FILES 10
 
+/**
+ * Maximum number of sockets to listen to.  This limit is arbitrary - it's just used
+ * to size an array.
+ */
+#define MAX_LISTENING_SOCKETS 10
+
 /**
  * The state of a Privoxy processing thread.
  */
@@ -1259,11 +1265,11 @@ struct configuration_spec
    /** The hostname to show on CGI pages, or NULL to use the real one. */
    const char *hostname;
 
-   /** IP address to bind to.  Defaults to HADDR_DEFAULT == 127.0.0.1. */
-   const char *haddr;
+   /** IP addresses to bind to.  Defaults to HADDR_DEFAULT == 127.0.0.1. */
+   const char *haddr[MAX_LISTENING_SOCKETS];
 
-   /** Port to bind to.  Defaults to HADDR_PORT == 8118. */
-   int         hport;
+   /** Ports to bind to.  Defaults to HADDR_PORT == 8118. */
+   int         hport[MAX_LISTENING_SOCKETS];
 
    /** Size limit for IOB */
    size_t buffer_limit;