Integrate unfix.org's IPv6 patches up to level 10, with rather a lot
[u/mdw/putty] / unix / uxnet.c
index 44b09c1..f1ef98b 100644 (file)
 #include <netinet/in.h>
 #include <netinet/tcp.h>
 #include <netdb.h>
+#include <sys/un.h>
 
 #define DEFINE_PLUG_METHOD_MACROS
 #include "putty.h"
 #include "network.h"
 #include "tree234.h"
 
+#ifndef X11_UNIX_PATH
+# define X11_UNIX_PATH "/tmp/.X11-unix/X"
+#endif
+
+#define ipv4_is_loopback(addr) (inet_netof(addr) == IN_LOOPBACKNET)
+
 struct Socket_tag {
     struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
-    char *error;
+    const char *error;
     int s;
     Plug plug;
     void *private_ptr;
@@ -54,17 +61,26 @@ struct Socket_tag {
 typedef struct Socket_tag *Actual_Socket;
 
 struct SockAddr_tag {
-    char *error;
-    /* address family this belongs to, AF_INET for IPv4, AF_INET6 for IPv6. */
+    const char *error;
+    /*
+     * Which address family this address belongs to. AF_INET for
+     * IPv4; AF_INET6 for IPv6; AF_UNSPEC indicates that name
+     * resolution has not been done and a simple host name is held
+     * in this SockAddr structure.
+     */
     int family;
-    unsigned long address;            /* Address IPv4 style. */
-#ifdef IPV6
+#ifndef NO_IPV6
     struct addrinfo *ai;              /* Address IPv6 style. */
+#else
+    unsigned long address;            /* Address IPv4 style. */
 #endif
+    char hostname[512];                       /* Store an unresolved host name. */
 };
 
 static tree234 *sktree;
 
+static void uxsel_tell(Actual_Socket s);
+
 static int cmpfortree(void *av, void *bv)
 {
     Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
@@ -79,7 +95,7 @@ static int cmpfortree(void *av, void *bv)
 static int cmpforsearch(void *av, void *bv)
 {
     Actual_Socket b = (Actual_Socket) bv;
-    int as = (int) av, bs = b->s;
+    int as = *(int *)av, bs = b->s;
     if (as < bs)
        return -1;
     if (as > bs)
@@ -104,86 +120,21 @@ void sk_cleanup(void)
     }
 }
 
-char *error_string(int error)
-{
-    switch (error) {
-      case EACCES:
-       return "Network error: Permission denied";
-      case EADDRINUSE:
-       return "Network error: Address already in use";
-      case EADDRNOTAVAIL:
-       return "Network error: Cannot assign requested address";
-      case EAFNOSUPPORT:
-       return
-           "Network error: Address family not supported by protocol family";
-      case EALREADY:
-       return "Network error: Operation already in progress";
-      case ECONNABORTED:
-       return "Network error: Software caused connection abort";
-      case ECONNREFUSED:
-       return "Network error: Connection refused";
-      case ECONNRESET:
-       return "Network error: Connection reset by peer";
-      case EDESTADDRREQ:
-       return "Network error: Destination address required";
-      case EFAULT:
-       return "Network error: Bad address";
-      case EHOSTDOWN:
-       return "Network error: Host is down";
-      case EHOSTUNREACH:
-       return "Network error: No route to host";
-      case EINPROGRESS:
-       return "Network error: Operation now in progress";
-      case EINTR:
-       return "Network error: Interrupted function call";
-      case EINVAL:
-       return "Network error: Invalid argument";
-      case EISCONN:
-       return "Network error: Socket is already connected";
-      case EMFILE:
-       return "Network error: Too many open files";
-      case EMSGSIZE:
-       return "Network error: Message too long";
-      case ENETDOWN:
-       return "Network error: Network is down";
-      case ENETRESET:
-       return "Network error: Network dropped connection on reset";
-      case ENETUNREACH:
-       return "Network error: Network is unreachable";
-      case ENOBUFS:
-       return "Network error: No buffer space available";
-      case ENOPROTOOPT:
-       return "Network error: Bad protocol option";
-      case ENOTCONN:
-       return "Network error: Socket is not connected";
-      case ENOTSOCK:
-       return "Network error: Socket operation on non-socket";
-      case EOPNOTSUPP:
-       return "Network error: Operation not supported";
-      case EPFNOSUPPORT:
-       return "Network error: Protocol family not supported";
-      case EPROTONOSUPPORT:
-       return "Network error: Protocol not supported";
-      case EPROTOTYPE:
-       return "Network error: Protocol wrong type for socket";
-      case ESHUTDOWN:
-       return "Network error: Cannot send after socket shutdown";
-      case ESOCKTNOSUPPORT:
-       return "Network error: Socket type not supported";
-      case ETIMEDOUT:
-       return "Network error: Connection timed out";
-      case EWOULDBLOCK:
-       return "Network error: Resource temporarily unavailable";
-      default:
-       return "Unknown network error";
-    }
+const char *error_string(int error)
+{
+    return strerror(error);
 }
 
-SockAddr sk_namelookup(char *host, char **canonicalname)
+SockAddr sk_namelookup(const char *host, char **canonicalname, int address_family)
 {
-    SockAddr ret = smalloc(sizeof(struct SockAddr_tag));
+    SockAddr ret = snew(struct SockAddr_tag);
+#ifndef NO_IPV6
+    struct addrinfo hints;
+    int err;
+#else
     unsigned long a;
     struct hostent *h = NULL;
+#endif
     char realhost[8192];
 
     /* Clear the structure and default to IPv4. */
@@ -192,61 +143,49 @@ SockAddr sk_namelookup(char *host, char **canonicalname)
     *realhost = '\0';
     ret->error = NULL;
 
+#ifndef NO_IPV6
+    hints.ai_flags = AI_CANONNAME;
+    hints.ai_family = address_family;
+    hints.ai_socktype = 0;
+    hints.ai_protocol = 0;
+    hints.ai_addrlen = 0;
+    hints.ai_addr = NULL;
+    hints.ai_canonname = NULL;
+    hints.ai_next = NULL;
+    err = getaddrinfo(host, NULL, NULL, &ret->ai);
+    if (err != 0) {
+       ret->error = gai_strerror(err);
+       return ret;
+    }
+    ret->family = ret->ai->ai_family;
+    *realhost = '\0';
+    if (ret->ai->ai_canonname != NULL)
+       strncat(realhost, ret->ai->ai_canonname, sizeof(realhost) - 1);
+    else
+       strncat(realhost, host, sizeof(realhost) - 1);
+#else
     if ((a = inet_addr(host)) == (unsigned long) INADDR_NONE) {
-#ifdef IPV6
-       if (getaddrinfo(host, NULL, NULL, &ret->ai) == 0) {
-           ret->family = ret->ai->ai_family;
-       } else
-#endif
-       {
-           /*
-            * Otherwise use the IPv4-only gethostbyname... (NOTE:
-            * we don't use gethostbyname as a fallback!)
-            */
-           if (ret->family == 0) {
+       /*
+        * Otherwise use the IPv4-only gethostbyname... (NOTE:
+        * we don't use gethostbyname as a fallback!)
+        */
+       if (ret->family == 0) {
                /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
                if ( (h = gethostbyname(host)) )
-                   ret->family = AF_INET;
-           }
-           if (ret->family == 0) {
+                       ret->family = AF_INET;
+       }
+       if (ret->family == 0) {
                ret->error = (h_errno == HOST_NOT_FOUND ||
-                             h_errno == NO_DATA ||
-                             h_errno == NO_ADDRESS ? "Host does not exist" :
-                             h_errno == TRY_AGAIN ?
-                             "Temporary name service failure" :
-                             "gethostbyname: unknown error");
+                   h_errno == NO_DATA ||
+                   h_errno == NO_ADDRESS ? "Host does not exist" :
+                   h_errno == TRY_AGAIN ?
+                   "Temporary name service failure" :
+                   "gethostbyname: unknown error");
                return ret;
-           }
-       }
-
-#ifdef IPV6
-       /* If we got an address info use that... */
-       if (ret->ai) {
-
-           /* Are we in IPv4 fallback mode? */
-           /* We put the IPv4 address into the a variable so we can further-on use the IPv4 code... */
-           if (ret->family == AF_INET)
-               memcpy(&a,
-                      (char *) &((struct sockaddr_in *) ret->ai->
-                                 ai_addr)->sin_addr, sizeof(a));
-
-           /* Now let's find that canonicalname... */
-           if (getnameinfo((struct sockaddr *) ret->ai->ai_addr,
-                           ret->family ==
-                           AF_INET ? sizeof(struct sockaddr_in) :
-                           sizeof(struct sockaddr_in6), realhost,
-                           sizeof(realhost), NULL, 0, 0) != 0) {
-               strncpy(realhost, host, sizeof(realhost));
-           }
-       }
-       /* We used the IPv4-only gethostbyname()... */
-       else
-#endif
-       {
-           memcpy(&a, h->h_addr, sizeof(a));
-           /* This way we are always sure the h->h_name is valid :) */
-           strncpy(realhost, h->h_name, sizeof(realhost));
        }
+       memcpy(&a, h->h_addr, sizeof(a));
+       /* This way we are always sure the h->h_name is valid :) */
+       strncpy(realhost, h->h_name, sizeof(realhost));
     } else {
        /*
         * This must be a numeric IPv4 address because it caused a
@@ -256,49 +195,112 @@ SockAddr sk_namelookup(char *host, char **canonicalname)
        strncpy(realhost, host, sizeof(realhost));
     }
     ret->address = ntohl(a);
+#endif
     realhost[lenof(realhost)-1] = '\0';
-    *canonicalname = smalloc(1+strlen(realhost));
+    *canonicalname = snewn(1+strlen(realhost), char);
     strcpy(*canonicalname, realhost);
     return ret;
 }
 
+SockAddr sk_nonamelookup(const char *host)
+{
+    SockAddr ret = snew(struct SockAddr_tag);
+    ret->error = NULL;
+    ret->family = AF_UNSPEC;
+    strncpy(ret->hostname, host, lenof(ret->hostname));
+    ret->hostname[lenof(ret->hostname)-1] = '\0';
+    return ret;
+}
+
 void sk_getaddr(SockAddr addr, char *buf, int buflen)
 {
-#ifdef IPV6
-    if (addr->family == AF_INET) {
-#endif
+
+    if (addr->family == AF_UNSPEC) {
+       strncpy(buf, addr->hostname, buflen);
+       buf[buflen-1] = '\0';
+    } else {
+#ifndef NO_IPV6
+       if (getnameinfo(addr->ai->ai_addr, addr->ai->ai_addrlen, buf, buflen,
+                       NULL, 0, NI_NUMERICHOST) != 0) {
+           buf[0] = '\0';
+           strncat(buf, "<unknown>", buflen - 1);
+       }
+#else
        struct in_addr a;
+       assert(addr->family == AF_INET);
        a.s_addr = htonl(addr->address);
        strncpy(buf, inet_ntoa(a), buflen);
-#ifdef IPV6
-    } else {
-       FIXME; /* I don't know how to get a text form of an IPv6 address. */
-    }
+       buf[buflen-1] = '\0';
 #endif
+    }
 }
 
-int sk_addrtype(SockAddr addr)
+int sk_hostname_is_local(char *name)
 {
-    return (addr->family == AF_INET ? ADDRTYPE_IPV4 : ADDRTYPE_IPV6);
+    return !strcmp(name, "localhost");
 }
 
-void sk_addrcopy(SockAddr addr, char *buf)
+int sk_address_is_local(SockAddr addr)
 {
-#ifdef IPV6
-    if (addr->family == AF_INET) {
-#endif
+
+    if (addr->family == AF_UNSPEC)
+       return 0;                      /* we don't know; assume not */
+    else {
+#ifndef NO_IPV6
+       if (addr->family == AF_INET)
+           return ipv4_is_loopback(
+               ((struct sockaddr_in *)addr->ai->ai_addr)->sin_addr);
+       else if (addr->family == AF_INET6)
+           return IN6_IS_ADDR_LOOPBACK(
+               &((struct sockaddr_in6 *)addr->ai->ai_addr)->sin6_addr);
+       else
+           return 0;
+#else
        struct in_addr a;
+       assert(addr->family == AF_INET);
        a.s_addr = htonl(addr->address);
-       memcpy(buf, (char*) &a.s_addr, 4);
-#ifdef IPV6
-    } else {
-       memcpy(buf, (char*) addr->ai, 16);
+       return ipv4_is_loopback(a);
+#endif
     }
+}
+
+int sk_addrtype(SockAddr addr)
+{
+    return (addr->family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+           addr->family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+           ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+
+#ifndef NO_IPV6
+    if (addr->family == AF_INET)
+       memcpy(buf, &((struct sockaddr_in *)addr->ai->ai_addr)->sin_addr,
+              sizeof(struct in_addr));
+    else if (addr->family == AF_INET6)
+       memcpy(buf, &((struct sockaddr_in6 *)addr->ai->ai_addr)->sin6_addr,
+              sizeof(struct in6_addr));
+    else
+       assert(FALSE);
+#else
+    struct in_addr a;
+
+    assert(addr->family == AF_INET);
+    a.s_addr = htonl(addr->address);
+    memcpy(buf, (char*) &a.s_addr, 4);
 #endif
 }
 
 void sk_addr_free(SockAddr addr)
 {
+
+#ifndef NO_IPV6
+    if (addr->ai != NULL)
+       freeaddrinfo(addr->ai);
+#endif
     sfree(addr);
 }
 
@@ -320,34 +322,34 @@ static void sk_tcp_flush(Socket s)
 }
 
 static void sk_tcp_close(Socket s);
-static int sk_tcp_write(Socket s, char *data, int len);
-static int sk_tcp_write_oob(Socket s, char *data, int len);
+static int sk_tcp_write(Socket s, const char *data, int len);
+static int sk_tcp_write_oob(Socket s, const char *data, int len);
 static void sk_tcp_set_private_ptr(Socket s, void *ptr);
 static void *sk_tcp_get_private_ptr(Socket s);
 static void sk_tcp_set_frozen(Socket s, int is_frozen);
-static char *sk_tcp_socket_error(Socket s);
-
-Socket sk_register(void *sock, Plug plug)
-{
-    static struct socket_function_table fn_table = {
-       sk_tcp_plug,
-       sk_tcp_close,
-       sk_tcp_write,
-       sk_tcp_write_oob,
-       sk_tcp_flush,
-       sk_tcp_set_private_ptr,
-       sk_tcp_get_private_ptr,
-       sk_tcp_set_frozen,
-       sk_tcp_socket_error
-    };
+static const char *sk_tcp_socket_error(Socket s);
+
+static struct socket_function_table tcp_fn_table = {
+    sk_tcp_plug,
+    sk_tcp_close,
+    sk_tcp_write,
+    sk_tcp_write_oob,
+    sk_tcp_flush,
+    sk_tcp_set_private_ptr,
+    sk_tcp_get_private_ptr,
+    sk_tcp_set_frozen,
+    sk_tcp_socket_error
+};
 
+Socket sk_register(OSSocket sockfd, Plug plug)
+{
     Actual_Socket ret;
 
     /*
      * Create Socket structure.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
@@ -360,7 +362,7 @@ Socket sk_register(void *sock, Plug plug)
     ret->oobpending = FALSE;
     ret->listener = 0;
 
-    ret->s = (int)sock;
+    ret->s = sockfd;
 
     if (ret->s < 0) {
        ret->error = error_string(errno);
@@ -369,40 +371,32 @@ Socket sk_register(void *sock, Plug plug)
 
     ret->oobinline = 0;
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
 }
 
 Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
-             int nodelay, Plug plug)
-{
-    static struct socket_function_table fn_table = {
-       sk_tcp_plug,
-       sk_tcp_close,
-       sk_tcp_write,
-       sk_tcp_write_oob,
-       sk_tcp_flush,
-       sk_tcp_set_private_ptr,
-       sk_tcp_get_private_ptr,
-       sk_tcp_set_frozen,
-       sk_tcp_socket_error
-    };
-
+             int nodelay, int keepalive, Plug plug)
+{
     int s;
-#ifdef IPV6
+#ifndef NO_IPV6
     struct sockaddr_in6 a6;
 #endif
     struct sockaddr_in a;
+    struct sockaddr_un au;
+    const struct sockaddr *sa;
     int err;
     Actual_Socket ret;
     short localport;
+    int fl, salen;
 
     /*
      * Create Socket structure.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
@@ -419,6 +413,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     /*
      * Open socket.
      */
+    assert(addr->family != AF_UNSPEC);
     s = socket(addr->family, SOCK_STREAM, 0);
     ret->s = s;
 
@@ -438,6 +433,11 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
     }
 
+    if (keepalive) {
+       int b = TRUE;
+       setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+    }
+
     /*
      * Bind to local address.
      */
@@ -446,87 +446,104 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     else
        localport = 0;                 /* just use port 0 (ie kernel picks) */
 
-    /* Loop round trying to bind */
-    while (1) {
-       int retcode;
+    /* BSD IP stacks need sockaddr_in zeroed before filling in */
+    memset(&a,'\0',sizeof(struct sockaddr_in));
+#ifndef NO_IPV6
+    memset(&a6,'\0',sizeof(struct sockaddr_in6));
+#endif
 
-#ifdef IPV6
-       if (addr->family == AF_INET6) {
-           memset(&a6, 0, sizeof(a6));
-           a6.sin6_family = AF_INET6;
-/*a6.sin6_addr      = in6addr_any; *//* == 0 */
-           a6.sin6_port = htons(localport);
-       } else
+    /* We don't try to bind to a local address for UNIX domain sockets.  (Why
+     * do we bother doing the bind when localport == 0 anyway?) */
+    if(addr->family != AF_UNIX) {
+       /* Loop round trying to bind */
+       while (1) {
+           int retcode;
+
+#ifndef NO_IPV6
+           if (addr->family == AF_INET6) {
+               /* XXX use getaddrinfo to get a local address? */
+               a6.sin6_family = AF_INET6;
+               a6.sin6_addr = in6addr_any;
+               a6.sin6_port = htons(localport);
+               retcode = bind(s, (struct sockaddr *) &a6, sizeof(a6));
+           } else
 #endif
-       {
-           a.sin_family = AF_INET;
-           a.sin_addr.s_addr = htonl(INADDR_ANY);
-           a.sin_port = htons(localport);
+           {
+               assert(addr->family == AF_INET);
+               a.sin_family = AF_INET;
+               a.sin_addr.s_addr = htonl(INADDR_ANY);
+               a.sin_port = htons(localport);
+               retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
+           }
+           if (retcode >= 0) {
+               err = 0;
+               break;                 /* done */
+           } else {
+               err = errno;
+               if (err != EADDRINUSE) /* failed, for a bad reason */
+                 break;
+           }
+           
+           if (localport == 0)
+             break;                   /* we're only looping once */
+           localport--;
+           if (localport == 0)
+             break;                   /* we might have got to the end */
        }
-#ifdef IPV6
-       retcode = bind(s, (addr->family == AF_INET6 ?
-                          (struct sockaddr *) &a6 :
-                          (struct sockaddr *) &a),
-                      (addr->family ==
-                       AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
-       retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
-       if (retcode >= 0) {
-           err = 0;
-           break;                     /* done */
-       } else {
-           err = errno;
-           if (err != EADDRINUSE)     /* failed, for a bad reason */
-               break;
+       
+       if (err) {
+           ret->error = error_string(err);
+           return (Socket) ret;
        }
-
-       if (localport == 0)
-           break;                     /* we're only looping once */
-       localport--;
-       if (localport == 0)
-           break;                     /* we might have got to the end */
-    }
-
-    if (err) {
-       ret->error = error_string(err);
-       return (Socket) ret;
     }
 
     /*
      * Connect to remote address.
      */
-#ifdef IPV6
-    if (addr->family == AF_INET6) {
-       memset(&a, 0, sizeof(a));
-       a6.sin6_family = AF_INET6;
-       a6.sin6_port = htons((short) port);
-       a6.sin6_addr =
-           ((struct sockaddr_in6 *) addr->ai->ai_addr)->sin6_addr;
-    } else
-#endif
-    {
+    switch(addr->family) {
+#ifndef NO_IPV6
+      case AF_INET:
+       /* XXX would be better to have got getaddrinfo() to fill in the port. */
+       ((struct sockaddr_in *)addr->ai->ai_addr)->sin_port =
+           htons(port);
+       sa = (const struct sockaddr *)addr->ai->ai_addr;
+       salen = addr->ai->ai_addrlen;
+       break;
+      case AF_INET6:
+       ((struct sockaddr_in *)addr->ai->ai_addr)->sin_port =
+           htons(port);
+       sa = (const struct sockaddr *)addr->ai->ai_addr;
+       salen = addr->ai->ai_addrlen;
+       break;
+#else
+      case AF_INET:
        a.sin_family = AF_INET;
        a.sin_addr.s_addr = htonl(addr->address);
        a.sin_port = htons((short) port);
+       sa = (const struct sockaddr *)&a;
+       salen = sizeof a;
+       break;
+#endif
+      case AF_UNIX:
+       assert(port == 0);      /* to catch confused people */
+       assert(strlen(addr->hostname) < sizeof au.sun_path);
+       memset(&au, 0, sizeof au);
+       au.sun_family = AF_UNIX;
+       strcpy(au.sun_path, addr->hostname);
+       sa = (const struct sockaddr *)&au;
+       salen = sizeof au;
+       break;
+
+      default:
+       assert(0 && "unknown address family");
     }
 
-    if ((
-#ifdef IPV6
-           connect(s, ((addr->family == AF_INET6) ?
-                       (struct sockaddr *) &a6 : (struct sockaddr *) &a),
-                   (addr->family == AF_INET6) ? sizeof(a6) : sizeof(a))
-#else
-           connect(s, (struct sockaddr *) &a, sizeof(a))
-#endif
-       ) < 0) {
-       /*
-        * FIXME: We are prepared to receive EWOULDBLOCK here,
-        * because we might want the connection to be made
-        * asynchronously; but how do we actually arrange this in
-        * Unix? I forget.
-        */
-       if ( errno != EWOULDBLOCK ) {
+    fl = fcntl(s, F_GETFL);
+    if (fl != -1)
+       fcntl(s, F_SETFL, fl | O_NONBLOCK);
+
+    if ((connect(s, sa, salen)) < 0) {
+       if ( errno != EINPROGRESS ) {
            ret->error = error_string(errno);
            return (Socket) ret;
        }
@@ -539,28 +556,20 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        ret->writable = 1;
     }
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
+    sk_addr_free(addr);
+
     return (Socket) ret;
 }
 
-Socket sk_newlistener(int port, Plug plug, int local_host_only)
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family)
 {
-    static struct socket_function_table fn_table = {
-       sk_tcp_plug,
-       sk_tcp_close,
-       sk_tcp_write,
-       sk_tcp_write_oob,
-       sk_tcp_flush,
-       sk_tcp_set_private_ptr,
-       sk_tcp_get_private_ptr,
-       sk_tcp_set_frozen,
-       sk_tcp_socket_error
-    };
-
     int s;
-#ifdef IPV6
-    struct sockaddr_in6 a6;
+#ifndef NO_IPV6
+    struct addrinfo hints, *ai;
+    char portstr[6];
 #endif
     struct sockaddr_in a;
     int err;
@@ -571,8 +580,8 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
     /*
      * Create Socket structure.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
@@ -586,9 +595,31 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
     ret->listener = 1;
 
     /*
+     * Translate address_family from platform-independent constants
+     * into local reality.
+     */
+    address_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+                     address_family == ADDRTYPE_IPV6 ? AF_INET6 : AF_UNSPEC);
+
+#ifndef NO_IPV6
+    /* Let's default to IPv6.
+     * If the stack doesn't support IPv6, we will fall back to IPv4. */
+    if (address_family == AF_UNSPEC) address_family = AF_INET6;
+#else
+    /* No other choice, default to IPv4 */
+    if (address_family == AF_UNSPEC)  address_family = AF_INET;
+#endif
+
+    /*
      * Open socket.
      */
-    s = socket(AF_INET, SOCK_STREAM, 0);
+    s = socket(address_family, SOCK_STREAM, 0);
+
+    /* If the host doesn't support IPv6 try fallback to IPv4. */
+    if (s < 0 && address_family == AF_INET6) {
+       address_family = AF_INET;
+       s = socket(address_family, SOCK_STREAM, 0);
+    }
     ret->s = s;
 
     if (s < 0) {
@@ -600,10 +631,30 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
 
     setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
 
-#ifdef IPV6
-    if (addr->family == AF_INET6) {
-       memset(&a6, 0, sizeof(a6));
-       a6.sin6_family = AF_INET6;
+    /* BSD IP stacks need sockaddr_in zeroed before filling in */
+    memset(&a,'\0',sizeof(struct sockaddr_in));
+#ifndef NO_IPV6
+#if 0
+    memset(&a6,'\0',sizeof(struct sockaddr_in6));
+#endif
+    hints.ai_flags = AI_NUMERICHOST;
+    hints.ai_family = address_family;
+    hints.ai_socktype = 0;
+    hints.ai_protocol = 0;
+    hints.ai_addrlen = 0;
+    hints.ai_addr = NULL;
+    hints.ai_canonname = NULL;
+    hints.ai_next = NULL;
+    sprintf(portstr, "%d", port);
+    if (srcaddr != NULL && getaddrinfo(srcaddr, portstr, &hints, &ai) == 0)
+       retcode = bind(s, ai->ai_addr, ai->ai_addrlen);
+    else
+#if 0
+    {
+       /*
+        * FIXME: Need two listening sockets, in principle, one for v4
+        * and one for v6
+        */
        if (local_host_only)
            a6.sin6_addr = in6addr_loopback;
        else
@@ -611,23 +662,38 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
        a6.sin6_port = htons(port);
     } else
 #endif
+#endif
     {
+       int got_addr = 0;
        a.sin_family = AF_INET;
-       if (local_host_only)
-           a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-       else
-           a.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       /*
+        * Bind to source address. First try an explicitly
+        * specified one...
+        */
+       if (srcaddr) {
+           a.sin_addr.s_addr = inet_addr(srcaddr);
+           if (a.sin_addr.s_addr != INADDR_NONE) {
+               /* Override localhost_only with specified listen addr. */
+               ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+               got_addr = 1;
+           }
+       }
+
+       /*
+        * ... and failing that, go with one of the standard ones.
+        */
+       if (!got_addr) {
+           if (local_host_only)
+               a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+           else
+               a.sin_addr.s_addr = htonl(INADDR_ANY);
+       }
+
        a.sin_port = htons((short)port);
+       retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
     }
-#ifdef IPV6
-    retcode = bind(s, (addr->family == AF_INET6 ?
-                      (struct sockaddr *) &a6 :
-                      (struct sockaddr *) &a),
-                  (addr->family ==
-                   AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
-    retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
-#endif
+
     if (retcode >= 0) {
        err = 0;
     } else {
@@ -646,6 +712,7 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
        return (Socket) ret;
     }
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
@@ -655,11 +722,50 @@ static void sk_tcp_close(Socket sock)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    uxsel_del(s->s);
     del234(sktree, s);
     close(s->s);
     sfree(s);
 }
 
+int sk_getxdmdata(void *sock, unsigned long *ip, int *port)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    struct sockaddr_in addr;
+    socklen_t addrlen;
+
+    /*
+     * We must check that this socket really _is_ an Actual_Socket.
+     */
+    if (s->fn != &tcp_fn_table)
+       return 0;                      /* failure */
+
+    addrlen = sizeof(addr);
+    if (getsockname(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
+       return 0;
+    switch(addr.sin_family) {
+      case AF_INET:
+       *ip = ntohl(addr.sin_addr.s_addr);
+       *port = ntohs(addr.sin_port);
+       break;
+      case AF_UNIX:
+       /*
+        * For a Unix socket, we return 0xFFFFFFFF for the IP address and
+        * our current pid for the port. Bizarre, but such is life.
+        */
+       *ip = ntohl(0xFFFFFFFF);
+       *port = getpid();
+       break;
+
+       /* XXX IPV6 */
+
+      default:
+       return 0;
+    }
+
+    return 1;
+}
+
 /*
  * The function which tries to send on a socket once it's deemed
  * writable.
@@ -723,9 +829,10 @@ void try_send(Actual_Socket s)
            }
        }
     }
+    uxsel_tell(s);
 }
 
-static int sk_tcp_write(Socket sock, char *buf, int len)
+static int sk_tcp_write(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
@@ -740,10 +847,16 @@ static int sk_tcp_write(Socket sock, char *buf, int len)
     if (s->writable)
        try_send(s);
 
+    /*
+     * Update the select() status to correctly reflect whether or
+     * not we should be selecting for write.
+     */
+    uxsel_tell(s);
+
     return bufchain_size(&s->output_data);
 }
 
-static int sk_tcp_write_oob(Socket sock, char *buf, int len)
+static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
@@ -761,10 +874,16 @@ static int sk_tcp_write_oob(Socket sock, char *buf, int len)
     if (s->writable)
        try_send(s);
 
+    /*
+     * Update the select() status to correctly reflect whether or
+     * not we should be selecting for write.
+     */
+    uxsel_tell(s);
+
     return s->sending_oob;
 }
 
-int select_result(int fd, int event)
+static int net_select_result(int fd, int event)
 {
     int ret;
     int err;
@@ -773,18 +892,13 @@ int select_result(int fd, int event)
     u_long atmark;
 
     /* Find the Socket structure */
-    s = find234(sktree, (void *) fd, cmpforsearch);
+    s = find234(sktree, &fd, cmpforsearch);
     if (!s)
        return 1;                      /* boggle */
 
     noise_ultralight(event);
 
     switch (event) {
-#ifdef FIXME_NONBLOCKING_CONNECTIONS
-      case FIXME:                     /* connected */
-       s->connected = s->writable = 1;
-       break;
-#endif
       case 4:                         /* exceptional */
        if (!s->oobinline) {
            /*
@@ -796,8 +910,8 @@ int select_result(int fd, int event)
            ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
            noise_ultralight(ret);
            if (ret <= 0) {
-               char *str = (ret == 0 ? "Internal networking trouble" :
-                            error_string(errno));
+               const char *str = (ret == 0 ? "Internal networking trouble" :
+                                  error_string(errno));
                /* We're inside the Unix frontend here, so we know
                 * that the frontend handle is unnecessary. */
                logevent(NULL, str);
@@ -810,10 +924,12 @@ int select_result(int fd, int event)
 
        /*
         * If we reach here, this is an oobinline socket, which
-        * means we should set s->oobpending and then fall through
-        * to the read case.
+        * means we should set s->oobpending and then deal with it
+        * when we get called for the readability event (which
+        * should also occur).
         */
        s->oobpending = TRUE;
+        break;
       case 1:                         /* readable; also acceptance */
        if (s->listener) {
            /*
@@ -826,15 +942,14 @@ int select_result(int fd, int event)
 
            memset(&isa, 0, sizeof(struct sockaddr_in));
            err = 0;
-           t = accept(s->s,(struct sockaddr *)&isa,&addrlen);
+           t = accept(s->s,(struct sockaddr *)&isa,(socklen_t *) &addrlen);
            if (t < 0) {
                break;
            }
 
-           if (s->localhost_only &&
-               ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) {
+           if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) {
                close(t);              /* someone let nonlocal through?! */
-           } else if (plug_accepting(s->plug, (void*)t)) {
+           } else if (plug_accepting(s->plug, t)) {
                close(t);              /* denied or error */
            }
            break;
@@ -864,7 +979,7 @@ int select_result(int fd, int event)
        } else
            atmark = 1;
 
-       ret = recv(s->s, buf, sizeof(buf), 0);
+       ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
        noise_ultralight(ret);
        if (ret < 0) {
            if (errno == EWOULDBLOCK) {
@@ -880,7 +995,15 @@ int select_result(int fd, int event)
        }
        break;
       case 2:                         /* writable */
-       {
+       if (!s->connected) {
+           /*
+            * select() reports a socket as _writable_ when an
+            * asynchronous connection is completed.
+            */
+           s->connected = s->writable = 1;
+           uxsel_tell(s);
+           break;
+       } else {
            int bufsize_before, bufsize_after;
            s->writable = 1;
            bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
@@ -953,11 +1076,11 @@ static void *sk_tcp_get_private_ptr(Socket sock)
  * if there's a problem. These functions extract an error message,
  * or return NULL if there's no problem.
  */
-char *sk_addr_error(SockAddr addr)
+const char *sk_addr_error(SockAddr addr)
 {
     return addr->error;
 }
-static char *sk_tcp_socket_error(Socket sock)
+static const char *sk_tcp_socket_error(Socket sock)
 {
     Actual_Socket s = (Actual_Socket) sock;
     return s->error;
@@ -974,42 +1097,21 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
        recv(s->s, &c, 1, MSG_PEEK);
     }
     s->frozen_readable = 0;
+    uxsel_tell(s);
 }
 
-/*
- * For Unix select()-based frontends: enumerate all sockets
- * currently active, and state whether we currently wish to receive
- * select events on them for reading, writing and exceptional
- * status.
- */
-static void set_rwx(Actual_Socket s, int *rwx)
+static void uxsel_tell(Actual_Socket s)
 {
-    int val = 0;
+    int rwx = 0;
+    if (!s->connected)
+       rwx |= 2;                      /* write == connect */
     if (s->connected && !s->frozen)
-       val |= 1 | 4;                  /* read, except */
+       rwx |= 1 | 4;                  /* read, except */
     if (bufchain_size(&s->output_data))
-       val |= 2;                      /* write */
+       rwx |= 2;                      /* write */
     if (s->listener)
-       val |= 1;                      /* read == accept */
-    *rwx = val;
-}
-
-int first_socket(int *state, int *rwx)
-{
-    Actual_Socket s;
-    *state = 0;
-    s = index234(sktree, (*state)++);
-    if (s)
-       set_rwx(s, rwx);
-    return s ? s->s : -1;
-}
-
-int next_socket(int *state, int *rwx)
-{
-    Actual_Socket s = index234(sktree, (*state)++);
-    if (s)
-       set_rwx(s, rwx);
-    return s ? s->s : -1;
+       rwx |= 1;                      /* read == accept */
+    uxsel_set(s->s, rwx, net_select_result);
 }
 
 int net_service_lookup(char *service)
@@ -1021,3 +1123,21 @@ int net_service_lookup(char *service)
     else
        return 0;
 }
+
+SockAddr platform_get_x11_unix_address(int displaynum, char **canonicalname)
+{
+    SockAddr ret = snew(struct SockAddr_tag);
+    int n;
+
+    memset(ret, 0, sizeof *ret);
+    ret->family = AF_UNIX;
+    n = snprintf(ret->hostname, sizeof ret->hostname,
+                "%s%d", X11_UNIX_PATH, displaynum);
+    if(n < 0)
+       ret->error = "snprintf failed";
+    else if(n >= sizeof ret->hostname)
+       ret->error = "X11 UNIX name too long";
+    else
+       *canonicalname = dupstr(ret->hostname);
+    return ret;
+}