Completely remove the 'frozen_readable' mechanism from uxnet.c. It
[u/mdw/putty] / unix / uxnet.c
index 44b09c1..5190ef8 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"
 
+/* Solaris needs <sys/sockio.h> for SIOCATMARK. */
+#ifndef SIOCATMARK
+#include <sys/sockio.h>
+#endif
+
+#ifndef X11_UNIX_PATH
+# define X11_UNIX_PATH "/tmp/.X11-unix/X"
+#endif
+
+/* 
+ * Access to sockaddr types without breaking C strict aliasing rules.
+ */
+union sockaddr_union {
+#ifdef NO_IPV6
+    struct sockaddr_in storage;
+#else
+    struct sockaddr_storage storage;
+    struct sockaddr_in6 sin6;
+#endif
+    struct sockaddr sa;
+    struct sockaddr_in sin;
+    struct sockaddr_un su;
+};
+
+/*
+ * We used to typedef struct Socket_tag *Socket.
+ *
+ * Since we have made the networking abstraction slightly more
+ * abstract, Socket no longer means a tcp socket (it could mean
+ * an ssl socket).  So now we must use Actual_Socket when we know
+ * we are talking about a tcp socket.
+ */
+typedef struct Socket_tag *Actual_Socket;
+
+/*
+ * Mutable state that goes with a SockAddr: stores information
+ * about where in the list of candidate IP(v*) addresses we've
+ * currently got to.
+ */
+typedef struct SockAddrStep_tag SockAddrStep;
+struct SockAddrStep_tag {
+#ifndef NO_IPV6
+    struct addrinfo *ai;              /* steps along addr->ais */
+#endif
+    int curraddr;
+};
+
 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;
     bufchain output_data;
-    int connected;
+    int connected;                    /* irrelevant for listening sockets */
     int writable;
     int frozen; /* this causes readability notifications to be ignored */
-    int frozen_readable; /* this means we missed at least one readability
-                         * notification while we were frozen */
     int localhost_only;                       /* for listening sockets */
     char oobdata[1];
     int sending_oob;
     int oobpending;                   /* is there OOB data available to read? */
     int oobinline;
+    enum { EOF_NO, EOF_PENDING, EOF_SENT } outgoingeof;
+    int incomingeof;
     int pending_error;                /* in case send() returns error */
     int listener;
+    int nodelay, keepalive;            /* for connect()-type sockets */
+    int privport, port;                /* and again */
+    SockAddr addr;
+    SockAddrStep step;
+    /*
+     * We sometimes need pairs of Socket structures to be linked:
+     * if we are listening on the same IPv6 and v4 port, for
+     * example. So here we define `parent' and `child' pointers to
+     * track this link.
+     */
+    Actual_Socket parent, child;
+};
+
+struct SockAddr_tag {
+    int refcount;
+    const char *error;
+    enum { UNRESOLVED, UNIX, IP } superfamily;
+#ifndef NO_IPV6
+    struct addrinfo *ais;             /* Addresses IPv6 style. */
+#else
+    unsigned long *addresses;         /* Addresses IPv4 style. */
+    int naddresses;
+#endif
+    char hostname[512];                       /* Store an unresolved host name. */
 };
 
 /*
- * We used to typedef struct Socket_tag *Socket.
- *
- * Since we have made the networking abstraction slightly more
- * abstract, Socket no longer means a tcp socket (it could mean
- * an ssl socket).  So now we must use Actual_Socket when we know
- * we are talking about a tcp socket.
+ * 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.
  */
-typedef struct Socket_tag *Actual_Socket;
+#ifndef NO_IPV6
+#define SOCKADDR_FAMILY(addr, step) \
+    ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+     (addr)->superfamily == UNIX ? AF_UNIX : \
+     (step).ai ? (step).ai->ai_family : AF_INET)
+#else
+#define SOCKADDR_FAMILY(addr, step) \
+    ((addr)->superfamily == UNRESOLVED ? AF_UNSPEC : \
+     (addr)->superfamily == UNIX ? AF_UNIX : AF_INET)
+#endif
 
-struct SockAddr_tag {
-    char *error;
-    /* address family this belongs to, AF_INET for IPv4, AF_INET6 for IPv6. */
-    int family;
-    unsigned long address;            /* Address IPv4 style. */
-#ifdef IPV6
-    struct addrinfo *ai;              /* Address IPv6 style. */
+/*
+ * Start a SockAddrStep structure to step through multiple
+ * addresses.
+ */
+#ifndef NO_IPV6
+#define START_STEP(addr, step) \
+    ((step).ai = (addr)->ais, (step).curraddr = 0)
+#else
+#define START_STEP(addr, step) \
+    ((step).curraddr = 0)
 #endif
-};
 
 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;
@@ -73,13 +156,17 @@ static int cmpfortree(void *av, void *bv)
        return -1;
     if (as > bs)
        return +1;
