Fix for portfwd-addr-family: on Unix, when a tunnel is specified as "Auto"
[u/mdw/putty] / unix / uxnet.c
index 71c4515..a82c986 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
 
+/*
+ * 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 Socket_tag {
     struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
@@ -34,7 +49,7 @@ 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
@@ -49,18 +64,15 @@ struct Socket_tag {
     int nodelay, keepalive;            /* for connect()-type sockets */
     int privport, port;                /* and again */
     SockAddr addr;
+    /*
+     * 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 {
     const char *error;
     /*
@@ -92,6 +104,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;
 }
 
@@ -231,7 +247,7 @@ SockAddr sk_nonamelookup(const char *host)
 static int sk_nextaddr(SockAddr addr)
 {
 #ifndef NO_IPV6
-    if (addr->ai->ai_next) {
+    if (addr->ai && addr->ai->ai_next) {
        addr->ai = addr->ai->ai_next;
        addr->family = addr->ai->ai_family;
        return TRUE;
@@ -294,7 +310,7 @@ static int sockaddr_is_loopback(struct sockaddr *sa)
        sin6 = (struct sockaddr_in6 *)sa;
        return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr);
 #endif
-      case AF_LOCAL:
+      case AF_UNIX:
        return TRUE;
       default:
        return FALSE;
@@ -417,7 +433,9 @@ Socket sk_register(OSSocket sockfd, Plug plug)
     ret->pending_error = 0;
     ret->oobpending = FALSE;
     ret->listener = 0;
+    ret->parent = ret->child = NULL;
     ret->addr = NULL;
+    ret->connected = 1;
 
     ret->s = sockfd;
 
@@ -447,6 +465,14 @@ static int try_connect(Actual_Socket sock)
     short localport;
     int fl, salen;
 
+    /*
+     * 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);
 
@@ -464,6 +490,8 @@ 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));
@@ -575,6 +603,7 @@ static int try_connect(Actual_Socket sock)
 
       default:
        assert(0 && "unknown address family");
+       exit(1); /* XXX: GCC doesn't understand assert() on some systems. */
     }
 
     fl = fcntl(s, F_GETFL);
@@ -596,9 +625,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;
@@ -625,6 +659,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     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->listener = 0;
     ret->addr = addr;
@@ -646,7 +681,7 @@ 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
@@ -659,6 +694,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
     struct sockaddr_in a;
     Actual_Socket ret;
     int retcode;
+    int address_family;
     int on = 1;
 
     /*
@@ -675,6 +711,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
     ret->frozen_readable = 0;
     ret->localhost_only = local_host_only;
     ret->pending_error = 0;
+    ret->parent = ret->child = NULL;
     ret->oobpending = FALSE;
     ret->listener = 1;
     ret->addr = NULL;
@@ -683,8 +720,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.
@@ -700,17 +740,21 @@ 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));
@@ -743,9 +787,8 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
         if (a.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;
         }
-        addr = (struct sockaddr *)a;
+        addr = (struct sockaddr *)&a;
         addrlen = sizeof(a);
         retcode = 0;
 #endif
@@ -791,6 +834,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);
@@ -803,6 +872,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);
@@ -811,42 +883,63 @@ 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;
+#ifdef NO_IPV6
     struct sockaddr_in addr;
+#else
+    struct sockaddr_storage addr;
+    struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)&addr;
+#endif
+    struct sockaddr *sa = (struct sockaddr *)&addr;
+    struct sockaddr_in *sin = (struct sockaddr_in *)&addr;
     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) {
+    if (getsockname(s->s, sa, &addrlen) < 0)
+       return NULL;
+    switch(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(sin->sin_addr.s_addr));
+       PUT_16BIT_MSB_FIRST(buf+4, ntohs(sin->sin_port));
        break;
+#ifndef NO_IPV6
+    case AF_INET6:
+       *lenp = 6;
+       buf = snewn(*lenp, char);
+       if (IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) {
+           memcpy(buf, sin6->sin6_addr.s6_addr + 12, 4);
+           PUT_16BIT_MSB_FIRST(buf+4, ntohs(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;
 }
 
 /*
@@ -879,11 +972,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
@@ -893,11 +984,6 @@ void try_send(Actual_Socket s)
                 */
                s->pending_error = err;
                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) {
@@ -992,12 +1078,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
@@ -1034,6 +1117,7 @@ static int net_select_result(int fd, int event)
 #endif
            socklen_t addrlen = sizeof(ss);
            int t;  /* socket of connection */
+            int fl;
 
            memset(&ss, 0, addrlen);
            t = accept(s->s, (struct sockaddr *)&ss, &addrlen);
@@ -1041,6 +1125,10 @@ static int net_select_result(int fd, int event)
                break;
            }
 
+            fl = fcntl(t, F_GETFL);
+            if (fl != -1)
+                fcntl(t, F_SETFL, fl | O_NONBLOCK);
+
            if (s->localhost_only &&
                !sockaddr_is_loopback((struct sockaddr *)&ss)) {
                close(t);              /* someone let nonlocal through?! */
@@ -1221,14 +1309,16 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
 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->listener) {
+       rwx |= 1;                       /* read == accept */
+    } else {
+       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 */
+    }
     uxsel_set(s->s, rwx, net_select_result);
 }
 
@@ -1242,15 +1332,29 @@ int net_service_lookup(char *service)
        return 0;
 }
 
-SockAddr platform_get_x11_unix_address(int displaynum, char **canonicalname)
+SockAddr platform_get_x11_unix_address(const char *display, int displaynum,
+                                      char **canonicalname)
 {
     SockAddr ret = snew(struct SockAddr_tag);
     int n;
 
     memset(ret, 0, sizeof *ret);
     ret->family = AF_UNIX;
-    n = snprintf(ret->hostname, sizeof ret->hostname,
-                "%s%d", X11_UNIX_PATH, displaynum);
+    /*
+     * Mac OS X Leopard uses an innovative X display naming
+     * convention in which the entire display name is the path to
+     * the Unix socket, including the trailing :0 which only
+     * _looks_ like a display number. Heuristically, I think
+     * detecting this by means of a leading slash ought to be
+     * adequate.
+     */
+    if (display[0] == '/') {
+       n = snprintf(ret->hostname, sizeof ret->hostname,
+                    "%s", display);
+    } 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)
@@ -1258,9 +1362,10 @@ SockAddr platform_get_x11_unix_address(int displaynum, char **canonicalname)
     else
        *canonicalname = dupstr(ret->hostname);
 #ifndef NO_IPV6
-    ret->ais = NULL;
+    ret->ai = ret->ais = NULL;
 #else
     ret->addresses = NULL;
+    ret->curraddr = ret->naddresses = 0;
 #endif
     return ret;
 }