Completely remove the 'frozen_readable' mechanism from uxnet.c. It
[u/mdw/putty] / unix / uxnet.c
index be7fb70..5190ef8 100644 (file)
 #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
 
-#define ipv4_is_loopback(addr) (inet_netof(addr) == IN_LOOPBACKNET)
+/* 
+ * 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;
@@ -36,52 +77,73 @@ struct Socket_tag {
     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;
 };
 
-/*
- * 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;
-
 struct SockAddr_tag {
+    int refcount;
     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;
+    enum { UNRESOLVED, UNIX, IP } superfamily;
 #ifndef NO_IPV6
     struct addrinfo *ais;             /* Addresses IPv6 style. */
-    struct addrinfo *ai;              /* steps along the linked list */
 #else
     unsigned long *addresses;         /* Addresses IPv4 style. */
-    int naddresses, curraddr;
+    int naddresses;
 #endif
     char hostname[512];                       /* Store an unresolved host name. */
 };
 
+/*
+ * 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.
+ */
+#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
+
+/*
+ * 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);
@@ -94,6 +156,10 @@ 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;
 }
 
@@ -140,9 +206,10 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, int address_famil
 
     /* 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;
+    ret->refcount = 1;
 
 #ifndef NO_IPV6
     hints.ai_flags = AI_CANONNAME;
@@ -156,15 +223,14 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, int address_famil
     hints.ai_canonname = NULL;
     hints.ai_next = NULL;
     err = getaddrinfo(host, NULL, &hints, &ret->ais);
-    ret->ai = ret->ais;
     if (err != 0) {
        ret->error = gai_strerror(err);
        return ret;
     }
-    ret->family = ret->ai->ai_family;
+    ret->superfamily = IP;
     *realhost = '\0';
-    if (ret->ai->ai_canonname != NULL)
-       strncat(realhost, ret->ai->ai_canonname, sizeof(realhost) - 1);
+    if (ret->ais->ai_canonname != NULL)
+       strncat(realhost, ret->ais->ai_canonname, sizeof(realhost) - 1);
     else
        strncat(realhost, host, sizeof(realhost) - 1);
 #else
@@ -173,12 +239,12 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, int address_famil
         * Otherwise use the IPv4-only gethostbyname... (NOTE:
         * we don't use gethostbyname as a fallback!)
         */