+    if (a < b)
+       return -1;
+    if (a > b)
+       return +1;
     return 0;
 }
 
 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,204 +191,266 @@ 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";
-    }
-}
-
-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;
+    int n;
+#endif
     char realhost[8192];
 
     /* Clear the structure and default to IPv4. */
     memset(ret, 0, sizeof(struct SockAddr_tag));
-    ret->family = 0;                  /* We set this one when we have resolved the host. */
+    ret->superfamily = UNRESOLVED;
     *realhost = '\0';
     ret->error = NULL;
-
-    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) {
-               /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
-               if ( (h = gethostbyname(host)) )
-                   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");
-               return ret;
-           }
+    ret->refcount = 1;
+
+#ifndef NO_IPV6
+    hints.ai_flags = AI_CANONNAME;
+    hints.ai_family = (address_family == ADDRTYPE_IPV4 ? AF_INET :
+                      address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+                      AF_UNSPEC);
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_protocol = 0;
+    hints.ai_addrlen = 0;
+    hints.ai_addr = NULL;
+    hints.ai_canonname = NULL;
+    hints.ai_next = NULL;
+    err = getaddrinfo(host, NULL, &hints, &ret->ais);
+    if (err != 0) {
+       ret->error = gai_strerror(err);
+       return ret;
+    }
+    ret->superfamily = IP;
+    *realhost = '\0';
+    if (ret->ais->ai_canonname != NULL)
+       strncat(realhost, ret->ais->ai_canonname, sizeof(realhost) - 1);
+    else
+       strncat(realhost, host, sizeof(realhost) - 1);
+#else
+    if ((a = inet_addr(host)) == (unsigned long)(in_addr_t)(-1)) {
+       /*
+        * Otherwise use the IPv4-only gethostbyname... (NOTE:
+        * we don't use gethostbyname as a fallback!)
+        */
+       if (ret->superfamily == UNRESOLVED) {
+           /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
+           if ( (h = gethostbyname(host)) )
+               ret->superfamily = IP;
        }
-
-#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));
-           }
+       if (ret->superfamily == UNRESOLVED) {
+           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");
+           return ret;
        }
-       /* 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));
+       /* This way we are always sure the h->h_name is valid :) */
+       strncpy(realhost, h->h_name, sizeof(realhost));
+       for (n = 0; h->h_addr_list[n]; n++);
+       ret->addresses = snewn(n, unsigned long);
+       ret->naddresses = n;
+       for (n = 0; n < ret->naddresses; n++) {
+           memcpy(&a, h->h_addr_list[n], sizeof(a));
+           ret->addresses[n] = ntohl(a);
        }
     } else {
        /*
         * This must be a numeric IPv4 address because it caused a
         * success return from inet_addr.
         */
-       ret->family = AF_INET;
+       ret->superfamily = IP;
        strncpy(realhost, host, sizeof(realhost));
+       ret->addresses = snew(unsigned long);
+       ret->naddresses = 1;
+       ret->addresses[0] = ntohl(a);
     }
-    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;
 }
 
