Can now save and load settings under Unix.
[u/mdw/putty] / unix / uxnet.c
index 44b09c1..0795d4d 100644 (file)
@@ -21,6 +21,8 @@
 #include "network.h"
 #include "tree234.h"
 
+#define ipv4_is_loopback(addr) (inet_netof(addr) == IN_LOOPBACKNET)
+
 struct Socket_tag {
     struct socket_function_table *fn;
     /* the above variable absolutely *must* be the first in this structure */
@@ -55,16 +57,24 @@ typedef struct Socket_tag *Actual_Socket;
 
 struct SockAddr_tag {
     char *error;
-    /* address family this belongs to, AF_INET for IPv4, AF_INET6 for IPv6. */
+    /*
+     * Which address family this address belongs to. AF_INET for
+     * IPv4; AF_INET6 for IPv6; AF_UNSPEC indicates that name
+     * resolution has not been done and a simple host name is held
+     * in this SockAddr structure.
+     */
     int family;
     unsigned long address;            /* Address IPv4 style. */
 #ifdef IPV6
     struct addrinfo *ai;              /* Address IPv6 style. */
 #endif
+    char hostname[512];                       /* Store an unresolved host name. */
 };
 
 static tree234 *sktree;
 
+static void uxsel_tell(Actual_Socket s);
+
 static int cmpfortree(void *av, void *bv)
 {
     Actual_Socket a = (Actual_Socket) av, b = (Actual_Socket) bv;
@@ -106,82 +116,12 @@ 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";
-    }
+    return strerror(error);
 }
 
-SockAddr sk_namelookup(char *host, char **canonicalname)
+SockAddr sk_namelookup(const char *host, char **canonicalname)
 {
-    SockAddr ret = smalloc(sizeof(struct SockAddr_tag));
+    SockAddr ret = snew(struct SockAddr_tag);
     unsigned long a;
     struct hostent *h = NULL;
     char realhost[8192];
@@ -257,44 +197,84 @@ SockAddr sk_namelookup(char *host, char **canonicalname)
     }
     ret->address = ntohl(a);
     realhost[lenof(realhost)-1] = '\0';
-    *canonicalname = smalloc(1+strlen(realhost));
+    *canonicalname = snewn(1+strlen(realhost), char);
     strcpy(*canonicalname, realhost);
     return ret;
 }
 
+SockAddr sk_nonamelookup(const char *host)
+{
+    SockAddr ret = snew(struct SockAddr_tag);
+    ret->error = NULL;
+    ret->family = AF_UNSPEC;
+    strncpy(ret->hostname, host, lenof(ret->hostname));
+    ret->hostname[lenof(ret->hostname)-1] = '\0';
+    return ret;
+}
+
 void sk_getaddr(SockAddr addr, char *buf, int buflen)
 {
 #ifdef IPV6
-    if (addr->family == AF_INET) {
+    if (addr->family == AF_INET6) {
+       FIXME; /* I don't know how to get a text form of an IPv6 address. */
+    } else
 #endif
+    if (addr->family == AF_INET) {
        struct in_addr a;
        a.s_addr = htonl(addr->address);
        strncpy(buf, inet_ntoa(a), buflen);
-#ifdef IPV6
+       buf[buflen-1] = '\0';
     } else {
-       FIXME; /* I don't know how to get a text form of an IPv6 address. */
+       assert(addr->family == AF_UNSPEC);
+       strncpy(buf, addr->hostname, buflen);
+       buf[buflen-1] = '\0';
     }
+}
+
+int sk_hostname_is_local(char *name)
+{
+    return !strcmp(name, "localhost");
+}
+
+int sk_address_is_local(SockAddr addr)
+{
+#ifdef IPV6
+    if (addr->family == AF_INET6) {
+       FIXME;  /* someone who can compile for IPV6 had better do this bit */
+    } else
 #endif
+    if (addr->family == AF_INET) {
+       struct in_addr a;
+       a.s_addr = htonl(addr->address);
+       return ipv4_is_loopback(a);
+    } else {
+       assert(addr->family == AF_UNSPEC);
+       return 0;                      /* we don't know; assume not */
+    }
 }
 
 int sk_addrtype(SockAddr addr)
 {
-    return (addr->family == AF_INET ? ADDRTYPE_IPV4 : ADDRTYPE_IPV6);
+    return (addr->family == AF_INET ? ADDRTYPE_IPV4 :
+#ifdef IPV6
+           addr->family == AF_INET6 ? ADDRTYPE_IPV6 :
+#endif
+           ADDRTYPE_NAME);
 }
 
 void sk_addrcopy(SockAddr addr, char *buf)
 {
+    assert(addr->family != AF_UNSPEC);
 #ifdef IPV6
-    if (addr->family == AF_INET) {
+    if (addr->family == AF_INET6) {
+       memcpy(buf, (char*) addr->ai, 16);
+    } else
 #endif
+    if (addr->family == AF_INET) {
        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);
     }
-#endif
 }
 
 void sk_addr_free(SockAddr addr)
@@ -320,34 +300,34 @@ static void sk_tcp_flush(Socket s)
 }
 
 static void sk_tcp_close(Socket s);
