Merge branch 'multisocket-rebased' of https://github.com/twaugh/tigervnc
diff --git a/common/network/TcpSocket.cxx b/common/network/TcpSocket.cxx
index e19f52b..6fca301 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,6 +40,7 @@
 #endif
 
 #include <stdlib.h>
+#include <unistd.h>
 #include <network/TcpSocket.h>
 #include <rfb/util.h>
 #include <rfb/LogWriter.h>
@@ -62,16 +61,6 @@
 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);
@@ -152,7 +141,7 @@
 
   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
@@ -222,7 +211,7 @@
       err = errorNumber;
 #ifndef WIN32
       if (err == EINTR)
-	continue;
+        continue;
 #endif
       closesocket(sock);
       break;
@@ -274,16 +263,17 @@
     return rfb::strDup("");
   }
 
-#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_PTON)
+#if defined(HAVE_GETADDRINFO)
   if (sa.u.sa.sa_family == AF_INET6) {
     char buffer[INET6_ADDRSTRLEN + 2];
-    const char *name;
+    int ret;
 
     buffer[0] = '[';
 
-    name = inet_ntop(sa.u.sa.sa_family, &sa.u.sin6.sin6_addr,
-                     buffer + 1, sizeof(buffer) - 2);
-    if (name == NULL) {
+    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("");
     }
@@ -356,7 +346,7 @@
 #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);
+                                &myaddr.u.sin6.sin6_addr);
 #endif
 
   if (peeraddr.u.sa.sa_family == AF_INET)
@@ -375,7 +365,7 @@
 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;
@@ -426,139 +416,77 @@
   }
 }
 
-static int bindIPv6 (const char *listenaddr,
-		     int port,
-		     bool localhostOnly)
+TcpListener::TcpListener(int sock)
 {
-#ifdef HAVE_GETADDRINFO
-  struct sockaddr_in6 addr6;
-  socklen_t sa_len;
-  int fd;
-
-  if (!UseIPv6)
-    return -1;
-
-  if ((fd = socket(AF_INET6, SOCK_STREAM, 0)) < 0)
-    return -1;
-
-#ifdef IPV6_V6ONLY
-  // - We made an IPv6-capable socket, and we need it to do IPv4 too
-  int opt = 0;
-  setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, (char *)&opt, sizeof(opt));
-#else
-  vlog.error("IPV6_V6ONLY support is missing. "
-	     "IPv4 clients may not be able to connect.");
-#endif
-
-  memset(&addr6, 0, (sa_len = sizeof(addr6)));
-  addr6.sin6_family = AF_INET6;
-  addr6.sin6_port = htons(port);
-
-  if (localhostOnly)
-    addr6.sin6_addr = in6addr_loopback;
-  else if (listenaddr != NULL) {
-#ifdef HAVE_INET_PTON
-    if (inet_pton(AF_INET6, listenaddr, &addr6.sin6_addr) != 1) {
-      closesocket(fd);
-      return -1;
-    }
-#else
-    // Unable to parse without inet_pton
-    closesocket(fd);
-    return -1;
-#endif
-  }
-
-  if (bind(fd, (struct sockaddr *) &addr6, sa_len) == -1) {
-    closesocket(fd);
-    return -1;
-  }
-
-  return fd;
-#else
-  return -1;
-#endif /* HAVE_GETADDRINFO */
+  fd = sock;
 }
 
-static int bindIPv4 (const char *listenaddr,
-		     int port,
-		     bool localhostOnly)
+TcpListener::TcpListener(const TcpListener& other)
 {
-  struct sockaddr_in addr;
-  socklen_t sa_len;
-  int fd;
-
-  if (!UseIPv4)
-    return -1;
-
-  if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
-    return -1;
-
-  memset(&addr, 0, (sa_len = sizeof(addr)));
-  addr.sin_family = AF_INET;
-  addr.sin_port = htons(port);
-
-  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)
-#endif
-      {
-	closesocket(fd);
-	throw Exception("invalid network interface address: %s", listenaddr);
-      }
-  } else
-    /* Bind to 0.0.0.0 by default. */
-    addr.sin_addr.s_addr = htonl(INADDR_ANY);
-
-  if (bind(fd, (struct sockaddr *) &addr, sa_len) == -1) {
-    closesocket(fd);
-    return -1;
-  }
-
-  return fd;
+  fd = dup (other.fd);
+  // Hope TcpListener::shutdown(other) doesn't get called...
 }
 