-void sk_getaddr(SockAddr addr, char *buf, int buflen)
+SockAddr sk_nonamelookup(const char *host)
 {
-#ifdef IPV6
-    if (addr->family == AF_INET) {
+    SockAddr ret = snew(struct SockAddr_tag);
+    ret->error = NULL;
+    ret->superfamily = UNRESOLVED;
+    strncpy(ret->hostname, host, lenof(ret->hostname));
+    ret->hostname[lenof(ret->hostname)-1] = '\0';
+#ifndef NO_IPV6
+    ret->ais = NULL;
+#else
+    ret->addresses = NULL;
 #endif
-       struct in_addr a;
-       a.s_addr = htonl(addr->address);
-       strncpy(buf, inet_ntoa(a), buflen);
-#ifdef IPV6
+    ret->refcount = 1;
+    return ret;
+}
+
+static int sk_nextaddr(SockAddr addr, SockAddrStep *step)
+{
+#ifndef NO_IPV6
+    if (step->ai && step->ai->ai_next) {
+       step->ai = step->ai->ai_next;
+       return TRUE;
+    } else
+       return FALSE;
+#else
+    if (step->curraddr+1 < addr->naddresses) {
+       step->curraddr++;
+       return TRUE;
     } else {
-       FIXME; /* I don't know how to get a text form of an IPv6 address. */
+       return FALSE;
     }
+#endif    
+}
+
+void sk_getaddr(SockAddr addr, char *buf, int buflen)
+{
+    /* XXX not clear what we should return for Unix-domain sockets; let's
+     * hope the question never arises */
+    assert(addr->superfamily != UNIX);
+    if (addr->superfamily == UNRESOLVED) {
+       strncpy(buf, addr->hostname, buflen);
+       buf[buflen-1] = '\0';
+    } else {
+#ifndef NO_IPV6
+       if (getnameinfo(addr->ais->ai_addr, addr->ais->ai_addrlen, buf, buflen,
+                       NULL, 0, NI_NUMERICHOST) != 0) {
+           buf[0] = '\0';
+           strncat(buf, "<unknown>", buflen - 1);
+       }
+#else
+       struct in_addr a;
+       SockAddrStep step;
+       START_STEP(addr, step);
+       assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+       a.s_addr = htonl(addr->addresses[0]);
+       strncpy(buf, inet_ntoa(a), buflen);
+       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") ||
+          !strcmp(name, "::1") ||
+          !strncmp(name, "127.", 4);
 }
 
-void sk_addrcopy(SockAddr addr, char *buf)
+#define ipv4_is_loopback(addr) \
+    (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
+
+static int sockaddr_is_loopback(struct sockaddr *sa)
 {
-#ifdef IPV6
-    if (addr->family == AF_INET) {
+    union sockaddr_union *u = (union sockaddr_union *)sa;
+    switch (u->sa.sa_family) {
+      case AF_INET:
+       return ipv4_is_loopback(u->sin.sin_addr);
+#ifndef NO_IPV6
+      case AF_INET6:
+       return IN6_IS_ADDR_LOOPBACK(&u->sin6.sin6_addr);
 #endif
+      case AF_UNIX:
+       return TRUE;
+      default:
+       return FALSE;
+    }
+}
+
+int sk_address_is_local(SockAddr addr)
+{
+    if (addr->superfamily == UNRESOLVED)
+       return 0;                      /* we don't know; assume not */
+    else if (addr->superfamily == UNIX)
+       return 1;
+    else {
+#ifndef NO_IPV6
+       return sockaddr_is_loopback(addr->ais->ai_addr);
+#else
        struct in_addr a;
-       a.s_addr = htonl(addr->address);
-       memcpy(buf, (char*) &a.s_addr, 4);
-#ifdef IPV6
-    } else {
-       memcpy(buf, (char*) addr->ai, 16);
+       SockAddrStep step;
+       START_STEP(addr, step);
+       assert(SOCKADDR_FAMILY(addr, step) == AF_INET);
+       a.s_addr = htonl(addr->addresses[0]);
+       return ipv4_is_loopback(a);
+#endif
     }
+}
+
+int sk_address_is_special_local(SockAddr addr)
+{
+    return addr->superfamily == UNIX;
+}
+
+int sk_addrtype(SockAddr addr)
+{
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
+    return (family == AF_INET ? ADDRTYPE_IPV4 :
+#ifndef NO_IPV6
+           family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+           ADDRTYPE_NAME);
+}
+
+void sk_addrcopy(SockAddr addr, char *buf)
+{
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
+#ifndef NO_IPV6
+    if (family == AF_INET)
+       memcpy(buf, &((struct sockaddr_in *)step.ai->ai_addr)->sin_addr,
+              sizeof(struct in_addr));
+    else if (family == AF_INET6)
+       memcpy(buf, &((struct sockaddr_in6 *)step.ai->ai_addr)->sin6_addr,
+              sizeof(struct in6_addr));
+    else
+       assert(FALSE);
+#else
+    struct in_addr a;
+
+    assert(family == AF_INET);
+    a.s_addr = htonl(addr->addresses[step.curraddr]);
+    memcpy(buf, (char*) &a.s_addr, 4);
 #endif
 }
 
 void sk_addr_free(SockAddr addr)
 {
+    if (--addr->refcount > 0)
+       return;
+#ifndef NO_IPV6
+    if (addr->ais != NULL)
+       freeaddrinfo(addr->ais);
+#else
+    sfree(addr->addresses);
+#endif
     sfree(addr);
 }
 
+SockAddr sk_addr_dup(SockAddr addr)
+{
+    addr->refcount++;
+    return addr;
+}
+
 static Plug sk_tcp_plug(Socket sock, Plug p)
 {
     Actual_Socket s = (Actual_Socket) sock;
@@ -320,332 +469,490 @@ 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_write_eof(Socket s);
 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_write_eof,
+    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);
     ret->writable = 1;                /* to start with */
     ret->sending_oob = 0;
     ret->frozen = 1;
-    ret->frozen_readable = 0;
     ret->localhost_only = 0;          /* unused, but best init anyway */
     ret->pending_error = 0;
     ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
     ret->listener = 0;
+    ret->parent = ret->child = NULL;
+    ret->addr = NULL;
+    ret->connected = 1;
 
-    ret->s = (int)sock;
+    ret->s = sockfd;
 
     if (ret->s < 0) {
-       ret->error = error_string(errno);
+       ret->error = strerror(errno);
        return (Socket) ret;
     }
 
     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
-    };
-
+static int try_connect(Actual_Socket sock)
+{
     int s;
-#ifdef IPV6
-    struct sockaddr_in6 a6;
-#endif
-    struct sockaddr_in a;
-    int err;
-    Actual_Socket ret;
+    union sockaddr_union u;
+    const union sockaddr_union *sa;
+    int err = 0;
     short localport;
+    int salen, family;
 
     /*
-     * Create Socket structure.
+     * Remove the socket from the tree before we overwrite its
+     * internal socket id, because that forms part of the tree's
+     * sorting criterion. We'll add it back before exiting this
+     * function, whether we changed anything or not.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
-    ret->error = NULL;
-    ret->plug = plug;
-    bufchain_init(&ret->output_data);
-    ret->connected = 0;                       /* to start with */
-    ret->writable = 0;                /* to start with */
-    ret->sending_oob = 0;
-    ret->frozen = 0;
-    ret->frozen_readable = 0;
-    ret->localhost_only = 0;          /* unused, but best init anyway */
-    ret->pending_error = 0;
-    ret->oobpending = FALSE;
-    ret->listener = 0;
+    del234(sktree, sock);
+
+    if (sock->s >= 0)
+        close(sock->s);
+
+    plug_log(sock->plug, 0, sock->addr, sock->port, NULL, 0);
 
     /*
      * Open socket.
      */
-    s = socket(addr->family, SOCK_STREAM, 0);
-    ret->s = s;
+    family = SOCKADDR_FAMILY(sock->addr, sock->step);
+    assert(family != AF_UNSPEC);
+    s = socket(family, SOCK_STREAM, 0);
+    sock->s = s;
 
     if (s < 0) {
-       ret->error = error_string(errno);
-       return (Socket) ret;
+       err = errno;
+       goto ret;
     }
 
-    ret->oobinline = oobinline;
-    if (oobinline) {
+    cloexec(s);
+
+    if (sock->oobinline) {
+       int b = TRUE;
+       if (setsockopt(s, SOL_SOCKET, SO_OOBINLINE,
+                       (void *) &b, sizeof(b)) < 0) {
+            err = errno;
+            close(s);
+            goto ret;
+        }
+    }
+
+    if (sock->nodelay) {
        int b = TRUE;
-       setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+       if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+                       (void *) &b, sizeof(b)) < 0) {
+            err = errno;
+            close(s);
+            goto ret;
+        }
     }
 
-    if (nodelay) {
+    if (sock->keepalive) {
        int b = TRUE;
-       setsockopt(s, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+       if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+                       (void *) &b, sizeof(b)) < 0) {
+            err = errno;
+            close(s);
+            goto ret;
+        }
     }
 
     /*
      * Bind to local address.
      */
-    if (privport)
+    if (sock->privport)
        localport = 1023;              /* count from 1023 downwards */
     else
        localport = 0;                 /* just use port 0 (ie kernel picks) */
 
-    /* Loop round trying to bind */
-    while (1) {
-       int retcode;
-
-#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
-#endif
-       {
-           a.sin_family = AF_INET;
-           a.sin_addr.s_addr = htonl(INADDR_ANY);
-           a.sin_port = htons(localport);
-       }
-#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));
+    /* BSD IP stacks need sockaddr_in zeroed before filling in */
+    memset(&u,'\0',sizeof(u));
+
+    /* 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 (family != AF_UNIX) {
+       /* Loop round trying to bind */
+       while (1) {
+           int retcode;
+
+#ifndef NO_IPV6
+           if (family == AF_INET6) {
+               /* XXX use getaddrinfo to get a local address? */
+               u.sin6.sin6_family = AF_INET6;
+               u.sin6.sin6_addr = in6addr_any;
+               u.sin6.sin6_port = htons(localport);
+               retcode = bind(s, &u.sa, sizeof(u.sin6));
+           } else
 #endif
-       if (retcode >= 0) {
-           err = 0;
-           break;                     /* done */
-       } else {
-           err = errno;
-           if (err != EADDRINUSE)     /* failed, for a bad reason */
-               break;
+           {
+               assert(family == AF_INET);
+               u.sin.sin_family = AF_INET;
+               u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+               u.sin.sin_port = htons(localport);
+               retcode = bind(s, &u.sa, sizeof(u.sin));
+           }
+           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 */
        }
-
-       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;
+       
+       if (err)
+           goto 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
+    switch(family) {
+#ifndef NO_IPV6
+      case AF_INET:
+       /* XXX would be better to have got getaddrinfo() to fill in the port. */
+       ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+           htons(sock->port);
+       sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+       salen = sock->step.ai->ai_addrlen;
+       break;
+      case AF_INET6:
+       ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
+           htons(sock->port);
+       sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+       salen = sock->step.ai->ai_addrlen;
+       break;
+#else
+      case AF_INET:
+       u.sin.sin_family = AF_INET;
+       u.sin.sin_addr.s_addr = htonl(sock->addr->addresses[sock->step.curraddr]);
+       u.sin.sin_port = htons((short) sock->port);
+       sa = &u;
+       salen = sizeof u.sin;
+       break;
 #endif
-    {
-       a.sin_family = AF_INET;
-       a.sin_addr.s_addr = htonl(addr->address);
-       a.sin_port = htons((short) port);
+      case AF_UNIX:
+       assert(sock->port == 0);       /* to catch confused people */
+       assert(strlen(sock->addr->hostname) < sizeof u.su.sun_path);
+       u.su.sun_family = AF_UNIX;
+       strcpy(u.su.sun_path, sock->addr->hostname);
+       sa = &u;
+       salen = sizeof u.su;
+       break;
+
+      default:
+       assert(0 && "unknown address family");
+       exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
     }
 
-    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 ) {
-           ret->error = error_string(errno);
-           return (Socket) ret;
+    nonblock(s);
+
+    if ((connect(s, &(sa->sa), salen)) < 0) {
+       if ( errno != EINPROGRESS ) {
+           err = errno;
+           goto ret;
        }
     } else {
        /*
         * If we _don't_ get EWOULDBLOCK, the connect has completed
         * and we should set the socket as connected and writable.
         */
-       ret->connected = 1;
-       ret->writable = 1;
+       sock->connected = 1;
+       sock->writable = 1;
     }
 
-    add234(sktree, ret);
+    uxsel_tell(sock);
 
-    return (Socket) ret;
+    ret:
+
+    /*
+     * No matter what happened, put the socket back in the tree.
+     */
+    add234(sktree, sock);
+
+    if (err)
+       plug_log(sock->plug, 1, sock->addr, sock->port, strerror(err), err);
+    return err;
 }
 
-Socket sk_newlistener(int port, Plug plug, int local_host_only)
+Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
+             int nodelay, int keepalive, 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
-    };
+    Actual_Socket ret;
+    int err;
+
+    /*
+     * Create Socket structure.
+     */
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
+    ret->error = NULL;
+    ret->plug = plug;
+    bufchain_init(&ret->output_data);
+    ret->connected = 0;                       /* to start with */
+    ret->writable = 0;                /* to start with */
+    ret->sending_oob = 0;
+    ret->frozen = 0;
+    ret->localhost_only = 0;          /* unused, but best init anyway */
+    ret->pending_error = 0;
+    ret->parent = ret->child = NULL;
+    ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
+    ret->listener = 0;
+    ret->addr = addr;
+    START_STEP(ret->addr, ret->step);
+    ret->s = -1;
+    ret->oobinline = oobinline;
+    ret->nodelay = nodelay;
+    ret->keepalive = keepalive;
+    ret->privport = privport;
+    ret->port = port;
+
+    err = 0;
+    do {
+        err = try_connect(ret);
+    } while (err && sk_nextaddr(ret->addr, &ret->step));
+
+    if (err)
+        ret->error = strerror(err);
+
+    return (Socket) ret;
+}
 
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family)
+{
     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;
+    union sockaddr_union u;
+    union sockaddr_union *addr;
+    int addrlen;
     Actual_Socket ret;
     int retcode;
+    int address_family;
     int on = 1;
 
     /*
      * 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);
     ret->writable = 0;                /* to start with */
     ret->sending_oob = 0;
     ret->frozen = 0;
-    ret->frozen_readable = 0;
     ret->localhost_only = local_host_only;
     ret->pending_error = 0;
+    ret->parent = ret->child = NULL;
     ret->oobpending = FALSE;
+    ret->outgoingeof = EOF_NO;
+    ret->incomingeof = FALSE;
     ret->listener = 1;
+    ret->addr = NULL;
+
+    /*
+     * Translate address_family from platform-independent constants
+     * into local reality.
+     */
+    address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+                     orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+                     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);
-    ret->s = s;
+    s = socket(address_family, SOCK_STREAM, 0);
+
+#ifndef NO_IPV6
+    /* 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);
+    }
+#endif
 
     if (s < 0) {
-       ret->error = error_string(errno);
+       ret->error = strerror(errno);
        return (Socket) ret;
     }
 
+    cloexec(s);
+
     ret->oobinline = 0;
 
-    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;
-       if (local_host_only)
-           a6.sin6_addr = in6addr_loopback;
-       else
-           a6.sin6_addr = in6addr_any;
-       a6.sin6_port = htons(port);
-    } else
-#endif
-    {
-       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);
-       a.sin_port = htons((short)port);
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                   (const char *)&on, sizeof(on)) < 0) {
+        ret->error = strerror(errno);
+        close(s);
+        return (Socket) ret;
     }
-#ifdef IPV6
-    retcode = bind(s, (addr->family == AF_INET6 ?
-                      (struct sockaddr *) &a6 :
-                      (struct sockaddr *) &a),
-                  (addr->family ==
-                   AF_INET6 ? sizeof(a6) : sizeof(a)));
+
+    retcode = -1;
+    addr = NULL; addrlen = -1;         /* placate optimiser */
+
+    if (srcaddr != NULL) {
+#ifndef NO_IPV6
+        hints.ai_flags = AI_NUMERICHOST;
+        hints.ai_family = address_family;
+        hints.ai_socktype = SOCK_STREAM;
+        hints.ai_protocol = 0;
+        hints.ai_addrlen = 0;
+        hints.ai_addr = NULL;
+        hints.ai_canonname = NULL;
+        hints.ai_next = NULL;
+       assert(port >= 0 && port <= 99999);
+        sprintf(portstr, "%d", port);
+        retcode = getaddrinfo(srcaddr, portstr, &hints, &ai);
+       if (retcode == 0) {
+           addr = (union sockaddr_union *)ai->ai_addr;
+           addrlen = ai->ai_addrlen;
+       }
 #else
-    retcode = bind(s, (struct sockaddr *) &a, sizeof(a));
+        memset(&u,'\0',sizeof u);
+        u.sin.sin_family = AF_INET;
+        u.sin.sin_port = htons(port);
+        u.sin.sin_addr.s_addr = inet_addr(srcaddr);
+        if (u.sin.sin_addr.s_addr != (in_addr_t)(-1)) {
+            /* Override localhost_only with specified listen addr. */
+            ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
+        }
+        addr = &u;
+        addrlen = sizeof(u.sin);
+        retcode = 0;
 #endif
-    if (retcode >= 0) {
-       err = 0;
-    } else {
-       err = errno;
     }
 
-    if (err) {
-       ret->error = error_string(err);
-       return (Socket) ret;
+    if (retcode != 0) {
+        memset(&u,'\0',sizeof u);
+#ifndef NO_IPV6
+        if (address_family == AF_INET6) {
+            u.sin6.sin6_family = AF_INET6;
+            u.sin6.sin6_port = htons(port);
+            if (local_host_only)
+                u.sin6.sin6_addr = in6addr_loopback;
+            else
+                u.sin6.sin6_addr = in6addr_any;
+            addr = &u;
+            addrlen = sizeof(u.sin6);
+        } else
+#endif
+        {
+            u.sin.sin_family = AF_INET;
+            u.sin.sin_port = htons(port);
+           if (local_host_only)
+               u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+           else
+               u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+            addr = &u;
+            addrlen = sizeof(u.sin);
+        }
     }
 
+    retcode = bind(s, &addr->sa, addrlen);
+    if (retcode < 0) {
+        close(s);
+       ret->error = strerror(errno);
+       return (Socket) ret;
+    }
 
     if (listen(s, SOMAXCONN) < 0) {
         close(s);
-       ret->error = error_string(errno);
+       ret->error = strerror(errno);
        return (Socket) ret;
     }
 
+#ifndef NO_IPV6
+    /*
+     * If we were given ADDRTYPE_UNSPEC, we must also create an
+     * IPv4 listening socket and link it to this one.
+     */
+    if (address_family == AF_INET6 && orig_address_family == ADDRTYPE_UNSPEC) {
+        Actual_Socket other;
+
+        other = (Actual_Socket) sk_newlistener(srcaddr, port, plug,
+                                               local_host_only, ADDRTYPE_IPV4);
+
+        if (other) {
+            if (!other->error) {
+                other->parent = ret;
+                ret->child = other;
+            } else {
+                /* If we couldn't create a listening socket on IPv4 as well
+                 * as IPv6, we must return an error overall. */
+                close(s);
+                sfree(ret);
+                return (Socket) other;
+            }
+        }
+    }
+#endif
+
+    ret->s = s;
+
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
@@ -655,11 +962,69 @@ static void sk_tcp_close(Socket sock)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    if (s->child)
+        sk_tcp_close((Socket)s->child);
+
+    uxsel_del(s->s);
     del234(sktree, s);
     close(s->s);
+    if (s->addr)
+        sk_addr_free(s->addr);
     sfree(s);
 }
 
+void *sk_getxdmdata(void *sock, int *lenp)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    union sockaddr_union u;
+    socklen_t addrlen;
+    char *buf;
+    static unsigned int unix_addr = 0xFFFFFFFF;
+
+    /*
+     * We must check that this socket really _is_ an Actual_Socket.
+     */
+    if (s->fn != &tcp_fn_table)
+       return NULL;                   /* failure */
+
+    addrlen = sizeof(u);
+    if (getsockname(s->s, &u.sa, &addrlen) < 0)
+       return NULL;
+    switch(u.sa.sa_family) {
+      case AF_INET:
+       *lenp = 6;
+       buf = snewn(*lenp, char);
+       PUT_32BIT_MSB_FIRST(buf, ntohl(u.sin.sin_addr.s_addr));
+       PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin.sin_port));
+       break;
+#ifndef NO_IPV6
+    case AF_INET6:
+       *lenp = 6;
+       buf = snewn(*lenp, char);
+       if (IN6_IS_ADDR_V4MAPPED(&u.sin6.sin6_addr)) {
+           memcpy(buf, u.sin6.sin6_addr.s6_addr + 12, 4);
+           PUT_16BIT_MSB_FIRST(buf+4, ntohs(u.sin6.sin6_port));
+       } else
+           /* This is stupid, but it's what XLib does. */
+           memset(buf, 0, 6);
+       break;
+#endif
+      case AF_UNIX:
+       *lenp = 6;
+       buf = snewn(*lenp, char);
+       PUT_32BIT_MSB_FIRST(buf, unix_addr--);
+        PUT_16BIT_MSB_FIRST(buf+4, getpid());
+       break;
+
+       /* XXX IPV6 */
+
+      default:
+       return NULL;
+    }
+
+    return buf;
+}
+
 /*
  * The function which tries to send on a socket once it's deemed
  * writable.
@@ -690,11 +1055,9 @@ void try_send(Actual_Socket s)
                 */
                s->writable = FALSE;
                return;
