Support for doing DNS at the proxy end. I've invented a new type of
[u/mdw/putty] / unix / uxnet.c
index 896f473..3440d19 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,12 +57,18 @@ 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;
@@ -106,10 +114,7 @@ void sk_cleanup(void)
 
 char *error_string(int error)
 {
-    char *ret = smalloc(strlen(strerror(error)) +
-                       strlen("Network error: "));
-    sprintf(ret, "Network error: %s",strerror(error));
-    return ret;
+    return strerror(error);
 }
 
 SockAddr sk_namelookup(char *host, char **canonicalname)
@@ -195,39 +200,78 @@ SockAddr sk_namelookup(char *host, char **canonicalname)
     return ret;
 }
 
+SockAddr sk_nonamelookup(char *host)
+{
+    SockAddr ret = smalloc(sizeof(struct SockAddr_tag));
+    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)
@@ -352,6 +396,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;
 
@@ -477,7 +522,7 @@ Socket sk_new(SockAddr addr, int port, int privport, int oobinline,
     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,
@@ -537,6 +582,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
@@ -545,11 +592,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
@@ -766,8 +834,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 */