-TcpListener::TcpListener(const char *listenaddr, int port, bool localhostOnly,
-			 int sock, bool close_) : closeFd(close_)
+TcpListener& TcpListener::operator= (const TcpListener& other)
 {
-  if (sock != -1) {
-    fd = sock;
-    return;
+  if (this != &other)
+  {
+    closesocket (fd);
+    fd = dup (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 = bindIPv6 (listenaddr, port, localhostOnly)) < 0)
-    if ((fd = bindIPv4 (listenaddr, port, localhostOnly)) < 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
 
   // - 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);
   }
+
+  fd = sock;
 }
 
 TcpListener::~TcpListener() {
-  if (closeFd) closesocket(fd);
+  closesocket(fd);
 }
 
 void TcpListener::shutdown()
@@ -596,46 +524,160 @@
   return s;
 }
 
-void TcpListener::getMyAddresses(std::list<char*>* result) {
-#if defined(HAVE_GETADDRINFO) && defined(HAVE_INET_PTON)
+int TcpListener::getMyPort() {
+  return TcpSocket::getSockPort(getFd());
+}
+
+
+void network::createLocalTcpListeners(std::list<TcpListener> *listeners,
+                                      int port)
+{
+  std::list<TcpListener> new_listeners;
+  vnc_sockaddr_t sa;
+#ifdef HAVE_GETADDRINFO
+  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;
+    }
+  }
+#endif /* HAVE_GETADDRINFO */
+  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;
+
+#ifdef HAVE_GETADDRINFO
   struct addrinfo *ai, *current, hints;
+  char service[16];
 
   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;
 
-  if ((getaddrinfo(NULL, NULL, &hints, &ai)) != 0)
-    return;
+  snprintf (service, sizeof (service) - 1, "%d", port);
+  service[sizeof (service) - 1] = '\0';
+  if ((getaddrinfo(addr, service, &hints, &ai)) != 0)
+    throw rdr::SystemException("getaddrinfo", errorNumber);
 
-  for (current= ai; current != NULL; current = current->ai_next) {
-    if (current->ai_family != AF_INET && current->ai_family != AF_INET6)
+  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];
-    inet_ntop(current->ai_family, current->ai_addr, addr, INET6_ADDRSTRLEN);
-    result->push_back(addr);
+    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);
 #else
-  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);
-    result->push_back(addr);
+  const hostent* addrs;
+  if (addr) {
+    /* Bind to specific address */
+    addrs = gethostbyname(addr);
+    if (addrs == 0)
+      throw rdr::SystemException("gethostbyname", errorNumber);
+    if (addrs->h_addrtype != AF_INET)
+      throw rdr::Exception("createTcpListeners: bad family");
+    for (int i=0; addrs->h_addr_list[i] != 0; i++) {
+      struct sockaddr_in addr;
+      addr.sin_family = AF_INET;
+      memcpy (&addr.sin_addr, addrs->h_addr_list[i], addrs->h_length);
+      addr.sin_port = htons(port);
+      try {
+        new_listeners.push_back(TcpListener ((struct sockaddr*)&addr,
+                                             addrs->h_length));
+      } 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;
+        }
+      }
+    }
+  } else {
+    /* Bind to any address */
+    struct sockaddr_in addr;
+    addr.sin_family = AF_INET;
+    addr.sin_addr.s_addr = htonl(INADDR_ANY);
+    addr.sin_port = htons(port);
+    try {
+      new_listeners.push_back(TcpListener ((struct sockaddr*)&addr,
+                                           sizeof (struct sockaddr_in)));
+    } 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;
+      }
+    }
   }
-#endif /* defined(HAVE_GETADDRINFO) && defined(HAVE_INET_PTON) */
-}
+#endif /* defined(HAVE_GETADDRINFO) */
 
-int TcpListener::getMyPort() {
-  return TcpSocket::getSockPort(getFd());
+  if (new_listeners.empty ())
+    throw SocketException("createTcpListeners: no addresses available",
+                          EADDRNOTAVAIL);
+
+  listeners->splice (listeners->end(), new_listeners);
 }
 
 
@@ -655,29 +697,69 @@
 
 
 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;
-
-#ifdef HAVE_GETADDRINFO
   vnc_sockaddr_t sa;
   socklen_t sa_size = sizeof(sa);
-  if (getpeername(s->getFd(), &sa.u.sa, &sa_size) != 0 ||
-      sa.u.sa.sa_family != AF_INET)
-    /* Matching only works for IPv4 */
+
+  if (getpeername(s->getFd(), &sa.u.sa, &sa_size) != 0)
     return false;
-#endif /* HAVE_GETADDRINFO */
 
   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);
@@ -701,31 +783,105 @@
 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;
 