-           } else if (nsent == 0 ||
-                      err == ECONNABORTED || err == ECONNRESET) {
+           } else {
                /*
-                * If send() returns CONNABORTED or CONNRESET, we
-                * unfortunately can't just call plug_closing(),
+                * We unfortunately can't just call plug_closing(),
                 * because it's quite likely that we're currently
                 * _in_ a call from the code we'd be calling back
                 * to, so we'd have to make half the SSH code
@@ -703,12 +1066,17 @@ void try_send(Actual_Socket s)
                 * plug_closing()) at some suitable future moment.
                 */
                s->pending_error = err;
+                /*
+                 * Immediately cease selecting on this socket, so that
+                 * we don't tight-loop repeatedly trying to do
+                 * whatever it was that went wrong.
+                 */
+                uxsel_tell(s);
+                /*
+                 * Notify the front end that it might want to call us.
+                 */
+                frontend_net_error_pending();
                return;
-           } else {
-               /* We're inside the Unix frontend here, so we know
-                * that the frontend handle is unnecessary. */
-               logevent(NULL, error_string(err));
-               fatalbox("%s", error_string(err));
            }
        } else {
            if (s->sending_oob) {
@@ -723,12 +1091,29 @@ void try_send(Actual_Socket s)
            }
        }
     }
