Blob Blame History Raw
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f2656d1..0e325e2 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -71,16 +71,9 @@ if(NOT DEFINED BUILD_WINVNC)
   set(BUILD_WINVNC 1)
 endif()
 
-# Minimum version is Windows 2000 (5.0)
+# Minimum version is Windows XP SP2 (5.2)
 if(WIN32)
-  if(NOT CMAKE_SIZEOF_VOID_P MATCHES 8)
-    add_definitions(-D_WIN32_IE=0x0500 -D_WIN32_WINNT=0x0500)
-  else()
-    set(WIN64 1)
-    # Win64 doesn't like us requesting a Windows version that didn't have
-    # 64-bit support. Request XP (5.1) instead.
-    add_definitions(-D_WIN32_IE=0x0501 -D_WIN32_WINNT=0x0501)
-  endif()
+  add_definitions(-D_WIN32_IE=0x0502 -D_WIN32_WINNT=0x0502)
 endif()
 
 if(CMAKE_SIZEOF_VOID_P MATCHES 8)
@@ -366,20 +359,6 @@ if(ENABLE_PAM)
 endif()
 set(HAVE_PAM ${ENABLE_PAM})
 
-# Check for socket functions
-if(WIN32)
-  set(CMAKE_EXTRA_INCLUDE_FILES winsock2.h ws2tcpip.h)
-  set(CMAKE_REQUIRED_LIBRARIES ws2_32)
-else()
-  set(CMAKE_EXTRA_INCLUDE_FILES sys/socket.h)
-endif()
-check_function_exists(inet_aton HAVE_INET_ATON)
-check_function_exists(inet_ntop HAVE_INET_NTOP)
-check_function_exists(getaddrinfo HAVE_GETADDRINFO)
-check_type_size(socklen_t SOCKLEN_T)
-set(CMAKE_EXTRA_INCLUDE_FILES) 
-set(CMAKE_REQUIRED_LIBRARIES)
-
 # Check for the newer standard string functions
 check_function_exists(snprintf HAVE_SNPRINTF)
 check_function_exists(strcasecmp HAVE_STRCASECMP)
diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx
index afd66f9..83387ab 100644
--- a/common/network/TcpSocket.cxx
+++ b/common/network/TcpSocket.cxx
@@ -31,10 +31,8 @@
 #include <sys/types.h>
 #include <sys/socket.h>
 #include <arpa/inet.h>
-#include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <netdb.h>
-#include <unistd.h>
 #include <errno.h>
 #include <string.h>
 #include <signal.h>
@@ -42,10 +40,15 @@
 #endif
 
 #include <stdlib.h>
+#include <unistd.h>
 #include <network/TcpSocket.h>
-#include <os/net.h>
 #include <rfb/util.h>
 #include <rfb/LogWriter.h>
+#include <rfb/Configuration.h>
+
+#ifdef WIN32
+#include <os/winerrno.h>
+#endif
 
 #ifndef INADDR_NONE
 #define INADDR_NONE ((unsigned long)-1)
@@ -54,26 +57,24 @@
 #define INADDR_LOOPBACK ((unsigned long)0x7F000001)
 #endif
 
-#if defined(HAVE_GETADDRINFO) && !defined(IN6_ARE_ADDR_EQUAL)
+#ifndef IN6_ARE_ADDR_EQUAL
 #define IN6_ARE_ADDR_EQUAL(a,b) \
   (memcmp ((const void*)(a), (const void*)(b), sizeof (struct in6_addr)) == 0)
 #endif
 
+// Missing on older Windows and OS X
+#ifndef AI_NUMERICSERV
+#define AI_NUMERICSERV 0
+#endif
+
 using namespace network;
 using namespace rdr;
 
-typedef struct vnc_sockaddr {
-	union {
-		sockaddr	sa;
-		sockaddr_in	sin;
-#ifdef HAVE_GETADDRINFO
-		sockaddr_in6	sin6;
-#endif
-	} u;
-} vnc_sockaddr_t;
-
 static rfb::LogWriter vlog("TcpSocket");
 
+static rfb::BoolParameter UseIPv4("UseIPv4", "Use IPv4 for incoming and outgoing connections.", true);
+static rfb::BoolParameter UseIPv6("UseIPv6", "Use IPv6 for incoming and outgoing connections.", true);
+
 /* Tunnelling support. */
 int network::findFreeTcpPort (void)
 {
@@ -117,6 +118,23 @@ static void initSockets() {
 }
 
 
+// -=- Socket duplication help for Windows
+static int dupsocket(int fd)
+{
+#ifdef WIN32
+  int ret;
+  WSAPROTOCOL_INFO info;
+  ret = WSADuplicateSocket(fd, GetCurrentProcessId(), &info);
+  if (ret != 0)
+    throw SocketException("unable to duplicate socket", errorNumber);
+  return WSASocket(info.iAddressFamily, info.iSocketType, info.iProtocol,
+                   &info, 0, 0);
+#else
+  return dup(fd);
+#endif
+}
+
+
 // -=- TcpSocket
 
 TcpSocket::TcpSocket(int sock, bool close)
@@ -130,14 +148,11 @@ TcpSocket::TcpSocket(const char *host, int port)
   int sock, err, result, family;
   vnc_sockaddr_t sa;
   socklen_t salen;
-#ifdef HAVE_GETADDRINFO
   struct addrinfo *ai, *current, hints;
-#endif
 
   // - Create a socket
   initSockets();
 
-#ifdef HAVE_GETADDRINFO
   memset(&hints, 0, sizeof(struct addrinfo));
   hints.ai_family = AF_UNSPEC;
   hints.ai_socktype = SOCK_STREAM;
@@ -147,13 +162,28 @@ TcpSocket::TcpSocket(const char *host, int port)
 
   if ((result = getaddrinfo(host, NULL, &hints, &ai)) != 0) {
     throw Exception("unable to resolve host by name: %s",
-		    gai_strerror(result));
+                    gai_strerror(result));
   }
 
+  // This logic is too complex for the compiler to determine if
+  // sock is properly assigned or not.
+  sock = -1;
+
   for (current = ai; current != NULL; current = current->ai_next) {
     family = current->ai_family;
-    if (family != AF_INET && family != AF_INET6)
+
+    switch (family) {
+    case AF_INET:
+      if (!UseIPv4)
+        continue;
+      break;
+    case AF_INET6:
+      if (!UseIPv6)
+        continue;
+      break;
+    default:
       continue;
+    }
 
     salen = current->ai_addrlen;
     memcpy(&sa, current->ai_addr, salen);
@@ -163,34 +193,10 @@ TcpSocket::TcpSocket(const char *host, int port)
     else
       sa.u.sin6.sin6_port = htons(port);
 
-#else /* HAVE_GETADDRINFO */
-    family = AF_INET;
-    salen = sizeof(struct sockaddr_in);
-
-    /* Try processing the host as an IP address */
-    memset(&sa, 0, sizeof(sa));
-    sa.u.sin.sin_family = AF_INET;
-    sa.u.sin.sin_addr.s_addr = inet_addr((char *)host);
-    sa.u.sin.sin_port = htons(port);
-    if ((int)sa.u.sin.sin_addr.s_addr == -1) {
-      /* Host was not an IP address - try resolving as DNS name */
-      struct hostent *hostinfo;
-      hostinfo = gethostbyname((char *)host);
-      if (hostinfo && hostinfo->h_addr) {
-        sa.u.sin.sin_addr.s_addr = ((struct in_addr *)hostinfo->h_addr)->s_addr;
-      } else {
-        err = errorNumber;
-        throw SocketException("unable to resolve host by name", err);
-      }
-    }
-#endif /* HAVE_GETADDRINFO */
-
     sock = socket (family, SOCK_STREAM, 0);
     if (sock == -1) {
       err = errorNumber;
-#ifdef HAVE_GETADDRINFO
       freeaddrinfo(ai);
-#endif /* HAVE_GETADDRINFO */
       throw SocketException("unable to create socket", err);
     }
 
@@ -199,19 +205,20 @@ TcpSocket::TcpSocket(const char *host, int port)
       err = errorNumber;
 #ifndef WIN32
       if (err == EINTR)
-	continue;
+        continue;
 #endif
       closesocket(sock);
       break;
     }
 