-  if (rfb::strSplit(&p[1], '/', &addr.buf, &mask.buf)) {
-    if (rfb::strContains(mask.buf, '.')) {
-      pattern.mask = inet_addr(mask.buf);
-    } else {
-      pattern.mask = atoi(mask.buf);
-      expandMask = true;
-    }
+  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 {
-    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);
+#ifdef HAVE_GETADDRINFO
+    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));
+    }
+
+    memcpy (&pattern.address.u.sa, ai->ai_addr, ai->ai_addrlen);
+    freeaddrinfo (ai);
+#else
+    pattern.address.u.sa.sa_family = AF_INET;
+    pattern.address.u.sin.sin_addr.s_addr = inet_addr(addr.buf);
+#endif /* HAVE_GETADDRINFO */
+
+    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 {
+      switch (family) {
+      case AF_INET:
+        pattern.prefixlen = 32;
+        break;
+      case AF_INET6:
+        pattern.prefixlen = 128;
+        break;
+      default:
+        throw Exception("unknown address family");
+      }
+    }
   }
 
-  pattern.address = inet_addr(addr.buf) & pattern.mask;
-  if ((pattern.address == INADDR_NONE) ||
-      (pattern.address == 0)) pattern.mask = 0;
+  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;
@@ -737,21 +893,45 @@
 }
 
 char* TcpFilter::patternToStr(const TcpFilter::Pattern& p) {
+  rfb::CharArray addr;
+#ifdef HAVE_GETADDRINFO
+  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("");
+#else
   in_addr tmp;
-  rfb::CharArray addr, mask;
-  tmp.s_addr = p.address;
+  tmp.s_addr = p.address.u.sin.sin_addr.s_addr;
   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];
+#endif /* HAVE_GETADDRINFO */
+
+  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 b0bba53..29c09c6 100644
--- a/common/network/TcpSocket.h
+++ b/common/network/TcpSocket.h
@@ -28,7 +28,13 @@
 #ifndef __NETWORK_TCP_SOCKET_H__
 #define __NETWORK_TCP_SOCKET_H__
 
+#ifdef HAVE_CONFIG_H
+#include <config.h> /* for HAVE_GETADDRINFO */
+#endif
+
 #include <network/Socket.h>
+#include <sys/socket.h> /* for socklen_t */
+#include <netinet/in.h> /* for struct sockaddr_in */
 
 #include <list>
 
@@ -66,20 +72,34 @@
 
   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);
     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;