+
+    /*
+     * If we reach here, we've finished sending everything we might
+     * have needed to send. Send EOF, if we need to.
+     */
+    if (s->outgoingeof == EOF_PENDING) {
+        shutdown(s->s, SHUT_WR);
+        s->outgoingeof = EOF_SENT;
+    }
+
+    /*
+     * Also update the select status, because we don't need to select
+     * for writing any more.
+     */
+    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;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Add the data to the buffer list on the socket.
      */
@@ -740,13 +1125,21 @@ 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;
 
+    assert(s->outgoingeof == EOF_NO);
+
     /*
      * Replace the buffer list on the socket with the data.
      */
@@ -761,30 +1154,54 @@ 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 void sk_tcp_write_eof(Socket sock)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+
+    assert(s->outgoingeof == EOF_NO);
+
+    /*
+     * Mark the socket as pending outgoing EOF.
+     */
+    s->outgoingeof = EOF_PENDING;
+
+    /*
+     * Now try sending from the start of the buffer list.
+     */
+    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);
+}
+
+static int net_select_result(int fd, int event)
 {
     int ret;
-    int err;
     char buf[20480];                  /* nice big buffer for plenty of speed */
     Actual_Socket s;
     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,13 +1213,19 @@ 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));
-               /* We're inside the Unix frontend here, so we know
-                * that the frontend handle is unnecessary. */
-               logevent(NULL, str);
-               fatalbox("%s", str);
+                return plug_closing(s->plug,
+                                   ret == 0 ? "Internal networking trouble" :
+                                   strerror(errno), errno, 0);
            } else {
+                /*
+                 * Receiving actual data on a socket means we can
+                 * stop falling back through the candidate
+                 * addresses to connect to.
+                 */
+                if (s->addr) {
+                    sk_addr_free(s->addr);
+                    s->addr = NULL;
+                }
                return plug_receive(s->plug, 2, buf, ret);
            }
            break;