-       if (ret->family == 0) {
+       if (ret->superfamily == UNRESOLVED) {
            /*debug(("Resolving \"%s\" with gethostbyname() (IPv4 only)...\n", host)); */
            if ( (h = gethostbyname(host)) )
-               ret->family = AF_INET;
+               ret->superfamily = IP;
        }
-       if (ret->family == 0) {
+       if (ret->superfamily == UNRESOLVED) {
            ret->error = (h_errno == HOST_NOT_FOUND ||
                          h_errno == NO_DATA ||
                          h_errno == NO_ADDRESS ? "Host does not exist" :
@@ -201,12 +267,11 @@ SockAddr sk_namelookup(const char *host, char **canonicalname, int address_famil
         * 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->curraddr = 0;
     }
 #endif
     realhost[lenof(realhost)-1] = '\0';
@@ -219,7 +284,7 @@ SockAddr sk_nonamelookup(const char *host)
 {
     SockAddr ret = snew(struct SockAddr_tag);
     ret->error = NULL;
-    ret->family = AF_UNSPEC;
+    ret->superfamily = UNRESOLVED;
     strncpy(ret->hostname, host, lenof(ret->hostname));
     ret->hostname[lenof(ret->hostname)-1] = '\0';
 #ifndef NO_IPV6
@@ -227,21 +292,21 @@ SockAddr sk_nonamelookup(const char *host)
 #else
     ret->addresses = NULL;
 #endif
+    ret->refcount = 1;
     return ret;
 }
 
-static int sk_nextaddr(SockAddr addr)
+static int sk_nextaddr(SockAddr addr, SockAddrStep *step)
 {
 #ifndef NO_IPV6
-    if (addr->ai->ai_next) {
-       addr->ai = addr->ai->ai_next;
-       addr->family = addr->ai->ai_family;
+    if (step->ai && step->ai->ai_next) {
+       step->ai = step->ai->ai_next;
        return TRUE;
     } else
        return FALSE;
 #else
-    if (addr->curraddr+1 < addr->naddresses) {
-       addr->curraddr++;
+    if (step->curraddr+1 < addr->naddresses) {
+       step->curraddr++;
        return TRUE;
     } else {
        return FALSE;
@@ -251,21 +316,25 @@ static int sk_nextaddr(SockAddr addr)
 
 void sk_getaddr(SockAddr addr, char *buf, int buflen)
 {
-
-    if (addr->family == AF_UNSPEC) {
+    /* 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->ai->ai_addr, addr->ai->ai_addrlen, buf, buflen,
+       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;
-       assert(addr->family == AF_INET);
-       a.s_addr = htonl(addr->addresses[addr->curraddr]);
+       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
@@ -274,66 +343,99 @@ void sk_getaddr(SockAddr addr, char *buf, int buflen)
 
 int sk_hostname_is_local(char *name)
 {
-    return !strcmp(name, "localhost");
+    return !strcmp(name, "localhost") ||
+          !strcmp(name, "::1") ||
+          !strncmp(name, "127.", 4);
 }
 
-int sk_address_is_local(SockAddr addr)
+#define ipv4_is_loopback(addr) \
+    (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
+
+static int sockaddr_is_loopback(struct sockaddr *sa)
 {
+    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;
+    }
+}
 
-    if (addr->family == AF_UNSPEC)
+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
-       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;
+       return sockaddr_is_loopback(addr->ais->ai_addr);
 #else
        struct in_addr a;
-       assert(addr->family == AF_INET);
-       a.s_addr = htonl(addr->addresses[addr->curraddr]);
+       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)
 {
-    return (addr->family == AF_INET ? ADDRTYPE_IPV4 :
+    SockAddrStep step;
+    int family;
+    START_STEP(addr, step);
+    family = SOCKADDR_FAMILY(addr, step);
+
+    return (family == AF_INET ? ADDRTYPE_IPV4 :
 #ifndef NO_IPV6
-           addr->family == AF_INET6 ? ADDRTYPE_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 (addr->family == AF_INET)
-       memcpy(buf, &((struct sockaddr_in *)addr->ai->ai_addr)->sin_addr,
+    if (family == AF_INET)
+       memcpy(buf, &((struct sockaddr_in *)step.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,
+    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(addr->family == AF_INET);
-    a.s_addr = htonl(addr->addresses[addr->curraddr]);
+    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);
@@ -343,6 +445,12 @@ void sk_addr_free(SockAddr addr)
     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;
@@ -363,6 +471,7 @@ static void sk_tcp_flush(Socket s)
 static void sk_tcp_close(Socket s);
 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);
@@ -373,6 +482,7 @@ static struct socket_function_table tcp_fn_table = {
     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,
@@ -395,12 +505,15 @@ Socket sk_register(OSSocket sockfd, Plug plug)
     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 = sockfd;
 
@@ -420,15 +533,19 @@ Socket sk_register(OSSocket sockfd, Plug plug)
 static int try_connect(Actual_Socket sock)
 {
     int s;
-#ifndef NO_IPV6
-    struct sockaddr_in6 a6;
-#endif
-    struct sockaddr_in a;
-    struct sockaddr_un au;
-    const struct sockaddr *sa;
+    union sockaddr_union u;
+    const union sockaddr_union *sa;
     int err = 0;
     short localport;
-    int fl, salen;
+    int salen, family;
+
+    /*
+     * 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.
+     */
+    del234(sktree, sock);
 
     if (sock->s >= 0)
         close(sock->s);
@@ -438,8 +555,9 @@ static int try_connect(Actual_Socket sock)
     /*
      * Open socket.
      */
-    assert(sock->addr->family != AF_UNSPEC);
-    s = socket(sock->addr->family, SOCK_STREAM, 0);
+    family = SOCKADDR_FAMILY(sock->addr, sock->step);
+    assert(family != AF_UNSPEC);
+    s = socket(family, SOCK_STREAM, 0);
     sock->s = s;
 
     if (s < 0) {
@@ -447,19 +565,36 @@ static int try_connect(Actual_Socket sock)
        goto ret;
     }
 
+    cloexec(s);
+
     if (sock->oobinline) {
        int b = TRUE;
-       setsockopt(s, SOL_SOCKET, SO_OOBINLINE, (void *) &b, sizeof(b));
+       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, IPPROTO_TCP, TCP_NODELAY, (void *) &b, sizeof(b));
+       if (setsockopt(s, IPPROTO_TCP, TCP_NODELAY,
+                       (void *) &b, sizeof(b)) < 0) {
+            err = errno;
+            close(s);
+            goto ret;
+        }
     }
 
     if (sock->keepalive) {
        int b = TRUE;
-       setsockopt(s, SOL_SOCKET, SO_KEEPALIVE, (void *) &b, sizeof(b));
+       if (setsockopt(s, SOL_SOCKET, SO_KEEPALIVE,
+                       (void *) &b, sizeof(b)) < 0) {
+            err = errno;
+            close(s);
+            goto ret;
+        }
     }
 
     /*
@@ -471,33 +606,30 @@ static int try_connect(Actual_Socket sock)
        localport = 0;                 /* just use port 0 (ie kernel picks) */
 
     /* 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
+    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(sock->addr->family != AF_UNIX) {
+    if (family != AF_UNIX) {
        /* Loop round trying to bind */
        while (1) {
            int retcode;
 
 #ifndef NO_IPV6
-           if (sock->addr->family == AF_INET6) {
+           if (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));
+               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
            {
-               assert(sock->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));
+               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;
@@ -522,49 +654,47 @@ static int try_connect(Actual_Socket sock)
     /*
      * Connect to remote address.
      */
-    switch(sock->addr->family) {
+    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->addr->ai->ai_addr)->sin_port =
+       ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
            htons(sock->port);
-       sa = (const struct sockaddr *)sock->addr->ai->ai_addr;
-       salen = sock->addr->ai->ai_addrlen;
+       sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+       salen = sock->step.ai->ai_addrlen;
        break;
       case AF_INET6:
-       ((struct sockaddr_in *)sock->addr->ai->ai_addr)->sin_port =
+       ((struct sockaddr_in *)sock->step.ai->ai_addr)->sin_port =
            htons(sock->port);
-       sa = (const struct sockaddr *)sock->addr->ai->ai_addr;
-       salen = sock->addr->ai->ai_addrlen;
+       sa = (const union sockaddr_union *)sock->step.ai->ai_addr;
+       salen = sock->step.ai->ai_addrlen;
        break;
 #else
       case AF_INET:
-       a.sin_family = AF_INET;
-       a.sin_addr.s_addr = htonl(sock->addr->addresses[sock->addr->curraddr]);
-       a.sin_port = htons((short) sock->port);
-       sa = (const struct sockaddr *)&a;
-       salen = sizeof a;
+       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
       case AF_UNIX:
        assert(sock->port == 0);       /* to catch confused people */
-       assert(strlen(sock->addr->hostname) < sizeof au.sun_path);
-       memset(&au, 0, sizeof au);
-       au.sun_family = AF_UNIX;
-       strcpy(au.sun_path, sock->addr->hostname);
-       sa = (const struct sockaddr *)&au;
-       salen = sizeof au;
+       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. */
     }
 
-    fl = fcntl(s, F_GETFL);
-    if (fl != -1)
-       fcntl(s, F_SETFL, fl | O_NONBLOCK);
+    nonblock(s);
 
-    if ((connect(s, sa, salen)) < 0) {
+    if ((connect(s, &(sa->sa), salen)) < 0) {
        if ( errno != EINPROGRESS ) {
            err = errno;
            goto ret;
@@ -579,9 +709,14 @@ static int try_connect(Actual_Socket sock)
     }
 
     uxsel_tell(sock);
-    add234(sktree, sock);
 
     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;
@@ -605,12 +740,15 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     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->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;
@@ -621,7 +759,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     err = 0;
     do {
         err = try_connect(ret);
-    } while (err && sk_nextaddr(ret->addr));
+    } while (err && sk_nextaddr(ret->addr, &ret->step));
 
     if (err)
         ret->error = strerror(err);
@@ -629,19 +767,19 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     return (Socket) ret;
 }
 
-Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int address_family)
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, int orig_address_family)
 {
     int s;
 #ifndef NO_IPV6
     struct addrinfo hints, *ai;
     char portstr[6];
-    struct sockaddr_in6 a6;
 #endif
-    struct sockaddr *addr;
+    union sockaddr_union u;
+    union sockaddr_union *addr;
     int addrlen;
-    struct sockaddr_in a;
     Actual_Socket ret;
     int retcode;
+    int address_family;
     int on = 1;
 
     /*
@@ -655,10 +793,12 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
     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;
 
@@ -666,8 +806,11 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
      * 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);
+    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.
@@ -683,20 +826,29 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
      */
     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 = strerror(errno);
        return (Socket) ret;
     }
 
+    cloexec(s);
+
     ret->oobinline = 0;
 
-    setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (const char *)&on, sizeof(on));
+    if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR,
+                   (const char *)&on, sizeof(on)) < 0) {
+        ret->error = strerror(errno);
+        close(s);
+        return (Socket) ret;
+    }
 
     retcode = -1;
     addr = NULL; addrlen = -1;         /* placate optimiser */
@@ -705,62 +857,61 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
 #ifndef NO_IPV6
         hints.ai_flags = AI_NUMERICHOST;
         hints.ai_family = address_family;
-        hints.ai_socktype = 0;
+        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 = ai->ai_addr;
+           addr = (union sockaddr_union *)ai->ai_addr;
            addrlen = ai->ai_addrlen;
        }
 #else
-        memset(&a,'\0',sizeof(struct sockaddr_in));
-        a.sin_family = AF_INET;
-        a.sin_port = htons(port);
-        a.sin_addr.s_addr = inet_addr(srcaddr);
-        if (a.sin_addr.s_addr != (in_addr_t)(-1)) {
+        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(a.sin_addr);
-            got_addr = 1;
+            ret->localhost_only = ipv4_is_loopback(u.sin.sin_addr);
         }
-        addr = (struct sockaddr *)a;
-        addrlen = sizeof(a);
+        addr = &u;
+        addrlen = sizeof(u.sin);
         retcode = 0;
 #endif
     }
 
     if (retcode != 0) {
+        memset(&u,'\0',sizeof u);
 #ifndef NO_IPV6
         if (address_family == AF_INET6) {
-            memset(&a6,'\0',sizeof(struct sockaddr_in6));
-            a6.sin6_family = AF_INET6;
-            a6.sin6_port = htons(port);
+            u.sin6.sin6_family = AF_INET6;
+            u.sin6.sin6_port = htons(port);
             if (local_host_only)
-                a6.sin6_addr = in6addr_loopback;
+                u.sin6.sin6_addr = in6addr_loopback;
             else
-                a6.sin6_addr = in6addr_any;
-            addr = (struct sockaddr *)&a6;
-            addrlen = sizeof(a6);
+                u.sin6.sin6_addr = in6addr_any;
+            addr = &u;
+            addrlen = sizeof(u.sin6);
         } else
 #endif
         {
-            memset(&a,'\0',sizeof(struct sockaddr_in));
-            a.sin_family = AF_INET;
-            a.sin_port = htons(port);
+            u.sin.sin_family = AF_INET;
+            u.sin.sin_port = htons(port);
            if (local_host_only)
-               a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+               u.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
            else
-               a.sin_addr.s_addr = htonl(INADDR_ANY);
-            addr = (struct sockaddr *)&a;
-            addrlen = sizeof(a);
+               u.sin.sin_addr.s_addr = htonl(INADDR_ANY);
+            addr = &u;
+            addrlen = sizeof(u.sin);
         }
     }
 
-    retcode = bind(s, addr, addrlen);
+    retcode = bind(s, &addr->sa, addrlen);
     if (retcode < 0) {
         close(s);
        ret->error = strerror(errno);
@@ -773,6 +924,32 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
        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);
@@ -785,6 +962,9 @@ 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);
@@ -793,42 +973,56 @@ static void sk_tcp_close(Socket sock)
     sfree(s);
 }
 