-static int sk_tcp_write(Socket s, char *data, int len);
-static int sk_tcp_write_oob(Socket s, char *data, int len);
+static int sk_tcp_write(Socket s, const char *data, int len);
+static int sk_tcp_write_oob(Socket s, const char *data, int len);
 static void sk_tcp_set_private_ptr(Socket s, void *ptr);
 static void *sk_tcp_get_private_ptr(Socket s);
 static void sk_tcp_set_frozen(Socket s, int is_frozen);
 static char *sk_tcp_socket_error(Socket s);
 
+static struct socket_function_table tcp_fn_table = {
+    sk_tcp_plug,
+    sk_tcp_close,
+    sk_tcp_write,
+    sk_tcp_write_oob,
+    sk_tcp_flush,
+    sk_tcp_set_private_ptr,
+    sk_tcp_get_private_ptr,
+    sk_tcp_set_frozen,
+    sk_tcp_socket_error
+};
+
 Socket sk_register(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
-    };
-
     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);
@@ -369,6 +349,7 @@ Socket sk_register(void *sock, Plug plug)
 
     ret->oobinline = 0;
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
@@ -377,18 +358,6 @@ Socket sk_register(void *sock, Plug plug)
 Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
              int nodelay, Plug plug)
 {
-    static struct socket_function_table fn_table = {
-       sk_tcp_plug,
-       sk_tcp_close,
-       sk_tcp_write,
-       sk_tcp_write_oob,
-       sk_tcp_flush,
-       sk_tcp_set_private_ptr,
-       sk_tcp_get_private_ptr,
-       sk_tcp_set_frozen,
-       sk_tcp_socket_error
-    };
-
     int s;
 #ifdef IPV6
     struct sockaddr_in6 a6;
@@ -401,8 +370,8 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     /*
      * Create Socket structure.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
@@ -419,6 +388,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     /*
      * Open socket.
      */
+    assert(addr->family != AF_UNSPEC);
     s = socket(addr->family, SOCK_STREAM, 0);
     ret->s = s;
 
@@ -511,6 +481,11 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        a.sin_port = htons((short) port);
     }
 