@@ -810,31 +1233,34 @@ 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) {
            /*
             * On a listening socket, the readability event means a
             * connection is ready to be accepted.
             */
-           struct sockaddr_in isa;
-           int addrlen = sizeof(struct sockaddr_in);
+           union sockaddr_union su;
+           socklen_t addrlen = sizeof(su);
            int t;  /* socket of connection */
 
-           memset(&isa, 0, sizeof(struct sockaddr_in));
-           err = 0;
-           t = accept(s->s,(struct sockaddr *)&isa,&addrlen);
+           memset(&su, 0, addrlen);
+           t = accept(s->s, &su.sa, &addrlen);
            if (t < 0) {
                break;
            }
 
+            nonblock(t);
+
            if (s->localhost_only &&
-               ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) {
+               !sockaddr_is_loopback(&su.sa)) {
                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;
@@ -846,10 +1272,8 @@ int select_result(int fd, int event)
         */
 
        /* In the case the socket is still frozen, we don't even bother */
-       if (s->frozen) {
-           s->frozen_readable = 1;
+       if (s->frozen)
            break;
-       }
 
        /*
         * We have received data on the socket. For an oobinline
@@ -864,7 +1288,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) {
@@ -872,15 +1296,48 @@ int select_result(int fd, int event)
            }
        }
        if (ret < 0) {
-           return plug_closing(s->plug, error_string(errno), errno, 0);
+            /*
+             * An error at this point _might_ be an error reported
+             * by a non-blocking connect(). So before we return a
+             * panic status to the user, let's just see whether
+             * that's the case.
+             */
+            int err = errno;
+           if (s->addr) {
+               plug_log(s->plug, 1, s->addr, s->port, strerror(err), err);
+               while (s->addr && sk_nextaddr(s->addr, &s->step)) {
+                   err = try_connect(s);
+               }
+           }
+            if (err != 0)
+                return plug_closing(s->plug, strerror(err), err, 0);
        } else if (0 == ret) {
+            s->incomingeof = TRUE;     /* stop trying to read now */
+            uxsel_tell(s);
            return plug_closing(s->plug, NULL, 0, 0);
        } else {
+            /*
+             * Receiving actual data on a socket means we can
+             * stop falling back through the candidate
+             * addresses to connect to.
+             */
+            if (s->addr) {
+                sk_addr_free(s->addr);
+                s->addr = NULL;
+            }
            return plug_receive(s->plug, atmark ? 0 : 1, buf, ret);
        }
        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);