-int sk_getxdmdata(void *sock, unsigned long *ip, int *port)
+void *sk_getxdmdata(void *sock, int *lenp)
 {
     Actual_Socket s = (Actual_Socket) sock;
-    struct sockaddr_in addr;
+    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 0;                      /* failure */
+       return NULL;                   /* failure */
 
-    addrlen = sizeof(addr);
-    if (getsockname(s->s, (struct sockaddr *)&addr, &addrlen) < 0)
-       return 0;
-    switch(addr.sin_family) {
+    addrlen = sizeof(u);
+    if (getsockname(s->s, &u.sa, &addrlen) < 0)
+       return NULL;
+    switch(u.sa.sa_family) {
       case AF_INET:
-       *ip = ntohl(addr.sin_addr.s_addr);
-       *port = ntohs(addr.sin_port);
+       *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:
-       /*
-        * 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();
+       *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 0;
+       return NULL;
     }
 
-    return 1;
+    return buf;
 }
 
 /*
@@ -861,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
@@ -874,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, strerror(err));
-               fatalbox("%s", strerror(err));
            }
        } else {
            if (s->sending_oob) {
@@ -894,6 +1091,20 @@ 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);
 }
 
@@ -901,6 +1112,8 @@ 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.
      */
@@ -925,6 +1138,8 @@ 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.
      */
@@ -948,6 +1163,30 @@ static int sk_tcp_write_oob(Socket sock, const char *buf, int len)
     return s->sending_oob;
 }
 
+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;
@@ -974,12 +1213,9 @@ static int net_select_result(int fd, int event)
            ret = recv(s->s, buf, sizeof(buf), MSG_OOB);
            noise_ultralight(ret);
            if (ret <= 0) {
-               const char *str = (ret == 0 ? "Internal networking trouble" :
-                                  strerror(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
@@ -1009,17 +1245,20 @@ static int net_select_result(int fd, int event)
             * 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));
-           t = accept(s->s,(struct sockaddr *)&isa,(socklen_t *) &addrlen);
+           memset(&su, 0, addrlen);
+           t = accept(s->s, &su.sa, &addrlen);
            if (t < 0) {
                break;
            }
 
-           if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) {
+            nonblock(t);
+
+           if (s->localhost_only &&
+               !sockaddr_is_loopback(&su.sa)) {
                close(t);              /* someone let nonlocal through?! */
            } else if (plug_accepting(s->plug, t)) {
                close(t);              /* denied or error */
@@ -1033,10 +1272,8 @@ static int net_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
@@ -1068,13 +1305,15 @@ static int net_select_result(int fd, int event)
             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)) {
+               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 {
             /*
@@ -1187,25 +1426,24 @@ 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;
     uxsel_tell(s);
 }
 
 static void uxsel_tell(Actual_Socket s)
 {
     int rwx = 0;
-    if (!s->connected)
-       rwx |= 2;                      /* write == connect */
-    if (s->connected && !s->frozen)
-       rwx |= 1 | 4;                  /* read, except */
-    if (bufchain_size(&s->output_data))
-       rwx |= 2;                      /* write */
-    if (s->listener)
-       rwx |= 1;                      /* read == accept */
+    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);
 }
 
@@ -1219,25 +1457,53 @@ int net_service_lookup(char *service)
        return 0;
 }
 
-SockAddr platform_get_x11_unix_address(int displaynum, char **canonicalname)
+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->family = AF_UNIX;
-    n = snprintf(ret->hostname, sizeof ret->hostname,
-                "%s%d", X11_UNIX_PATH, displaynum);
-    if(n < 0)
+    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)
+    else if (n >= sizeof ret->hostname)
        ret->error = "X11 UNIX name too long";
-    else
-       *canonicalname = dupstr(ret->hostname);
+
 #ifndef NO_IPV6
     ret->ais = NULL;
 #else
     ret->addresses = NULL;
+    ret->naddresses = 0;
 #endif
+    ret->refcount = 1;
     return ret;
 }