Fix for portfwd-addr-family: on Unix, when a tunnel is specified as "Auto"
[u/mdw/putty] / unix / uxnet.c
index 357fc86..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
 
-#define ipv4_is_loopback(addr) (inet_netof(addr) == IN_LOOPBACKNET)
+/*
+ * 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;
@@ -36,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
@@ -51,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;
     /*
@@ -94,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;
 }
 
@@ -233,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;
@@ -277,6 +291,32 @@ int sk_hostname_is_local(char *name)
     return !strcmp(name, "localhost");
 }
 
+#define ipv4_is_loopback(addr) \
+    (((addr).s_addr & htonl(0xff000000)) == htonl(0x7f000000))
+
+static int sockaddr_is_loopback(struct sockaddr *sa)
+{
+    struct sockaddr_in *sin;
+#ifndef NO_IPV6
+    struct sockaddr_in6 *sin6;
+#endif
+
+    switch (sa->sa_family) {
+      case AF_INET:
+       sin = (struct sockaddr_in *)sa;
+       return ipv4_is_loopback(sin->sin_addr);
+#ifndef NO_IPV6
+      case AF_INET6:
+       sin6 = (struct sockaddr_in6 *)sa;
+       return IN6_IS_ADDR_LOOPBACK(&sin6->sin6_addr);
+#endif
+      case AF_UNIX:
+       return TRUE;
+      default:
+       return FALSE;
+    }
+}
+
 int sk_address_is_local(SockAddr addr)
 {
 
@@ -284,14 +324,7 @@ int sk_address_is_local(SockAddr addr)
        return 0;                      /* we don't know; assume not */
     else {
 #ifndef NO_IPV6
-       if (addr->family == AF_INET)
-           return ipv4_is_loopback(
-               ((struct sockaddr_in *)addr->ai->ai_addr)->sin_addr);
-       else if (addr->family == AF_INET6)
-           return IN6_IS_ADDR_LOOPBACK(
-               &((struct sockaddr_in6 *)addr->ai->ai_addr)->sin6_addr);
-       else
-           return 0;
+       return sockaddr_is_loopback(addr->ai->ai_addr);
 #else
        struct in_addr a;
        assert(addr->family == AF_INET);
@@ -400,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;
 
@@ -430,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);
 
@@ -447,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));
@@ -558,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);
@@ -579,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;
@@ -608,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;
@@ -629,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
@@ -642,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;
 
     /*
@@ -658,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;
@@ -666,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.
@@ -683,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));
@@ -711,6 +772,7 @@ Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only, i
         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) {
@@ -725,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
@@ -773,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);
@@ -785,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);
@@ -793,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;
 }
 
 /*
@@ -861,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
@@ -875,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) {
@@ -974,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
@@ -1009,17 +1110,27 @@ 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);
+#ifdef NO_IPV6
+           struct sockaddr_in ss;
+#else
+           struct sockaddr_storage ss;
+#endif
+           socklen_t addrlen = sizeof(ss);
            int t;  /* socket of connection */
+            int fl;
 
-           memset(&isa, 0, sizeof(struct sockaddr_in));
-           t = accept(s->s,(struct sockaddr *)&isa,(socklen_t *) &addrlen);
+           memset(&ss, 0, addrlen);
+           t = accept(s->s, (struct sockaddr *)&ss, &addrlen);
            if (t < 0) {
                break;
            }
 
-           if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) {
+            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?! */
            } else if (plug_accepting(s->plug, t)) {
                close(t);              /* denied or error */
@@ -1198,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);
 }
 
@@ -1219,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)
@@ -1235,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;
 }