@@ -924,7 +1381,7 @@ void net_pending_errors(void)
                 * An error has occurred on this socket. Pass it to the
                 * plug.
                 */
-               plug_closing(s->plug, error_string(s->pending_error),
+               plug_closing(s->plug, strerror(s->pending_error),
                             s->pending_error, 0);
                break;
            }
@@ -953,11 +1410,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;
@@ -969,47 +1426,25 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
     if (s->frozen == is_frozen)
        return;
     s->frozen = is_frozen;
-    if (!is_frozen && s->frozen_readable) {
-       char c;
-       recv(s->s, &c, 1, MSG_PEEK);
-    }
-    s->frozen_readable = 0;
-}
-
-/*
- * 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)
-{
-    int val = 0;
-    if (s->connected && !s->frozen)
-       val |= 1 | 4;                  /* read, except */
-    if (bufchain_size(&s->output_data))
-       val |= 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;
+    uxsel_tell(s);
 }
 
-int next_socket(int *state, int *rwx)
+static void uxsel_tell(Actual_Socket s)
 {
-    Actual_Socket s = index234(sktree, (*state)++);
-    if (s)
-       set_rwx(s, rwx);
-    return s ? s->s : -1;
+    int rwx = 0;
+    if (!s->pending_error) {
+        if (s->listener) {
+            rwx |= 1;                  /* read == accept */
+        } else {
+            if (!s->connected)
+                rwx |= 2;              /* write == connect */
+            if (s->connected && !s->frozen && !s->incomingeof)
+                rwx |= 1 | 4;          /* read, except */
+            if (bufchain_size(&s->output_data))
+                rwx |= 2;              /* write */
+        }
+    }
+    uxsel_set(s->s, rwx, net_select_result);
 }
 
 int net_service_lookup(char *service)