-#ifdef HAVE_GETADDRINFO
     if (result == 0)
       break;
   }
 
   freeaddrinfo(ai);
-#endif /* HAVE_GETADDRINFO */
+
+  if (current == NULL)
+    throw Exception("No useful address for host");
 
   if (result == -1)
     throw SocketException("unable connect to socket", err);
@@ -240,27 +247,63 @@ int TcpSocket::getMyPort() {
 }
 
 char* TcpSocket::getPeerAddress() {
-  struct sockaddr_in  info;
-  struct in_addr    addr;
-  socklen_t info_size = sizeof(info);
+  vnc_sockaddr_t sa;
+  socklen_t sa_size = sizeof(sa);
 
-  getpeername(getFd(), (struct sockaddr *)&info, &info_size);
-  memcpy(&addr, &info.sin_addr, sizeof(addr));
+  if (getpeername(getFd(), &sa.u.sa, &sa_size) != 0) {
+    vlog.error("unable to get peer name for socket");
+    return rfb::strDup("");
+  }
+
+  if (sa.u.sa.sa_family == AF_INET6) {
+    char buffer[INET6_ADDRSTRLEN + 2];
+    int ret;
+
+    buffer[0] = '[';
+
+    ret = getnameinfo(&sa.u.sa, sizeof(sa.u.sin6),
+                      buffer + 1, sizeof(buffer) - 2, NULL, 0,
+                      NI_NUMERICHOST);
+    if (ret != 0) {
+      vlog.error("unable to convert peer name to a string");
+      return rfb::strDup("");
+    }
+
+    strcat(buffer, "]");
+
+    return rfb::strDup(buffer);
+  }
+
+  if (sa.u.sa.sa_family == AF_INET) {
+    char *name;
+
+    name = inet_ntoa(sa.u.sin.sin_addr);
+    if (name == NULL) {
+      vlog.error("unable to convert peer name to a string");
+      return rfb::strDup("");
+    }
 
-  char* name = inet_ntoa(addr);
-  if (name) {
     return rfb::strDup(name);
-  } else {
-    return rfb::strDup("");
   }
+
+  vlog.error("unknown address family for socket");
+  return rfb::strDup("");
 }
 
 int TcpSocket::getPeerPort() {
-  struct sockaddr_in  info;
-  socklen_t info_size = sizeof(info);
+  vnc_sockaddr_t sa;
+  socklen_t sa_size = sizeof(sa);
+
+  getpeername(getFd(), &sa.u.sa, &sa_size);
 
-  getpeername(getFd(), (struct sockaddr *)&info, &info_size);
-  return ntohs(info.sin_port);
+  switch (sa.u.sa.sa_family) {
+  case AF_INET6:
+    return ntohs(sa.u.sin6.sin6_port);
+  case AF_INET:
+    return ntohs(sa.u.sin.sin_port);
+  default:
+    return 0;
+  }
 }
 
 char* TcpSocket::getPeerEndpoint() {
@@ -288,13 +331,14 @@ bool TcpSocket::sameMachine() {
   if (peeraddr.u.sa.sa_family != myaddr.u.sa.sa_family)
       return false;
 
-#ifdef HAVE_GETADDRINFO
   if (peeraddr.u.sa.sa_family == AF_INET6)
       return IN6_ARE_ADDR_EQUAL(&peeraddr.u.sin6.sin6_addr,
-				&myaddr.u.sin6.sin6_addr);
-#endif
+                                &myaddr.u.sin6.sin6_addr);
+  if (peeraddr.u.sa.sa_family == AF_INET)
+    return (peeraddr.u.sin.sin_addr.s_addr == myaddr.u.sin.sin_addr.s_addr);
 
-  return (peeraddr.u.sin.sin_addr.s_addr == myaddr.u.sin.sin_addr.s_addr);
+  // No idea what this is. Assume we're on different machines.
+  return false;
 }
 
 void TcpSocket::shutdown()
@@ -306,7 +350,7 @@ void TcpSocket::shutdown()
 bool TcpSocket::enableNagles(int sock, bool enable) {
   int one = enable ? 0 : 1;
   if (setsockopt(sock, IPPROTO_TCP, TCP_NODELAY,
-		 (char *)&one, sizeof(one)) < 0) {
+                 (char *)&one, sizeof(one)) < 0) {
     int e = errorNumber;
     vlog.error("unable to setsockopt TCP_NODELAY: %d", e);
     return false;
@@ -335,77 +379,89 @@ bool TcpSocket::isListening(int sock)
 
 int TcpSocket::getSockPort(int sock)
 {
-  struct sockaddr_in info;
-  socklen_t info_size = sizeof(info);
-  if (getsockname(sock, (struct sockaddr *)&info, &info_size) < 0)
-    return 0;
-  return ntohs(info.sin_port);
+  vnc_sockaddr_t sa;
+  socklen_t sa_size = sizeof(sa);
+  if (getsockname(sock, &sa.u.sa, &sa_size) < 0)
+     return 0;
+
+  switch (sa.u.sa.sa_family) {
+  case AF_INET6:
+    return ntohs(sa.u.sin6.sin6_port);
+  default:
+    return ntohs(sa.u.sin.sin_port);
+  }
 }
 
+TcpListener::TcpListener(int sock)
+{
+  fd = sock;
+}
 
-TcpListener::TcpListener(const char *listenaddr, int port, bool localhostOnly,
-			 int sock, bool close_) : closeFd(close_)
+TcpListener::TcpListener(const TcpListener& other)
 {
-  if (sock != -1) {
-    fd = sock;
-    return;
+  fd = dupsocket (other.fd);
+  // Hope TcpListener::shutdown(other) doesn't get called...
+}
+
+TcpListener& TcpListener::operator= (const TcpListener& other)
+{
+  if (this != &other)
+  {
+    closesocket (fd);
+    fd = dupsocket (other.fd);
+    // Hope TcpListener::shutdown(other) doesn't get called...
   }
+  return *this;
+}
+
+TcpListener::TcpListener(const struct sockaddr *listenaddr,
+                         socklen_t listenaddrlen)
+{
+  int one = 1;
+  vnc_sockaddr_t sa;
+  int sock;
 
   initSockets();
-  if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
-    throw SocketException("unable to create listening socket", errorNumber);
 
+  if ((sock = socket (listenaddr->sa_family, SOCK_STREAM, 0)) < 0)
+     throw SocketException("unable to create listening socket", errorNumber);
+
+  memcpy (&sa, listenaddr, listenaddrlen);
+#ifdef IPV6_V6ONLY
+  if (listenaddr->sa_family == AF_INET6) {
+    if (setsockopt (sock, IPPROTO_IPV6, IPV6_V6ONLY, (char*)&one, sizeof(one)))
+      throw SocketException("unable to set IPV6_V6ONLY", errorNumber);
+  }
+#endif /* defined(IPV6_V6ONLY) */
+
+  if (bind(sock, &sa.u.sa, listenaddrlen) == -1) {
+    closesocket(sock);
+    throw SocketException("failed to bind socket", errorNumber);
+  }
 #ifndef WIN32
   // - By default, close the socket on exec()
-  fcntl(fd, F_SETFD, FD_CLOEXEC);
+  fcntl(sock, F_SETFD, FD_CLOEXEC);
 
-  int one = 1;
-  if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,
-		 (char *)&one, sizeof(one)) < 0) {
+  if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
+                 (char *)&one, sizeof(one)) < 0) {
     int e = errorNumber;
-    closesocket(fd);
+    closesocket(sock);
     throw SocketException("unable to create listening socket", e);
   }
-#endif
-
-  // - Bind it to the desired port
-  struct sockaddr_in addr;
-  memset(&addr, 0, sizeof(addr));
-  addr.sin_family = AF_INET;
 
-  if (localhostOnly) {
-    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-  } else if (listenaddr != NULL) {
-#ifdef HAVE_INET_ATON
-    if (inet_aton(listenaddr, &addr.sin_addr) == 0)
-#else
-    /* Some systems (e.g. Windows) do not have inet_aton, sigh */
-    if ((addr.sin_addr.s_addr = inet_addr(listenaddr)) == INADDR_NONE)
+  fd = sock;
 #endif
-    {
-      closesocket(fd);
-      throw Exception("invalid network interface address: %s", listenaddr);
-    }
-  } else
-    addr.sin_addr.s_addr = htonl(INADDR_ANY); /* Bind to 0.0.0.0 by default. */
-
-  addr.sin_port = htons(port);
-  if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
-    int e = errorNumber;
-    closesocket(fd);
-    throw SocketException("unable to bind listening socket", e);
-  }
 
   // - Set it to be a listening socket