+    {
+       int i = 1;
+       ioctl(s, FIONBIO, &i);
+    }
+
     if ((
 #ifdef IPV6
            connect(s, ((addr->family == AF_INET6) ?
@@ -520,13 +495,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
            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 ) {
+       if ( errno != EINPROGRESS ) {
            ret->error = error_string(errno);
            return (Socket) ret;
        }
@@ -539,25 +508,14 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
        ret->writable = 1;
     }
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
 }
 
-Socket sk_newlistener(int port, Plug plug, int local_host_only)
+Socket sk_newlistener(char *srcaddr, int port, Plug plug, int local_host_only)
 {
-    static struct socket_function_table fn_table = {
-       sk_tcp_plug,
-       sk_tcp_close,
-       sk_tcp_write,
-       sk_tcp_write_oob,
-       sk_tcp_flush,
-       sk_tcp_set_private_ptr,
-       sk_tcp_get_private_ptr,
-       sk_tcp_set_frozen,
-       sk_tcp_socket_error
-    };
-
     int s;
 #ifdef IPV6
     struct sockaddr_in6 a6;
@@ -571,8 +529,8 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
     /*
      * Create Socket structure.
      */
-    ret = smalloc(sizeof(struct Socket_tag));
-    ret->fn = &fn_table;
+    ret = snew(struct Socket_tag);
+    ret->fn = &tcp_fn_table;
     ret->error = NULL;
     ret->plug = plug;
     bufchain_init(&ret->output_data);
@@ -604,6 +562,8 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
     if (addr->family == AF_INET6) {
        memset(&a6, 0, sizeof(a6));
        a6.sin6_family = AF_INET6;
+       /* FIXME: srcaddr is ignored for IPv6, because I (SGT) don't
+        * know how to do it. :-) */
        if (local_host_only)
            a6.sin6_addr = in6addr_loopback;
        else
@@ -612,11 +572,32 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
     } else
 #endif
     {
+       int got_addr = 0;
        a.sin_family = AF_INET;
-       if (local_host_only)
-           a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
-       else
-           a.sin_addr.s_addr = htonl(INADDR_ANY);
+
+       /*
+        * Bind to source address. First try an explicitly
+        * specified one...
+        */
+       if (srcaddr) {
+           a.sin_addr.s_addr = inet_addr(srcaddr);
+           if (a.sin_addr.s_addr != INADDR_NONE) {
+               /* Override localhost_only with specified listen addr. */
+               ret->localhost_only = ipv4_is_loopback(a.sin_addr);
+               got_addr = 1;
+           }
+       }
+
+       /*
+        * ... and failing that, go with one of the standard ones.
+        */
+       if (!got_addr) {
+           if (local_host_only)
+               a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+           else
+               a.sin_addr.s_addr = htonl(INADDR_ANY);
+       }
+
        a.sin_port = htons((short)port);
     }
 #ifdef IPV6
@@ -646,6 +627,7 @@ Socket sk_newlistener(int port, Plug plug, int local_host_only)
        return (Socket) ret;
     }
 
+    uxsel_tell(ret);
     add234(sktree, ret);
 
     return (Socket) ret;
@@ -655,11 +637,41 @@ static void sk_tcp_close(Socket sock)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
+    uxsel_del(s->s);
     del234(sktree, s);
     close(s->s);
     sfree(s);
 }
 
+int sk_getxdmdata(void *sock, unsigned long *ip, int *port)
+{
+    Actual_Socket s = (Actual_Socket) sock;
+    struct sockaddr_in addr;
+    socklen_t addrlen;
+
+    /*
+     * We must check that this socket really _is_ an Actual_Socket.
+     */
+    if (s->fn != &tcp_fn_table)
+       return 0;                      /* failure */
+
+    /*
+     * If we ever implement connecting to a local X server through
+     * a Unix socket, we return 0xFFFFFFFF for the IP address and
+     * our current pid for the port. Bizarre, but such is life.
+     */
+
+    addrlen = sizeof(addr);
+    if (getsockname(s->s, (struct sockaddr *)&addr, &addrlen) < 0 ||
+       addr.sin_family != AF_INET)
+       return 0;
+
+    *ip = ntohl(addr.sin_addr.s_addr);
+    *port = ntohs(addr.sin_port);
+
+    return 1;
+}
+
 /*
  * The function which tries to send on a socket once it's deemed
  * writable.
@@ -723,9 +735,10 @@ void try_send(Actual_Socket s)
            }
        }
     }
+    uxsel_tell(s);
 }
 
-static int sk_tcp_write(Socket sock, char *buf, int len)
+static int sk_tcp_write(Socket sock, const char *buf, int len)
 {
     Actual_Socket s = (Actual_Socket) sock;
 
@@ -743,7 +756,7 @@ static int sk_tcp_write(Socket sock, char *buf, int len)
     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;
 
@@ -764,7 +777,7 @@ static int sk_tcp_write_oob(Socket sock, char *buf, int len)
     return s->sending_oob;
 }
 
-int select_result(int fd, int event)
+static int net_select_result(int fd, int event)
 {
     int ret;
     int err;
@@ -780,11 +793,6 @@ int select_result(int fd, int event)
     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) {
            /*
@@ -810,10 +818,12 @@ int select_result(int fd, int event)
 
        /*
         * If we reach here, this is an oobinline socket, which
-        * means we should set s->oobpending and then fall through
-        * to the read case.
+        * means we should set s->oobpending and then deal with it
+        * when we get called for the readability event (which
+        * should also occur).
         */
        s->oobpending = TRUE;
+        break;
       case 1:                         /* readable; also acceptance */
        if (s->listener) {
            /*
@@ -831,8 +841,7 @@ int select_result(int fd, int event)
                break;
            }
 
-           if (s->localhost_only &&
-               ntohl(isa.sin_addr.s_addr) != INADDR_LOOPBACK) {
+           if (s->localhost_only && !ipv4_is_loopback(isa.sin_addr)) {
                close(t);              /* someone let nonlocal through?! */
            } else if (plug_accepting(s->plug, (void*)t)) {
                close(t);              /* denied or error */
@@ -864,7 +873,7 @@ int select_result(int fd, int event)
        } else
            atmark = 1;
 
-       ret = recv(s->s, buf, sizeof(buf), 0);
+       ret = recv(s->s, buf, s->oobpending ? 1 : sizeof(buf), 0);
        noise_ultralight(ret);
        if (ret < 0) {
            if (errno == EWOULDBLOCK) {
@@ -880,7 +889,15 @@ int select_result(int fd, int event)
        }
        break;
       case 2:                         /* writable */
-       {
+       if (!s->connected) {
+           /*
+            * select() reports a socket as _writable_ when an
+            * asynchronous connection is completed.
+            */
+           s->connected = s->writable = 1;
+           uxsel_tell(s);
+           break;
+       } else {
            int bufsize_before, bufsize_after;
            s->writable = 1;
            bufsize_before = s->sending_oob + bufchain_size(&s->output_data);
@@ -974,42 +991,21 @@ static void sk_tcp_set_frozen(Socket sock, int is_frozen)
        recv(s->s, &c, 1, MSG_PEEK);
     }
     s->frozen_readable = 0;
+    uxsel_tell(s);
 }
 
-/*
- * For Unix select()-based frontends: enumerate all sockets
- * currently active, and state whether we currently wish to receive
- * select events on them for reading, writing and exceptional
- * status.
- */
-static void set_rwx(Actual_Socket s, int *rwx)
+static void uxsel_tell(Actual_Socket s)
 {
-    int val = 0;
+    int rwx = 0;
+    if (!s->connected)
+       rwx |= 2;                      /* write == connect */
     if (s->connected && !s->frozen)
-       val |= 1 | 4;                  /* read, except */
+       rwx |= 1 | 4;                  /* read, except */
     if (bufchain_size(&s->output_data))
-       val |= 2;                      /* write */
+       rwx |= 2;                      /* write */
     if (s->listener)
-       val |= 1;                      /* read == accept */
-    *rwx = val;
-}
-
-int first_socket(int *state, int *rwx)
-{
-    Actual_Socket s;
-    *state = 0;
-    s = index234(sktree, (*state)++);
-    if (s)
-       set_rwx(s, rwx);
-    return s ? s->s : -1;
-}
-
-int next_socket(int *state, int *rwx)
-{
-    Actual_Socket s = index234(sktree, (*state)++);
-    if (s)
-       set_rwx(s, rwx);
-    return s ? s->s : -1;
+       rwx |= 1;                      /* read == accept */
+    uxsel_set(s->s, rwx, net_select_result);
 }
 
 int net_service_lookup(char *service)