@@ -1021,3 +1456,54 @@ int net_service_lookup(char *service)
     else
        return 0;
 }
+
+char *get_hostname(void)
+{
+    int len = 128;
+    char *hostname = NULL;
+    do {
+       len *= 2;
+       hostname = sresize(hostname, len, char);
+       if ((gethostname(hostname, len) < 0) &&
+           (errno != ENAMETOOLONG)) {
+           sfree(hostname);
+           hostname = NULL;
+           break;
+       }
+    } while (strlen(hostname) >= len-1);
+    return hostname;
+}
+
+SockAddr platform_get_x11_unix_address(const char *sockpath, int displaynum)
+{
+    SockAddr ret = snew(struct SockAddr_tag);
+    int n;
+
+    memset(ret, 0, sizeof *ret);
+    ret->superfamily = UNIX;
+    /*
+     * In special circumstances (notably Mac OS X Leopard), we'll
+     * have been passed an explicit Unix socket path.
+     */
+    if (sockpath) {
+       n = snprintf(ret->hostname, sizeof ret->hostname,
+                    "%s", sockpath);
+    } else {
+       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";
+
+#ifndef NO_IPV6
+    ret->ais = NULL;
+#else
+    ret->addresses = NULL;
+    ret->naddresses = 0;
+#endif
+    ret->refcount = 1;
+    return ret;
+}