-  if (listen(fd, 5) < 0) {
+  if (listen(sock, 5) < 0) {
     int e = errorNumber;
-    closesocket(fd);
+    closesocket(sock);
     throw SocketException("unable to set socket to listening mode", e);
   }
 }
 
 TcpListener::~TcpListener() {
-  if (closeFd) closesocket(fd);
+  closesocket(fd);
 }
 
 void TcpListener::shutdown()
@@ -444,17 +500,45 @@ TcpListener::accept() {
 }
 
 void TcpListener::getMyAddresses(std::list<char*>* result) {
-  const hostent* addrs = gethostbyname(0);
-  if (addrs == 0)
-    throw rdr::SystemException("gethostbyname", errorNumber);
-  if (addrs->h_addrtype != AF_INET)
-    throw rdr::Exception("getMyAddresses: bad family");
-  for (int i=0; addrs->h_addr_list[i] != 0; i++) {
-    const char* addrC = inet_ntoa(*((struct in_addr*)addrs->h_addr_list[i]));
-    char* addr = new char[strlen(addrC)+1];
-    strcpy(addr, addrC);
+  struct addrinfo *ai, *current, hints;
+
+  initSockets();
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_canonname = NULL;
+  hints.ai_addr = NULL;
+  hints.ai_next = NULL;
+
+  // Windows doesn't like NULL for service, so specify something
+  if ((getaddrinfo(NULL, "1", &hints, &ai)) != 0)
+    return;
+
+  for (current= ai; current != NULL; current = current->ai_next) {
+    switch (current->ai_family) {
+    case AF_INET:
+      if (!UseIPv4)
+        continue;
+      break;
+    case AF_INET6:
+      if (!UseIPv6)
+        continue;
+      break;
+    default:
+      continue;
+    }
+
+    char *addr = new char[INET6_ADDRSTRLEN];
+
+    getnameinfo(current->ai_addr, current->ai_addrlen, addr, INET6_ADDRSTRLEN,
+                NULL, 0, NI_NUMERICHOST);
+
     result->push_back(addr);
   }
+
+  freeaddrinfo(ai);
 }
 
 int TcpListener::getMyPort() {
@@ -462,6 +546,115 @@ int TcpListener::getMyPort() {
 }
 
 
+void network::createLocalTcpListeners(std::list<TcpListener> *listeners,
+                                      int port)
+{
+  std::list<TcpListener> new_listeners;
+  vnc_sockaddr_t sa;
+
+  initSockets();
+
+  if (UseIPv6) {
+    sa.u.sin6.sin6_family = AF_INET6;
+    sa.u.sin6.sin6_port = htons (port);
+    sa.u.sin6.sin6_addr = in6addr_loopback;
+    try {
+      new_listeners.push_back (TcpListener (&sa.u.sa, sizeof (sa.u.sin6)));
+    } catch (SocketException& e) {
+      // Ignore this if it is due to lack of address family support on
+      // the interface or on the system
+      if (e.err != EADDRNOTAVAIL && e.err != EAFNOSUPPORT)
+        // Otherwise, report the error
+        throw;
+    }
+  }
+  if (UseIPv4) {
+    sa.u.sin.sin_family = AF_INET;
+    sa.u.sin.sin_port = htons (port);
+    sa.u.sin.sin_addr.s_addr = htonl (INADDR_LOOPBACK);
+    try {
+      new_listeners.push_back (TcpListener (&sa.u.sa, sizeof (sa.u.sin)));
+    } catch (SocketException& e) {
+      // Ignore this if it is due to lack of address family support on
+      // the interface or on the system
+      if (e.err != EADDRNOTAVAIL && e.err != EAFNOSUPPORT)
+        // Otherwise, report the error
+        throw;
+    }
+  }
+
+  if (new_listeners.empty ())
+    throw SocketException("createLocalTcpListeners: no addresses available",
+                          EADDRNOTAVAIL);
+
+  listeners->splice (listeners->end(), new_listeners);
+}
+
+void network::createTcpListeners(std::list<TcpListener> *listeners,
+                                 const char *addr,
+                                 int port)
+{
+  std::list<TcpListener> new_listeners;
+
+  struct addrinfo *ai, *current, hints;
+  char service[16];
+  int result;
+
+  initSockets();
+
+  memset(&hints, 0, sizeof(struct addrinfo));
+  hints.ai_flags = AI_PASSIVE | AI_NUMERICSERV;
+  hints.ai_family = AF_UNSPEC;
+  hints.ai_socktype = SOCK_STREAM;
+  hints.ai_canonname = NULL;
+  hints.ai_addr = NULL;
+  hints.ai_next = NULL;
+
+  snprintf (service, sizeof (service) - 1, "%d", port);
+  service[sizeof (service) - 1] = '\0';
+  if ((result = getaddrinfo(addr, service, &hints, &ai)) != 0)
+    throw rdr::Exception("unable to resolve listening address: %s",
+                         gai_strerror(result));
+
+  for (current = ai; current != NULL; current = current->ai_next) {
+    switch (current->ai_family) {
+    case AF_INET:
+      if (!UseIPv4)
+        continue;
+      break;
+
+    case AF_INET6:
+      if (!UseIPv6)
+        continue;
+      break;
+
+    default:
+      continue;
+    }
+
+    try {
+      new_listeners.push_back(TcpListener (current->ai_addr,
+                                           current->ai_addrlen));
+    } catch (SocketException& e) {
+      // Ignore this if it is due to lack of address family support on
+      // the interface or on the system
+      if (e.err != EADDRNOTAVAIL && e.err != EAFNOSUPPORT) {
+        // Otherwise, report the error
+        freeaddrinfo(ai);
+        throw;
+      }
+    }
+  }
+  freeaddrinfo(ai);
+
+  if (new_listeners.empty ())
+    throw SocketException("createTcpListeners: no addresses available",
+                          EADDRNOTAVAIL);
+
+  listeners->splice (listeners->end(), new_listeners);
+}
+
+
 TcpFilter::TcpFilter(const char* spec) {
   rfb::CharArray tmp;
   tmp.buf = rfb::strDup(spec);
@@ -478,20 +671,69 @@ TcpFilter::~TcpFilter() {
 
 
 static bool
-patternMatchIP(const TcpFilter::Pattern& pattern, const char* value) {
-  unsigned long address = inet_addr((char *)value);
-  if (address == INADDR_NONE) return false;
-  return ((pattern.address & pattern.mask) == (address & pattern.mask));
+patternMatchIP(const TcpFilter::Pattern& pattern, vnc_sockaddr_t *sa) {
+  switch (pattern.address.u.sa.sa_family) {
+    unsigned long address;
+
+  case AF_INET:
+    if (sa->u.sa.sa_family != AF_INET)
+      return false;
+
+    address = sa->u.sin.sin_addr.s_addr;
+    if (address == htonl (INADDR_NONE)) return false;
+    return ((pattern.address.u.sin.sin_addr.s_addr &
+             pattern.mask.u.sin.sin_addr.s_addr) ==
+            (address & pattern.mask.u.sin.sin_addr.s_addr));
+
+  case AF_INET6:
+    if (sa->u.sa.sa_family != AF_INET6)
+      return false;
+
+    for (unsigned int n = 0; n < 16; n++) {
+      unsigned int bits = (n + 1) * 8;
+      unsigned int mask;
+      if (pattern.prefixlen > bits)
+        mask = 0xff;
+      else {
+        unsigned int lastbits = 0xff;
+        lastbits <<= bits - pattern.prefixlen;
+        mask = lastbits & 0xff;
+      }
+
+      if ((pattern.address.u.sin6.sin6_addr.s6_addr[n] & mask) !=
+          (sa->u.sin6.sin6_addr.s6_addr[n] & mask))
+        return false;
+
+      if (mask < 0xff)
+        break;
+    }
+
+    return true;
+
+  case AF_UNSPEC:
+    // Any address matches
+    return true;
+
+  default:
+    break;
+  }
+
+  return false;
 }
 
 bool
 TcpFilter::verifyConnection(Socket* s) {
   rfb::CharArray name;
+  vnc_sockaddr_t sa;
+  socklen_t sa_size = sizeof(sa);
+
+  if (getpeername(s->getFd(), &sa.u.sa, &sa_size) != 0)
+    return false;
 
   name.buf = s->getPeerAddress();
   std::list<TcpFilter::Pattern>::iterator i;
   for (i=filter.begin(); i!=filter.end(); i++) {
-    if (patternMatchIP(*i, name.buf)) {
+    if (patternMatchIP(*i, &sa)) {
       switch ((*i).action) {
       case Accept:
         vlog.debug("ACCEPT %s", name.buf);
@@ -515,31 +757,102 @@ TcpFilter::verifyConnection(Socket* s) {
 TcpFilter::Pattern TcpFilter::parsePattern(const char* p) {
   TcpFilter::Pattern pattern;
 
-  bool expandMask = false;
-  rfb::CharArray addr, mask;
+  rfb::CharArray addr, pref;
+  bool prefix_specified;
+  int family;
+
+  prefix_specified = rfb::strSplit(&p[1], '/', &addr.buf, &pref.buf);
+  if (addr.buf[0] == '\0') {
+    // Match any address
+    memset (&pattern.address, 0, sizeof (pattern.address));
+    pattern.address.u.sa.sa_family = AF_UNSPEC;
+    pattern.prefixlen = 0;
+  } else {
+    struct addrinfo hints;
+    struct addrinfo *ai;
+    char *p = addr.buf;
+    int result;
+    memset (&hints, 0, sizeof (hints));
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_flags = AI_NUMERICHOST;
+
+    // Take out brackets, if present
+    if (*p == '[') {
+      size_t len;
+      p++;
+      len = strlen (p);
+      if (len > 0 && p[len - 1] == ']')
+        p[len - 1] = '\0';
+    }
+
+    if ((result = getaddrinfo (p, NULL, &hints, &ai)) != 0) {
+      throw Exception("unable to resolve host by name: %s",
+                      gai_strerror(result));
+    }
 
-  if (rfb::strSplit(&p[1], '/', &addr.buf, &mask.buf)) {
-    if (rfb::strContains(mask.buf, '.')) {
-      pattern.mask = inet_addr(mask.buf);
+    memcpy (&pattern.address.u.sa, ai->ai_addr, ai->ai_addrlen);
+    freeaddrinfo (ai);
+
+    family = pattern.address.u.sa.sa_family;
+
+    if (prefix_specified) {
+      if (family == AF_INET &&
+          rfb::strContains(pref.buf, '.')) {
+        throw Exception("mask no longer supported for filter, "
+                        "use prefix instead");
+      }
+
+      pattern.prefixlen = (unsigned int) atoi(pref.buf);
     } else {
-      pattern.mask = atoi(mask.buf);
-      expandMask = true;
+      switch (family) {
+      case AF_INET:
+        pattern.prefixlen = 32;
+        break;
+      case AF_INET6:
+        pattern.prefixlen = 128;
+        break;
+      default:
+        throw Exception("unknown address family");
+      }
     }
-  } else {
-    pattern.mask = 32;
-    expandMask = true;
-  }
-  if (expandMask) {
-    unsigned long expanded = 0;
-    // *** check endianness!
-    for (int i=0; i<(int)pattern.mask; i++)
-      expanded |= 1<<(31-i);
-    pattern.mask = htonl(expanded);
   }
 
-  pattern.address = inet_addr(addr.buf) & pattern.mask;
-  if ((pattern.address == INADDR_NONE) ||
-      (pattern.address == 0)) pattern.mask = 0;
+  family = pattern.address.u.sa.sa_family;
+
+  if (pattern.prefixlen > (family == AF_INET ? 32: 128))
+    throw Exception("invalid prefix length for filter address: %u",
+                    pattern.prefixlen);
+
+  // Compute mask from address and prefix length
+  memset (&pattern.mask, 0, sizeof (pattern.mask));
+  switch (family) {
+    unsigned long mask;
+  case AF_INET:
+    mask = 0;
+    for (unsigned int i=0; i<pattern.prefixlen; i++)
+      mask |= 1<<(31-i);
+    pattern.mask.u.sin.sin_addr.s_addr = htonl(mask);
+    break;
+
+  case AF_INET6:
+    for (unsigned int n = 0; n < 16; n++) {
+      unsigned int bits = (n + 1) * 8;
+      if (pattern.prefixlen > bits)
+        pattern.mask.u.sin6.sin6_addr.s6_addr[n] = 0xff;
+      else {
+        unsigned int lastbits = 0xff;
+        lastbits <<= bits - pattern.prefixlen;
+        pattern.mask.u.sin6.sin6_addr.s6_addr[n] = lastbits & 0xff;
+        break;
+      }
+    }
+    break;
+  case AF_UNSPEC:
+    // No mask to compute
+    break;
+  default:
+    ; /* not reached */
+  }
 
   switch(p[0]) {
   case '+': pattern.action = TcpFilter::Accept; break;
@@ -551,21 +864,39 @@ TcpFilter::Pattern TcpFilter::parsePattern(const char* p) {
 }
 
 char* TcpFilter::patternToStr(const TcpFilter::Pattern& p) {
-  in_addr tmp;
-  rfb::CharArray addr, mask;
-  tmp.s_addr = p.address;
-  addr.buf = rfb::strDup(inet_ntoa(tmp));
-  tmp.s_addr = p.mask;
-  mask.buf = rfb::strDup(inet_ntoa(tmp));
-  char* result = new char[strlen(addr.buf)+1+strlen(mask.buf)+1+1];
+  rfb::CharArray addr;
+  char buffer[INET6_ADDRSTRLEN + 2];
+
+  if (p.address.u.sa.sa_family == AF_INET) {
+    getnameinfo(&p.address.u.sa, sizeof(p.address.u.sin),
+                buffer, sizeof (buffer), NULL, 0, NI_NUMERICHOST);
+    addr.buf = rfb::strDup(buffer);
+  } else if (p.address.u.sa.sa_family == AF_INET6) {
+    buffer[0] = '[';
+    getnameinfo(&p.address.u.sa, sizeof(p.address.u.sin6),
+                buffer + 1, sizeof (buffer) - 2, NULL, 0, NI_NUMERICHOST);
+    strcat(buffer, "]");
+    addr.buf = rfb::strDup(buffer);
+  } else if (p.address.u.sa.sa_family == AF_UNSPEC)
+    addr.buf = rfb::strDup("");
+
+  char action;
   switch (p.action) {
-  case Accept: result[0] = '+'; break;
-  case Reject: result[0] = '-'; break;
-  case Query: result[0] = '?'; break;
+  case Accept: action = '+'; break;
+  case Reject: action = '-'; break;
+  default:
+  case Query: action = '?'; break;
   };
-  result[1] = 0;
-  strcat(result, addr.buf);
-  strcat(result, "/");
-  strcat(result, mask.buf);
+  size_t resultlen = (1                   // action
+                      + strlen (addr.buf) // address
+                      + 1                 // slash
+                      + 3                 // prefix length, max 128
+                      + 1);               // terminating nul
+  char* result = new char[resultlen];
+  if (addr.buf[0] == '\0')
+    snprintf(result, resultlen, "%c", action);
+  else
+    snprintf(result, resultlen, "%c%s/%u", action, addr.buf, p.prefixlen);
+
   return result;
 }
diff --git a/common/network/TcpSocket.h b/common/network/TcpSocket.h
index cd4393e..3f72c85 100644
--- a/common/network/TcpSocket.h
+++ b/common/network/TcpSocket.h
@@ -30,6 +30,14 @@
 
 #include <network/Socket.h>
 
+#ifdef WIN32
+#include <winsock2.h>
+#include <ws2tcpip.h>
+#else
+#include <sys/socket.h> /* for socklen_t */
+#include <netinet/in.h> /* for struct sockaddr_in */
+#endif
+
 #include <list>
 
 /* Tunnelling support. */
@@ -65,20 +73,33 @@ namespace network {
 
   class TcpListener : public SocketListener {
   public:
-    TcpListener(const char *listenaddr, int port, bool localhostOnly=false,
-		int sock=-1, bool close=true);
+    TcpListener(const struct sockaddr *listenaddr, socklen_t listenaddrlen);
+    TcpListener(int sock);
+    TcpListener(const TcpListener& other);
+    TcpListener& operator= (const TcpListener& other);
     virtual ~TcpListener();
 
     virtual void shutdown();
     virtual Socket* accept();
 
-    void getMyAddresses(std::list<char*>* addrs);
+    static void getMyAddresses(std::list<char*>* result);
     int getMyPort();
-
-  private:
-    bool closeFd;
   };
 
+  void createLocalTcpListeners(std::list<TcpListener> *listeners,
+                               int port);
+  void createTcpListeners(std::list<TcpListener> *listeners,
+                          const char *addr,
+                          int port);
+
+  typedef struct vnc_sockaddr {
+    union {
+      sockaddr     sa;
+      sockaddr_in  sin;
+      sockaddr_in6 sin6;
+    } u;
+  } vnc_sockaddr_t;
+
   class TcpFilter : public ConnectionFilter {
   public:
     TcpFilter(const char* filter);
@@ -89,8 +110,10 @@ namespace network {
     typedef enum {Accept, Reject, Query} Action;
     struct Pattern {
       Action action;
-      unsigned long address;
-      unsigned long mask;
+      vnc_sockaddr_t address;
+      unsigned int prefixlen;
+
+      vnc_sockaddr_t mask; // computed from address and prefix
     };
     static Pattern parsePattern(const char* s);
     static char* patternToStr(const Pattern& p);
diff --git a/common/os/CMakeLists.txt b/common/os/CMakeLists.txt
index 39d5c10..cd066f8 100644
--- a/common/os/CMakeLists.txt
+++ b/common/os/CMakeLists.txt
@@ -2,7 +2,6 @@ include_directories(${CMAKE_SOURCE_DIR}/common)
 
 add_library(os STATIC
   print.c
-  net.c
   w32tiger.c
   os.cxx
   tls.cxx)
diff --git a/common/os/net.c b/common/os/net.c
deleted file mode 100644
index 7bad36c..0000000
--- a/common/os/net.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/* Copyright (C) 2008 TightVNC Team.  All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * 
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
- * USA.
- */
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#include <stdlib.h>
-#include <string.h>
-
-#ifdef WIN32
-#include <winsock2.h>
-#include <ws2tcpip.h>
-#else
-#include <sys/socket.h>
-#include <netinet/in.h>
-#include <arpa/inet.h>
-#endif
-
-#include <os/net.h>
-
-
-#ifndef HAVE_INET_NTOP
-const char *tight_inet_ntop(int af, const void *src, char *dst,
-			    socklen_t size) {
-	char *tempstr;
-
-	/* Catch bugs - we should not use IPv6 if we don't have inet_ntop */
-	if (af != AF_INET)
-		abort();
-
-	/* inet_ntoa never fails */
-	tempstr = inet_ntoa(*(struct in_addr *)(src));
-	memcpy(dst, tempstr, strlen(tempstr) + 1);
-
-	return dst;
-}
-#endif
diff --git a/common/os/net.h b/common/os/net.h
deleted file mode 100644
index bd8b21c..0000000
--- a/common/os/net.h
+++ /dev/null
@@ -1,50 +0,0 @@
-/* Copyright (C) 2008 TightVNC Team.  All Rights Reserved.
- *
- * This is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- * 
- * This software is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- * GNU General Public License for more details.
- * 
- * You should have received a copy of the GNU General Public License
- * along with this software; if not, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307,
- * USA.
- */
-
-#ifndef OS_NET_H
-#define OS_NET_H
-
-#ifdef HAVE_CONFIG_H
-#include <config.h>
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#ifndef HAVE_SOCKLEN_T
-typedef int socklen_t;
-#endif
-
-/* IPv6 support on server side - we have to have all those functions */
-#if defined(HAVE_INET_NTOP)
-#define HAVE_IPV6
-#endif
-
-/* IPv4-only stub implementation */
-#ifndef HAVE_INET_NTOP
-const char *tight_inet_ntop(int af, const void *src,
-			    char *dst, socklen_t size);
-#define inet_ntop tight_inet_ntop
-#endif
-
-#ifdef __cplusplus
-};
-#endif
-
-#endif /* OS_NET_H */
diff --git a/config.h.in b/config.h.in
index ec2aed9..c6d27ee 100644
--- a/config.h.in
+++ b/config.h.in
@@ -2,9 +2,6 @@
 #define PACKAGE_VERSION "@VERSION@"
 
 #cmakedefine HAVE_SOCKLEN_T
-#cmakedefine HAVE_INET_ATON
-#cmakedefine HAVE_INET_NTOP
-#cmakedefine HAVE_GETADDRINFO
 #cmakedefine HAVE_SNPRINTF
 #cmakedefine HAVE_STRCASECMP
 #cmakedefine HAVE_STRNCASECMP
diff --git a/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx
index b5498e1..400af75 100644
--- a/unix/x0vncserver/x0vncserver.cxx
+++ b/unix/x0vncserver/x0vncserver.cxx
@@ -434,6 +434,8 @@ int main(int argc, char** argv)
   signal(SIGINT, CleanupSignalHandler);
   signal(SIGTERM, CleanupSignalHandler);
 
+  std::list<TcpListener> listeners;
+
   try {
     TXWindow::init(dpy,"x0vncserver");
     Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
@@ -448,13 +450,16 @@ int main(int argc, char** argv)
     QueryConnHandler qcHandler(dpy, &server);
     server.setQueryConnectionHandler(&qcHandler);
 
-    TcpListener listener(NULL, (int)rfbport);
+    createTcpListeners(&listeners, 0, (int)rfbport);
     vlog.info("Listening on port %d", (int)rfbport);
 
     const char *hostsData = hostsFile.getData();
     FileTcpFilter fileTcpFilter(hostsData);
     if (strlen(hostsData) != 0)
-      listener.setFilter(&fileTcpFilter);
+      for (std::list<TcpListener>::iterator i = listeners.begin();
+           i != listeners.end();
+           i++)
+        (*i).setFilter(&fileTcpFilter);
     delete[] hostsData;
 
     PollingScheduler sched((int)pollingCycle, (int)maxProcessorUsage);
@@ -469,7 +474,11 @@ int main(int argc, char** argv)
       TXWindow::handleXEvents(dpy);
 
       FD_ZERO(&rfds);
-      FD_SET(listener.getFd(), &rfds);
+      for (std::list<TcpListener>::iterator i = listeners.begin();
+           i != listeners.end();
+           i++)
+        FD_SET((*i).getFd(), &rfds);
+
       server.getSockets(&sockets);
       int clients_connected = 0;
       for (i = sockets.begin(); i != sockets.end(); i++) {
@@ -514,12 +523,16 @@ int main(int argc, char** argv)
       }
 
       // Accept new VNC connections
-      if (FD_ISSET(listener.getFd(), &rfds)) {
-        Socket* sock = listener.accept();
-        if (sock) {
-          server.addSocket(sock);
-        } else {
-          vlog.status("Client connection rejected");
+      for (std::list<TcpListener>::iterator i = listeners.begin();
+           i != listeners.end();
+           i++) {
+        if (FD_ISSET((*i).getFd(), &rfds)) {
+          Socket* sock = (*i).accept();
+          if (sock) {
+            server.addSocket(sock);
+          } else {
+            vlog.status("Client connection rejected");
+          }
         }
       }
 
diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc
index 05169b0..d0a9953 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.cc
+++ b/unix/xserver/hw/vnc/XserverDesktop.cc
@@ -135,13 +135,13 @@ public:
 
 
 XserverDesktop::XserverDesktop(ScreenPtr pScreen_,
-                               network::TcpListener* listener_,
-                               network::TcpListener* httpListener_,
+                               std::list<network::TcpListener> listeners_,
+                               std::list<network::TcpListener> httpListeners_,
                                const char* name, const rfb::PixelFormat &pf,
                                void* fbptr, int stride)
   : pScreen(pScreen_),
     server(0), httpServer(0),
-    listener(listener_), httpListener(httpListener_),
+    listeners(listeners_), httpListeners(httpListeners_),
     cmap(0), deferredUpdateTimerSet(false),
     grabbing(false), ignoreHooks_(false), directFbptr(true),
     queryConnectId(0)
@@ -155,7 +155,7 @@ XserverDesktop::XserverDesktop(ScreenPtr pScreen_,
   setFramebuffer(pScreen->width, pScreen->height, fbptr, stride);
   server->setQueryConnectionHandler(this);
 
-  if (httpListener)
+  if (!httpListeners.empty ())
     httpServer = new FileHTTPServer(this);
 }
 
@@ -315,7 +315,7 @@ char* XserverDesktop::substitute(const char* varName)
   }
   if (strcmp(varName, "$PORT") == 0) {
     char* str = new char[10];
-    sprintf(str, "%d", listener ? listener->getMyPort() : 0);
+    sprintf(str, "%d", listeners.empty () ? 0 : (*listeners.begin ()).getMyPort());
     return str;
   }
   if (strcmp(varName, "$WIDTH") == 0) {
@@ -587,14 +587,18 @@ void XserverDesktop::blockHandler(fd_set* fds, OSTimePtr timeout)
 
     // Add all sockets we want read events for, after purging
     // any closed sockets.
-    if (listener)
-      FD_SET(listener->getFd(), fds);
-    if (httpListener)
-      FD_SET(httpListener->getFd(), fds);
+    for (std::list<network::TcpListener>::iterator i = listeners.begin();
+         i != listeners.end();
+         i++)
+      FD_SET((*i).getFd(), fds);
+    for (std::list<network::TcpListener>::iterator i = httpListeners.begin();
+         i != httpListeners.end();
+         i++)
+      FD_SET((*i).getFd(), fds);
 
     std::list<Socket*> sockets;
-    server->getSockets(&sockets);
     std::list<Socket*>::iterator i;
+    server->getSockets(&sockets);
     for (i = sockets.begin(); i != sockets.end(); i++) {
       int fd = (*i)->getFd();
       if ((*i)->isShutdown()) {
@@ -646,20 +650,24 @@ void XserverDesktop::wakeupHandler(fd_set* fds, int nfds)
     // First check for file descriptors with something to do
     if (nfds >= 1) {
 
-      if (listener) {
-        if (FD_ISSET(listener->getFd(), fds)) {
-          FD_CLR(listener->getFd(), fds);
-          Socket* sock = listener->accept();
+      for (std::list<network::TcpListener>::iterator i = listeners.begin();
+           i != listeners.end();
+           i++) {
+        if (FD_ISSET((*i).getFd(), fds)) {
+          FD_CLR((*i).getFd(), fds);
+          Socket* sock = (*i).accept();
           sock->outStream().setBlocking(false);
           server->addSocket(sock);
           vlog.debug("new client, sock %d",sock->getFd());
         }
       }
 
-      if (httpListener) {
-        if (FD_ISSET(httpListener->getFd(), fds)) {
-          FD_CLR(httpListener->getFd(), fds);
-          Socket* sock = httpListener->accept();
+      for (std::list<network::TcpListener>::iterator i = httpListeners.begin();
+           i != httpListeners.end();
+           i++) {
+        if (FD_ISSET((*i).getFd(), fds)) {
+          FD_CLR((*i).getFd(), fds);
+          Socket* sock = (*i).accept();
           sock->outStream().setBlocking(false);
           httpServer->addSocket(sock);
           vlog.debug("new http client, sock %d",sock->getFd());
diff --git a/unix/xserver/hw/vnc/XserverDesktop.h b/unix/xserver/hw/vnc/XserverDesktop.h
index ca6e8af..23cdc83 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.h
+++ b/unix/xserver/hw/vnc/XserverDesktop.h
@@ -58,8 +58,9 @@ class XserverDesktop : public rfb::SDesktop, public rfb::FullFramePixelBuffer,
                        public rfb::VNCServerST::QueryConnectionHandler {
 public:
 
-  XserverDesktop(ScreenPtr pScreen, network::TcpListener* listener,
-                 network::TcpListener* httpListener_,
+  XserverDesktop(ScreenPtr pScreen,
+                 std::list<network::TcpListener> listeners_,
+                 std::list<network::TcpListener> httpListeners_,
                  const char* name, const rfb::PixelFormat &pf,
                  void* fbptr, int stride);
   virtual ~XserverDesktop();
@@ -136,8 +137,8 @@ private:
   InputDevice *inputDevice;
   rfb::VNCServerST* server;
   rfb::HTTPServer* httpServer;
-  network::TcpListener* listener;
-  network::TcpListener* httpListener;
+  std::list<network::TcpListener> listeners;
+  std::list<network::TcpListener> httpListeners;
   ColormapPtr cmap;
   int stride_;
   bool deferredUpdateTimerSet;
diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc
index 1abf7d5..1cfaf51 100644
--- a/unix/xserver/hw/vnc/vncExtInit.cc
+++ b/unix/xserver/hw/vnc/vncExtInit.cc
@@ -127,6 +127,9 @@ rfb::StringParameter desktopName("desktop", "Name of VNC desktop","x11");
 rfb::BoolParameter localhostOnly("localhost",
                                  "Only allow connections from localhost",
                                  false);
+rfb::StringParameter interface("interface",
+                               "listen on the specified network address",
+                               "all");
 
 static PixelFormat vncGetPixelFormat(ScreenPtr pScreen)
 {
@@ -222,29 +225,43 @@ void vncExtensionInit()
     for (int scr = 0; scr < screenInfo.numScreens; scr++) {
 
       if (!desktop[scr]) {
-        network::TcpListener* listener = 0;
-        network::TcpListener* httpListener = 0;
+        std::list<network::TcpListener> listeners;
+        std::list<network::TcpListener> httpListeners;
         if (scr == 0 && vncInetdSock != -1) {
           if (network::TcpSocket::isListening(vncInetdSock))
           {
-            listener = new network::TcpListener(NULL, 0, 0, vncInetdSock, true);
+            listeners.push_back (network::TcpListener(vncInetdSock));
             vlog.info("inetd wait");
           }
         } else {
+          const char *addr = interface;
           int port = rfbport;
           if (port == 0) port = 5900 + atoi(display);
           port += 1000 * scr;
-          listener = new network::TcpListener(listenaddr, port, localhostOnly);
+          if (strcasecmp(addr, "all") == 0)
+            addr = 0;
+          if (localhostOnly)
+            network::createLocalTcpListeners(&listeners, port);
+          else
+            network::createTcpListeners(&listeners, addr, port);
+
           vlog.info("Listening for VNC connections on %s interface(s), port %d",
-		    listenaddr == NULL ? "all" : listenaddr, port);
+                    localhostOnly ? "local" : (const char*)interface,
+                    port);
+
           CharArray httpDirStr(httpDir.getData());
           if (httpDirStr.buf[0]) {
             port = httpPort;
             if (port == 0) port = 5800 + atoi(display);
             port += 1000 * scr;
-            httpListener = new network::TcpListener(listenaddr, port, localhostOnly);
+            if (localhostOnly)
+              network::createLocalTcpListeners(&httpListeners, port);
+            else
+              network::createTcpListeners(&httpListeners, addr, port);
+
             vlog.info("Listening for HTTP connections on %s interface(s), port %d",
-		      listenaddr == NULL ? "all" : listenaddr, port);
+                      localhostOnly ? "local" : (const char*)interface,
+                      port);
           }
         }
 
@@ -252,15 +269,15 @@ void vncExtensionInit()
         PixelFormat pf = vncGetPixelFormat(screenInfo.screens[scr]);
 
         desktop[scr] = new XserverDesktop(screenInfo.screens[scr],
-                                          listener,
-                                          httpListener,
+                                          listeners,
+                                          httpListeners,
                                           desktopNameStr.buf,
                                           pf,
                                           vncFbptr[scr],
                                           vncFbstride[scr]);
         vlog.info("created VNC server for screen %d", scr);
 
-        if (scr == 0 && vncInetdSock != -1 && !listener) {
+        if (scr == 0 && vncInetdSock != -1 && listeners.empty()) {
           network::Socket* sock = new network::TcpSocket(vncInetdSock);
           desktop[scr]->addClient(sock, false);
           vlog.info("added inetd sock");
diff --git a/vncviewer/vncviewer.cxx b/vncviewer/vncviewer.cxx
index 2d7685f..81bf849 100644
--- a/vncviewer/vncviewer.cxx
+++ b/vncviewer/vncviewer.cxx
@@ -459,15 +459,45 @@ int main(int argc, char** argv)
 #endif
 
   if (listenMode) {
+    std::list<TcpListener> listeners;
     try {
       int port = 5500;
       if (isdigit(vncServerName[0]))
         port = atoi(vncServerName);
 
-      TcpListener listener(NULL, port);
+      createTcpListeners(&listeners, 0, port);
 
       vlog.info("Listening on port %d\n", port);
-      sock = listener.accept();   
+
+      /* Wait for a connection */
+      while (sock == NULL) {
+        fd_set rfds;
+        FD_ZERO(&rfds);
+        for (std::list<TcpListener>::iterator i = listeners.begin();
+             i != listeners.end();
+             i++)
+          FD_SET((*i).getFd(), &rfds);
+
+        int n = select(FD_SETSIZE, &rfds, 0, 0, 0);
+        if (n < 0) {
+          if (errno == EINTR) {
+            vlog.debug("Interrupted select() system call");
+            continue;
+          } else {
+            throw rdr::SystemException("select", errno);
+          }
+        }
+
+        for (std::list<TcpListener>::iterator i = listeners.begin ();
+             i != listeners.end();
+             i++)
+          if (FD_ISSET((*i).getFd(), &rfds)) {
+            sock = (*i).accept();
+            if (sock)
+              /* Got a connection */
+              break;
+          }
+      }
     } catch (rdr::Exception& e) {
       vlog.error("%s", e.str());
       fl_alert("%s", e.str());
diff --git a/win/rfb_win32/Dialog.cxx b/win/rfb_win32/Dialog.cxx
index 70a5fb5..24e0d9f 100644
--- a/win/rfb_win32/Dialog.cxx
+++ b/win/rfb_win32/Dialog.cxx
@@ -86,21 +86,17 @@ TCHAR* Dialog::getItemString(int id) {
 }
 
 void Dialog::setItemChecked(int id, bool state) {
-  dlog.debug("bool[%d]=%d", id, (int)state);
   SendMessage(GetDlgItem(handle, id), BM_SETCHECK, state ? BST_CHECKED : BST_UNCHECKED, 0);
 }
 void Dialog::setItemInt(int id, int value) {
-  dlog.debug("int[%d]=%d", id, value);
   SetDlgItemInt(handle, id, value, TRUE);
 }
 void Dialog::setItemString(int id, const TCHAR* s) {
-  dlog.debug("string[%d]=%s", id, (const char*)CStr(s));
   SetDlgItemText(handle, id, s);
 }
 
 
 void Dialog::enableItem(int id, bool state) {
-  dlog.debug("enable[%d]=%d", id, (int)state);
   EnableWindow(GetDlgItem(handle, id), state);
 }
 
@@ -361,7 +357,6 @@ bool PropSheet::showPropSheet(HWND owner, bool showApply, bool showCtxtHelp, boo
 }
 
 void PropSheet::reInitPages() {
-  plog.debug("reInitPages %lx", handle);
   std::list<PropSheetPage*>::iterator pspi;
   for (pspi=pages.begin(); pspi!=pages.end(); pspi++) {
     if ((*pspi)->handle)
@@ -370,7 +365,6 @@ void PropSheet::reInitPages() {
 }
 
 bool PropSheet::commitPages() {
-  plog.debug("commitPages %lx", handle);
   bool result = true;
   std::list<PropSheetPage*>::iterator pspi;
   for (pspi=pages.begin(); pspi!=pages.end(); pspi++) {
@@ -383,7 +377,6 @@ bool PropSheet::commitPages() {
 
 void PropSheetPage::setChanged(bool changed) {
   if (propSheet) {
-    plog.debug("setChanged[%lx(%lx)]=%d", handle, propSheet->handle, (int)changed);
     if (changed)
       PropSheet_Changed(propSheet->handle, handle);
     else
diff --git a/win/rfb_win32/SocketManager.cxx b/win/rfb_win32/SocketManager.cxx
index d4f1965..b073b8f 100644
--- a/win/rfb_win32/SocketManager.cxx
+++ b/win/rfb_win32/SocketManager.cxx
@@ -174,7 +174,7 @@ void SocketManager::processEvent(HANDLE event) {
       vlog.info("deleting listening socket");
       remListener(li.sock);
     } else if (network_events.lNetworkEvents & FD_ADDRESS_LIST_CHANGE) {
-      li.notifier->processAddressChange(li.sock);
+      li.notifier->processAddressChange();
       requestAddressChangeEvents(li.sock);
     } else {
       vlog.error("unknown listener event: %lx", network_events.lNetworkEvents);
diff --git a/win/rfb_win32/SocketManager.h b/win/rfb_win32/SocketManager.h
index ef35974..c3c8faf 100644
--- a/win/rfb_win32/SocketManager.h
+++ b/win/rfb_win32/SocketManager.h
@@ -48,7 +48,7 @@ namespace rfb {
       class AddressChangeNotifier {
       public:
         virtual ~AddressChangeNotifier() {}
-        virtual void processAddressChange(network::SocketListener* sl) = 0;
+        virtual void processAddressChange() = 0;
       };
 
       // Add a listening socket.  Incoming connections will be added to the supplied
diff --git a/win/vncconfig/vncconfig.cxx b/win/vncconfig/vncconfig.cxx
index 535febb..74d5e66 100644
--- a/win/vncconfig/vncconfig.cxx
+++ b/win/vncconfig/vncconfig.cxx
@@ -16,6 +16,7 @@
  * USA.
  */
 
+#include <winsock2.h>
 #include <windows.h>
 #include <commctrl.h>
 #include <string.h>
diff --git a/win/winvnc/ManagedListener.cxx b/win/winvnc/ManagedListener.cxx
index f2933bb..ac408e7 100644
--- a/win/winvnc/ManagedListener.cxx
+++ b/win/winvnc/ManagedListener.cxx
@@ -26,12 +26,16 @@ static LogWriter vlog("ManagedListener");
 
 
 ManagedListener::ManagedListener(SocketManager* mgr)
-: sock(0), filter(0), manager(mgr), addrChangeNotifier(0), server(0), port(0), localOnly(false) {
+: filter(0), manager(mgr), addrChangeNotifier(0), server(0), port(0), localOnly(false) {
 }
 
 ManagedListener::~ManagedListener() {
-  if (sock)
-    manager->remListener(sock);
+  if (!sockets.empty()) {
+    std::list<network::TcpListener>::iterator iter;
+    for (iter = sockets.begin(); iter != sockets.end(); ++iter)
+      manager->remListener(&*iter);
+    sockets.clear();
+  }
   delete filter;
 }
 
@@ -57,8 +61,11 @@ void ManagedListener::setFilter(const char* filterStr) {
   vlog.info("set filter to %s", filterStr);
   delete filter;
   filter = new network::TcpFilter(filterStr);
-  if (sock && !localOnly)
-    sock->setFilter(filter);
+  if (!sockets.empty() && !localOnly) {
+    std::list<network::TcpListener>::iterator iter;
+    for (iter = sockets.begin(); iter != sockets.end(); ++iter)
+      iter->setFilter(filter);
+  }
 }
 
 void ManagedListener::setAddressChangeNotifier(SocketManager::AddressChangeNotifier* acn) {
@@ -68,26 +75,39 @@ void ManagedListener::setAddressChangeNotifier(SocketManager::AddressChangeNotif
   refresh();
 }
 
+bool ManagedListener::isListening() {
+  return !sockets.empty();
+}
 
 void ManagedListener::refresh() {
-  if (sock)
-    manager->remListener(sock);
-  sock = 0;
+  std::list<network::TcpListener>::iterator iter;
+  if (!sockets.empty()) {
+    for (iter = sockets.begin(); iter != sockets.end(); ++iter)
+      manager->remListener(&*iter);
+    sockets.clear();
+  }
   if (!server)
     return;
   try {
-    if (port)
-      sock = new network::TcpListener(NULL, port, localOnly);
+    if (port) {
+      if (localOnly)
+        network::createLocalTcpListeners(&sockets, port);
+      else
+        network::createTcpListeners(&sockets, NULL, port);
+    }
   } catch (rdr::Exception& e) {
     vlog.error(e.str());
   }
-  if (sock) {
-    if (!localOnly)
-      sock->setFilter(filter);
+  if (!sockets.empty()) {
+    if (!localOnly) {
+      for (iter = sockets.begin(); iter != sockets.end(); ++iter)
+        iter->setFilter(filter);
+    }
     try {
-      manager->addListener(sock, server, addrChangeNotifier);
-    } catch (...) {
-      sock = 0;
+      for (iter = sockets.begin(); iter != sockets.end(); ++iter)
+        manager->addListener(&*iter, server, addrChangeNotifier);    } catch (...) {
+      // FIXME: Should unwind what we've added
+      sockets.clear();
       throw;
     }
   }
diff --git a/win/winvnc/ManagedListener.h b/win/winvnc/ManagedListener.h
index e83aa0b..1c7099f 100644
--- a/win/winvnc/ManagedListener.h
+++ b/win/winvnc/ManagedListener.h
@@ -40,10 +40,12 @@ namespace winvnc {
     void setPort(int port, bool localOnly=false);
     void setFilter(const char* filter);
     void setAddressChangeNotifier(rfb::win32::SocketManager::AddressChangeNotifier* acn);
-  
-    network::TcpListener* sock;
+
+    bool isListening();
+
   protected:
     void refresh();
+    std::list<network::TcpListener> sockets;
     network::TcpFilter* filter;
     rfb::win32::SocketManager* manager;
     rfb::win32::SocketManager::AddressChangeNotifier* addrChangeNotifier;
diff --git a/win/winvnc/VNCServerWin32.cxx b/win/winvnc/VNCServerWin32.cxx
index 4d89a0e..467ff70 100644
--- a/win/winvnc/VNCServerWin32.cxx
+++ b/win/winvnc/VNCServerWin32.cxx
@@ -43,7 +43,7 @@ static IntParameter http_port("HTTPPortNumber",
 static IntParameter port_number("PortNumber",
   "TCP/IP port on which the server will accept connections", 5900);
 static StringParameter hosts("Hosts",
-  "Filter describing which hosts are allowed access to this server", "+0.0.0.0/0.0.0.0");
+  "Filter describing which hosts are allowed access to this server", "+");
 static BoolParameter localHost("LocalHost",
   "Only accept connections from via the local loop-back network interface", false);
 static BoolParameter queryOnlyIfLoggedOn("QueryOnlyIfLoggedOn",
@@ -86,8 +86,8 @@ VNCServerWin32::~VNCServerWin32() {
 }
 
 
-void VNCServerWin32::processAddressChange(network::SocketListener* sock_) {
-  if (!trayIcon || (sock_ != rfbSock.sock))
+void VNCServerWin32::processAddressChange() {
+  if (!trayIcon)
     return;
 
   // Tool-tip prefix depends on server mode
@@ -97,8 +97,8 @@ void VNCServerWin32::processAddressChange(network::SocketListener* sock_) {
 
   // Fetch the list of addresses
   std::list<char*> addrs;
-  if (rfbSock.sock)
-    rfbSock.sock->getMyAddresses(&addrs);
+  if (rfbSock.isListening())
+    TcpListener::getMyAddresses(&addrs);
   else
     addrs.push_front(strDup("Not accepting connections"));
 
@@ -132,7 +132,7 @@ void VNCServerWin32::regConfigChanged() {
   httpSock.setPort(http_port, localHost);
 
   // -=- Update the Java viewer's web page port number.
-  httpServer.setRFBport(rfbSock.sock ? port_number : 0);
+  httpServer.setRFBport(rfbSock.isListening() ? port_number : 0);
 
   // -=- Update the TCP address filter for both ports, if open.
   CharArray pattern(hosts.getData());
@@ -140,7 +140,7 @@ void VNCServerWin32::regConfigChanged() {
   httpSock.setFilter(pattern.buf);
 
   // -=- Update the tray icon tooltip text with IP addresses
-  processAddressChange(rfbSock.sock);
+  processAddressChange();
 }
 
 
diff --git a/win/winvnc/VNCServerWin32.h b/win/winvnc/VNCServerWin32.h
index 5b40a5a..27305d0 100644
--- a/win/winvnc/VNCServerWin32.h
+++ b/win/winvnc/VNCServerWin32.h
@@ -82,7 +82,7 @@ namespace winvnc {
 
     // SocketManager::AddressChangeNotifier interface
     // Used to keep tray icon up to date
-    virtual void processAddressChange(network::SocketListener* sl);
+    virtual void processAddressChange();
 
     // RegConfig::Callback interface
     // Called via the EventManager whenver RegConfig sees the registry change