+#ifdef HAVE_GETADDRINFO
+      sockaddr_in6 sin6;
+#endif
+    } u;
+  } vnc_sockaddr_t;
+
   class TcpFilter : public ConnectionFilter {
   public:
     TcpFilter(const char* filter);
@@ -90,8 +110,10 @@
     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/unix/x0vncserver/x0vncserver.cxx b/unix/x0vncserver/x0vncserver.cxx
index a9b328f..2d9a066 100644
--- a/unix/x0vncserver/x0vncserver.cxx
+++ b/unix/x0vncserver/x0vncserver.cxx
@@ -477,6 +477,8 @@
   signal(SIGINT, CleanupSignalHandler);
   signal(SIGTERM, CleanupSignalHandler);
 
+  std::list<TcpListener> listeners;
+
   try {
     TXWindow::init(dpy,"x0vncserver");
     Geometry geo(DisplayWidth(dpy, DefaultScreen(dpy)),
@@ -491,13 +493,16 @@
     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);
@@ -513,7 +518,11 @@
 
       FD_ZERO(&rfds);
       FD_SET(ConnectionNumber(dpy), &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++) {
@@ -558,12 +567,16 @@
       }
 
       // 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/RFBGlue.cc b/unix/xserver/hw/vnc/RFBGlue.cc
index 53d5bdb..19338fc 100644
--- a/unix/xserver/hw/vnc/RFBGlue.cc
+++ b/unix/xserver/hw/vnc/RFBGlue.cc
@@ -187,7 +187,10 @@
 int vncIsTCPPortUsed(int port)
 {
   try {
-    network::TcpListener l(NULL, port);
+    // Attempt to create TCPListeners on that port.
+    // They go out of scope immediately and are destroyed.
+    std::list<network::TcpListener> dummy;
+    network::createTcpListeners (&dummy, 0, port);
   } catch (rdr::Exception& e) {
     return 0;
   }
diff --git a/unix/xserver/hw/vnc/XserverDesktop.cc b/unix/xserver/hw/vnc/XserverDesktop.cc
index 54e09cb..165afbb 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.cc
+++ b/unix/xserver/hw/vnc/XserverDesktop.cc
@@ -91,14 +91,14 @@
 
 
 XserverDesktop::XserverDesktop(int screenIndex_,
-                               network::TcpListener* listener_,
-                               network::TcpListener* httpListener_,
+                               std::list<network::TcpListener> listeners_,
+                               std::list<network::TcpListener> httpListeners_,
                                const char* name, const rfb::PixelFormat &pf,
                                int width, int height,
                                void* fbptr, int stride)
   : screenIndex(screenIndex_),
     server(0), httpServer(0),
-    listener(listener_), httpListener(httpListener_),
+    listeners(listeners_), httpListeners(httpListeners_),
     deferredUpdateTimerSet(false), directFbptr(true),
     queryConnectId(0)
 {
@@ -108,7 +108,7 @@
   setFramebuffer(width, height, fbptr, stride);
   server->setQueryConnectionHandler(this);
 
-  if (httpListener)
+  if (!httpListeners.empty ())
     httpServer = new FileHTTPServer(this);
 }
 
@@ -222,7 +222,7 @@
   }
   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) {
@@ -393,14 +393,18 @@
 
     // 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()) {
@@ -452,20 +456,24 @@
     // 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 7dcaa29..6909a76 100644
--- a/unix/xserver/hw/vnc/XserverDesktop.h
+++ b/unix/xserver/hw/vnc/XserverDesktop.h
@@ -50,8 +50,9 @@
                        public rfb::VNCServerST::QueryConnectionHandler {
 public:
 
-  XserverDesktop(int screenIndex, network::TcpListener* listener,
-                 network::TcpListener* httpListener_,
+  XserverDesktop(int screenIndex,
+                 std::list<network::TcpListener> listeners_,
+                 std::list<network::TcpListener> httpListeners_,
                  const char* name, const rfb::PixelFormat &pf,
                  int width, int height, void* fbptr, int stride);
   virtual ~XserverDesktop();
@@ -112,8 +113,8 @@
   int screenIndex;
   rfb::VNCServerST* server;
   rfb::HTTPServer* httpServer;
-  network::TcpListener* listener;
-  network::TcpListener* httpListener;
+  std::list<network::TcpListener> listeners;
+  std::list<network::TcpListener> httpListeners;
   bool deferredUpdateTimerSet;
   bool directFbptr;
   struct timeval dixTimeout;
diff --git a/unix/xserver/hw/vnc/vncExtInit.cc b/unix/xserver/hw/vnc/vncExtInit.cc
index 2277783..e307e31 100644
--- a/unix/xserver/hw/vnc/vncExtInit.cc
+++ b/unix/xserver/hw/vnc/vncExtInit.cc
@@ -134,33 +134,44 @@
     for (int scr = 0; scr < vncGetScreenCount(); 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::isSocket(vncInetdSock) &&
               !network::TcpSocket::isConnected(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(vncGetDisplay());
           port += 1000 * scr;
-          if (strcasecmp(interface, "all") == 0)
-            listener = new network::TcpListener(NULL, port, localhostOnly);
+          if (strcasecmp(addr, "all") == 0)
+            addr = 0;
+          if (localhostOnly)
+            network::createLocalTcpListeners(&listeners, port);
           else
-            listener = new network::TcpListener(interface, port, localhostOnly);
+            network::createTcpListeners(&listeners, addr, port);
+
           vlog.info("Listening for VNC connections on %s interface(s), port %d",
-                    (const char*)interface, port);
+                    localhostOnly ? "local" : (const char*)interface,
+                    port);
+
           CharArray httpDirStr(httpDir.getData());
           if (httpDirStr.buf[0]) {
             port = httpPort;
             if (port == 0) port = 5800 + atoi(vncGetDisplay());
             port += 1000 * scr;
-            httpListener = new network::TcpListener(interface, 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",
-                      (const char*)interface, port);
+                      localhostOnly ? "local" : (const char*)interface,
+                      port);
           }
         }
 
@@ -168,8 +179,8 @@
         PixelFormat pf = vncGetPixelFormat(scr);
 
         desktop[scr] = new XserverDesktop(scr,
-                                          listener,
-                                          httpListener,
+                                          listeners,
+                                          httpListeners,
                                           desktopNameStr.buf,
                                           pf,
                                           vncGetScreenWidth(scr),
@@ -178,7 +189,7 @@
                                           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 5f84679..6fe6356 100644
--- a/vncviewer/vncviewer.cxx
+++ b/vncviewer/vncviewer.cxx
@@ -532,15 +532,45 @@
 #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());