Browse Source

Merge branch 'thirdparty'

# Conflicts:

#	source/putty/proxy/proxy.c
#	source/putty/windows/network.c

Source commit: 561d633766c4a1a191c50143b12b1aa2de9a3471
Martin Prikryl 3 years ago
parent
commit
52e2db4e1c

+ 85 - 9
source/putty/proxy/http_p.c

@@ -72,7 +72,8 @@ typedef struct HttpProxyNegotiator {
     uint32_t nonce_count;
     prompts_t *prompts;
     int username_prompt_index, password_prompt_index;
-    size_t content_length;
+    size_t content_length, chunk_length;
+    bool chunked_transfer;
     ProxyNegotiator pn;
 } HttpProxyNegotiator;
 
@@ -151,7 +152,9 @@ static void proxy_http_free(ProxyNegotiator *pn)
 #define HTTP_HEADER_LIST(X) \
     X(HDR_CONNECTION, "Connection") \
     X(HDR_CONTENT_LENGTH, "Content-Length") \
+    X(HDR_TRANSFER_ENCODING, "Transfer-Encoding") \
     X(HDR_PROXY_AUTHENTICATE, "Proxy-Authenticate") \
+    X(HDR_PROXY_CONNECTION, "Proxy-Connection") \
     /* end of list */
 
 typedef enum HttpHeader {
@@ -322,7 +325,7 @@ static HttpAuthDetails *parse_http_auth_header(HttpProxyNegotiator *s)
                 d->hash_username = !stricmp(s->token->s, "true");
             } else if (!stricmp(s->token->s, "algorithm")) {
                 if (!get_separator(s, '=') ||
-                    !get_token(s))
+                    (!get_token(s) && !get_quoted_string(s)))
                     return auth_error(d, "parse error in Digest algorithm "
                                       "field");
                 { // WINSCP
@@ -467,6 +470,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
         crReturnV;
 
         s->content_length = 0;
+        s->chunked_transfer = false;
         s->connection_close = false;
 
         /*
@@ -486,7 +490,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
                 crStopV;
             }
 
-            if (maj_ver < 1 && (maj_ver == 1 && min_ver < 1)) {
+            if (maj_ver < 1 || (maj_ver == 1 && min_ver < 1)) {
                 /* Before HTTP/1.1, connections close by default */
                 s->connection_close = true;
             }
@@ -538,7 +542,25 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
                 if (!get_token(s))
                     continue;
                 s->content_length = strtoumax(s->token->s, NULL, 10);
-            } else if (hdr == HDR_CONNECTION) {
+            } else if (hdr == HDR_TRANSFER_ENCODING) {
+                /*
+                 * The Transfer-Encoding header value should be a
+                 * comma-separated list of keywords including
+                 * "chunked", "deflate" and "gzip". We parse it in the
+                 * most superficial way, by just looking for "chunked"
+                 * and ignoring everything else.
+                 *
+                 * It's OK to do that because we're not actually
+                 * _using_ the error document - we only have to skip
+                 * over it to find the end of the HTTP response. So we
+                 * don't care if it's gzipped or not.
+                 */
+                while (get_token(s)) {
+                    if (!stricmp(s->token->s, "chunked"))
+                        s->chunked_transfer = true;
+                }
+            } else if (hdr == HDR_CONNECTION ||
+                       hdr == HDR_PROXY_CONNECTION) {
                 if (!get_token(s))
                     continue;
                 if (!stricmp(s->token->s, "close"))
@@ -593,8 +615,62 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
         } while (s->header->len > 0);
 
         /* Read and ignore the entire response document */
-        crMaybeWaitUntilV(bufchain_try_consume(
-                              pn->input, s->content_length));
+        if (!s->chunked_transfer) {
+            /* Simple approach: read exactly Content-Length bytes */
+            crMaybeWaitUntilV(bufchain_try_consume(
+                                  pn->input, s->content_length));
+        } else {
+            /* Chunked transfer: read a sequence of
+             * <hex length>\r\n<data>\r\n chunks, terminating in one with
+             * zero length */
+            do {
+                /*
+                 * Expect a chunk length
+                 */
+                s->chunk_length = 0;
+                while (true) {
+                    char c;
+                    crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                          pn->input, &c, 1));
+                    if (c == '\r') {
+                        continue;
+                    } else if (c == '\n') {
+                        break;
+                    } else if ('0' <= c && c <= '9') {
+                        s->chunk_length = s->chunk_length*16 + (c-'0');
+                    } else if ('A' <= c && c <= 'F') {
+                        s->chunk_length = s->chunk_length*16 + (c-'A'+10);
+                    } else if ('a' <= c && c <= 'f') {
+                        s->chunk_length = s->chunk_length*16 + (c-'a'+10);
+                    } else {
+                        pn->error = dupprintf(
+                            "Received bad character 0x%02X in chunk length "
+                            "during HTTP chunked transfer encoding",
+                            (unsigned)(unsigned char)c);
+                        crStopV;
+                    }
+                }
+
+                /*
+                 * Expect that many bytes of chunked data
+                 */
+                crMaybeWaitUntilV(bufchain_try_consume(
+                                      pn->input, s->chunk_length));
+
+                /* Now expect \r\n */
+                {
+                    char buf[2];
+                    crMaybeWaitUntilV(bufchain_try_fetch_consume(
+                                          pn->input, buf, 2));
+                    if (memcmp(buf, "\r\n", 2)) {
+                        pn->error = dupprintf(
+                            "Missing CRLF after chunk "
+                            "during HTTP chunked transfer encoding");
+                        crStopV;
+                    }
+                }
+            } while (s->chunk_length);
+        }
 
         if (200 <= s->http_status && s->http_status < 300) {
             /* Any 2xx HTTP response means we're done */
@@ -603,9 +679,9 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
             /* 407 is Proxy Authentication Required, which we may be
              * able to do something about. */
             if (s->connection_close) {
-                pn->error = dupprintf("HTTP proxy closed connection after "
-                                      "asking for authentication");
-                crStopV;
+                /* If we got 407 + connection closed, reconnect before
+                 * sending our next request. */
+                pn->reconnect = true;
             }
 
             /* If the best we can do is report some kind of error from

+ 21 - 4
source/putty/proxy/proxy.c

@@ -99,6 +99,7 @@ static void sk_proxy_close (Socket *s)
     ProxySocket *ps = container_of(s, ProxySocket, sock);
 
     sk_close(ps->sub_socket);
+    sk_addr_free(ps->proxy_addr);
     sk_addr_free(ps->remote_addr);
     proxy_negotiator_cleanup(ps);
     bufchain_clear(&ps->output_from_negotiator);
@@ -223,6 +224,16 @@ static void proxy_negotiate(ProxySocket *ps)
         return;
     }
 
+    if (ps->pn->reconnect) {
+        sk_close(ps->sub_socket);
+        SockAddr *proxy_addr = sk_addr_dup(ps->proxy_addr);
+        ps->sub_socket = sk_new(proxy_addr, ps->proxy_port,
+                                ps->proxy_privport, ps->proxy_oobinline,
+                                ps->proxy_nodelay, ps->proxy_keepalive,
+                                &ps->plugimpl);
+        ps->pn->reconnect = false;
+    }
+
     while (bufchain_size(&ps->output_from_negotiator)) {
         ptrlen data = bufchain_prefix(&ps->output_from_negotiator);
         sk_write(ps->sub_socket, data.ptr, data.len);
@@ -605,10 +616,16 @@ Socket *new_connection(SockAddr *addr, const char *hostname,
         /* create the actual socket we will be using,
          * connected to our proxy server and port.
          */
-        ps->sub_socket = sk_new(proxy_addr,
-                                conf_get_int(conf, CONF_proxy_port),
-                                privport, oobinline,
-                                nodelay, keepalive, &ps->plugimpl,
+        ps->proxy_addr = sk_addr_dup(proxy_addr);
+        ps->proxy_port = conf_get_int(conf, CONF_proxy_port);
+        ps->proxy_privport = privport;
+        ps->proxy_oobinline = oobinline;
+        ps->proxy_nodelay = nodelay;
+        ps->proxy_keepalive = keepalive;
+        ps->sub_socket = sk_new(proxy_addr, ps->proxy_port,
+                                ps->proxy_privport, ps->proxy_oobinline,
+                                ps->proxy_nodelay, ps->proxy_keepalive,
+                                &ps->plugimpl,
                                 #ifdef WINSCP
                                 conf_get_int(conf, CONF_connect_timeout), conf_get_int(conf, CONF_sndbuf),
                                 conf_get_str(conf, CONF_srcaddr)

+ 10 - 0
source/putty/proxy/proxy.h

@@ -22,6 +22,11 @@ struct ProxySocket {
     SockAddr *remote_addr;
     int remote_port;
 
+    /* Parameters needed to make further connections to the proxy */
+    SockAddr *proxy_addr;
+    int proxy_port;
+    bool proxy_privport, proxy_oobinline, proxy_nodelay, proxy_keepalive;
+
     bufchain pending_output_data;
     bufchain pending_oob_output_data;
     bufchain pending_input_data;
@@ -68,6 +73,11 @@ struct ProxyNegotiator {
 
     /* Set to report user abort during proxy negotiation.  */
     bool aborted;
+
+    /* Set to request the centralised code to make a fresh connection
+     * to the proxy server, e.g. because an HTTP proxy slammed the
+     * connection shut after sending 407 Proxy Auth Required. */
+    bool reconnect;
 };
 
 #ifndef __cplusplus

+ 1 - 1
source/putty/proxy/socks5.c

@@ -70,7 +70,7 @@ static void proxy_socks5_free(ProxyNegotiator *pn)
     strbuf_free(s->password);
     if (s->prompts)
         free_prompts(s->prompts);
-    smemclr(s, sizeof(s));
+    smemclr(s, sizeof(*s));
     sfree(s);
 }
 

+ 195 - 127
source/putty/windows/network.c

@@ -24,6 +24,10 @@
 #endif
 #include <ws2tcpip.h>
 
+#if HAVE_AFUNIX_H
+#include <afunix.h>
+#endif
+
 #ifndef NO_IPV6
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -83,12 +87,28 @@ struct NetSocket {
     Socket sock;
 };
 
+/*
+ * Top-level discriminator for SockAddr.
+ *
+ * UNRESOLVED means a host name not yet put through DNS; IP means a
+ * resolved IP address (or list of them); UNIX indicates the AF_UNIX
+ * network family (which Windows also has); NAMEDPIPE indicates that
+ * this SockAddr is phony, holding a Windows named pipe pathname
+ * instead of any address WinSock can understand.
+ */
+typedef enum SuperFamily {
+    UNRESOLVED,
+    IP,
+#if HAVE_AFUNIX_H
+    UNIX,
+#endif
+    NAMEDPIPE
+} SuperFamily;
+
 struct SockAddr {
     int refcount;
     char *error;
-    bool resolved;
-    bool namedpipe; /* indicates that this SockAddr is phony, holding a Windows
-                     * named pipe pathname instead of a network address */
+    SuperFamily superfamily;
 #ifndef NO_IPV6
     struct addrinfo *ais;              /* Addresses IPv6 style. */
 #endif
@@ -99,18 +119,27 @@ struct SockAddr {
 
 /*
  * 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.
+ * AF_INET6 for IPv6; AF_UNIX for Unix-domain sockets; AF_UNSPEC
+ * indicates that name resolution has not been done and a simple host
+ * name is held in this SockAddr structure.
  */
+static inline int sockaddr_family(SockAddr *addr, SockAddrStep step)
+{
+    switch (addr->superfamily) {
+      case IP:
 #ifndef NO_IPV6
-#define SOCKADDR_FAMILY(addr, step) \
-    (!(addr)->resolved ? AF_UNSPEC : \
-     (step).ai ? (step).ai->ai_family : AF_INET)
-#else
-#define SOCKADDR_FAMILY(addr, step) \
-    (!(addr)->resolved ? AF_UNSPEC : AF_INET)
+        if (step.ai)
+            return step.ai->ai_family;
+#endif
+        return AF_INET;
+#if HAVE_AFUNIX_H
+      case UNIX:
+        return AF_UNIX;
 #endif
+      default:
+        return AF_UNSPEC;
+    }
+}
 
 /*
  * Start a SockAddrStep structure to step through multiple
@@ -462,9 +491,8 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
 #ifndef NO_IPV6
     ret->ais = NULL;
 #endif
-    ret->namedpipe = false;
     ret->addresses = NULL;
-    ret->resolved = false;
+    ret->superfamily = UNRESOLVED;
     ret->refcount = 1;
     *realhost = '\0';
 
@@ -488,7 +516,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
             }
             if (err == 0)
             {
-                ret->resolved = true;
+                ret->superfamily = IP;
             }
         } else
 #endif
@@ -498,12 +526,12 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
              * (NOTE: we don't use gethostbyname as a fallback!)
              */
             if ( (h = p_gethostbyname(host)) )
-                ret->resolved = true;
+                ret->superfamily = IP;
             else
                 err = p_WSAGetLastError();
         }
 
-        if (!ret->resolved) {
+        if (ret->superfamily != IP) {
             ret->error = (err == WSAENETDOWN ? "Network is down" :
                           err == WSAHOST_NOT_FOUND ? "Host does not exist" :
                           err == WSATRY_AGAIN ? "Host not found" :
@@ -556,7 +584,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
         ret->addresses = snewn(1, unsigned long);
         ret->naddresses = 1;
         ret->addresses[0] = p_ntohl(a);
-        ret->resolved = true;
+        ret->superfamily = IP;
         strncpy(realhost, host, sizeof(realhost));
     }
     realhost[lenof(realhost)-1] = '\0';
@@ -564,40 +592,39 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
     return ret;
 }
 
-SockAddr *sk_nonamelookup(const char *host)
+static SockAddr *sk_special_addr(SuperFamily superfamily, const char *name)
 {
     SockAddr *ret = snew(SockAddr);
     ret->error = NULL;
-    ret->resolved = false;
+    ret->superfamily = superfamily;
 #ifndef NO_IPV6
     ret->ais = NULL;
 #endif
-    ret->namedpipe = false;
     ret->addresses = NULL;
     ret->naddresses = 0;
     ret->refcount = 1;
-    strncpy(ret->hostname, host, lenof(ret->hostname));
+    strncpy(ret->hostname, name, lenof(ret->hostname));
     ret->hostname[lenof(ret->hostname)-1] = '\0';
     return ret;
 }
 
+SockAddr *sk_nonamelookup(const char *host)
+{
+    return sk_special_addr(UNRESOLVED, host);
+}
+
 SockAddr *sk_namedpipe_addr(const char *pipename)
 {
-    SockAddr *ret = snew(SockAddr);
-    ret->error = NULL;
-    ret->resolved = false;
-#ifndef NO_IPV6
-    ret->ais = NULL;
-#endif
-    ret->namedpipe = true;
-    ret->addresses = NULL;
-    ret->naddresses = 0;
-    ret->refcount = 1;
-    strncpy(ret->hostname, pipename, lenof(ret->hostname));
-    ret->hostname[lenof(ret->hostname)-1] = '\0';
-    return ret;
+    return sk_special_addr(NAMEDPIPE, pipename);
 }
 
+#if HAVE_AFUNIX_H
+SockAddr *sk_unix_addr(const char *sockpath)
+{
+    return sk_special_addr(UNIX, sockpath);
+}
+#endif
+
 static bool sk_nextaddr(SockAddr *addr, SockAddrStep *step)
 {
 #ifndef NO_IPV6
@@ -639,7 +666,7 @@ void sk_getaddr(SockAddr *addr, char *buf, int buflen)
         }
     } else
 #endif
-    if (SOCKADDR_FAMILY(addr, step) == AF_INET) {
+    if (sockaddr_family(addr, step) == AF_INET) {
         struct in_addr a;
         assert(addr->addresses && step.curraddr < addr->naddresses);
         a.s_addr = p_htonl(addr->addresses[step.curraddr]);
@@ -670,7 +697,7 @@ static SockAddr sk_extractaddr_tmp(
 #ifndef NO_IPV6
     toret.ais = step->ai;
 #endif
-    if (SOCKADDR_FAMILY(addr, *step) == AF_INET
+    if (sockaddr_family(addr, *step) == AF_INET
 #ifndef NO_IPV6
         && !toret.ais
 #endif
@@ -682,7 +709,11 @@ static SockAddr sk_extractaddr_tmp(
 
 bool sk_addr_needs_port(SockAddr *addr)
 {
-    return !addr->namedpipe;
+    return addr->superfamily != NAMEDPIPE
+#if HAVE_AFUNIX_H
+        && addr->superfamily != UNIX
+#endif
+        ;
 }
 
 bool sk_hostname_is_local(const char *name)
@@ -730,7 +761,7 @@ bool sk_address_is_local(SockAddr *addr)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
 #ifndef NO_IPV6
     if (family == AF_INET6) {
@@ -766,7 +797,7 @@ int sk_addrtype(SockAddr *addr)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
     return (family == AF_INET ? ADDRTYPE_IPV4 :
 #ifndef NO_IPV6
@@ -780,7 +811,7 @@ void sk_addrcopy(SockAddr *addr, char *buf)
     SockAddrStep step;
     int family;
     START_STEP(addr, step);
-    family = SOCKADDR_FAMILY(addr, step);
+    family = sockaddr_family(addr, step);
 
     assert(family != AF_UNSPEC);
 #ifndef NO_IPV6
@@ -941,7 +972,7 @@ static DWORD try_connect(NetSocket *sock,
     /*
      * Open socket.
      */
-    family = SOCKADDR_FAMILY(sock->addr, sock->step);
+    family = sockaddr_family(sock->addr, sock->step);
 
     /*
      * Remove the socket from the tree before we overwrite its
@@ -1226,21 +1257,26 @@ Socket *sk_new(SockAddr *addr, int port, bool privport, bool oobinline,
     return &ret->sock;
 }
 
-Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
-                       bool local_host_only, int orig_address_family)
+Socket *sk_newlistener_internal(const char *srcaddr, int port, Plug *plug,
+                                bool local_host_only, int orig_address_family)
 {
     SOCKET s;
+    SOCKADDR_IN a;
 #ifndef NO_IPV6
     SOCKADDR_IN6 a6;
 #endif
-    SOCKADDR_IN a;
+#if HAVE_AFUNIX_H
+    SOCKADDR_UN au;
+#endif
+    struct sockaddr *bindaddr;
+    unsigned bindsize;
 
     DWORD err;
     const char *errstr;
     NetSocket *ret;
     int retcode;
 
-    int address_family;
+    int address_family = orig_address_family;
 
     /*
      * Create NetSocket structure.
@@ -1260,16 +1296,6 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     ret->parent = ret->child = NULL;
     ret->addr = NULL;
 
-    /*
-     * Translate address_family from platform-independent constants
-     * into local reality.
-     */
-    address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
-#ifndef NO_IPV6
-                      orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
-#endif
-                      AF_UNSPEC);
-
     /*
      * Our default, if passed the `don't care' value
      * ADDRTYPE_UNSPEC, is to listen on IPv4. If IPv6 is supported,
@@ -1295,85 +1321,100 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
 
     ret->oobinline = false;
 
+#if HAVE_AFUNIX_H
+    if (address_family != AF_UNIX)
+#endif
     {
         BOOL on = true;
         p_setsockopt(s, SOL_SOCKET, SO_EXCLUSIVEADDRUSE,
                      (const char *)&on, sizeof(on));
     }
 
+    switch (address_family) {
 #ifndef NO_IPV6
-        if (address_family == AF_INET6) {
-            memset(&a6, 0, sizeof(a6));
-            a6.sin6_family = AF_INET6;
-            if (local_host_only)
-                a6.sin6_addr = in6addr_loopback;
-            else
-                a6.sin6_addr = in6addr_any;
-            if (srcaddr != NULL && p_getaddrinfo) {
-                struct addrinfo hints;
-                struct addrinfo *ai;
-                int err;
-
-                memset(&hints, 0, sizeof(hints));
-                hints.ai_family = AF_INET6;
-                hints.ai_flags = 0;
-                {
-                    /* strip [] on IPv6 address literals */
-                    char *trimmed_addr = host_strduptrim(srcaddr);
-                    err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
-                    sfree(trimmed_addr);
-                }
-                if (err == 0 && ai->ai_family == AF_INET6) {
-                    a6.sin6_addr =
-                        ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
-                }
-            }
-            a6.sin6_port = p_htons(port);
-        } else
-#endif
-        {
-            bool got_addr = false;
-            a.sin_family = AF_INET;
+      case AF_INET6: {
+        memset(&a6, 0, sizeof(a6));
+        a6.sin6_family = AF_INET6;
+        if (local_host_only)
+            a6.sin6_addr = in6addr_loopback;
+        else
+            a6.sin6_addr = in6addr_any;
+        if (srcaddr != NULL && p_getaddrinfo) {
+            struct addrinfo hints;
+            struct addrinfo *ai;
+            int err;
 
-            /*
-             * Bind to source address. First try an explicitly
-             * specified one...
-             */
-            if (srcaddr) {
-                a.sin_addr.s_addr = p_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 = true;
-                }
+            memset(&hints, 0, sizeof(hints));
+            hints.ai_family = AF_INET6;
+            hints.ai_flags = 0;
+            {
+                /* strip [] on IPv6 address literals */
+                char *trimmed_addr = host_strduptrim(srcaddr);
+                err = p_getaddrinfo(trimmed_addr, NULL, &hints, &ai);
+                sfree(trimmed_addr);
+            }
+            if (err == 0 && ai->ai_family == AF_INET6) {
+                a6.sin6_addr =
+                    ((struct sockaddr_in6 *)ai->ai_addr)->sin6_addr;
             }
+        }
+        a6.sin6_port = p_htons(port);
+        bindaddr = (struct sockaddr *)&a6;
+        bindsize = sizeof(a6);
+        break;
+      }
+#endif
+      case AF_INET: {
+        bool got_addr = false;
+        a.sin_family = AF_INET;
 
-            /*
-             * ... and failing that, go with one of the standard ones.
-             */
-            if (!got_addr) {
-                if (local_host_only)
-                    a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
-                else
-                    a.sin_addr.s_addr = p_htonl(INADDR_ANY);
+        /*
+         * Bind to source address. First try an explicitly
+         * specified one...
+         */
+        if (srcaddr) {
+            a.sin_addr.s_addr = p_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 = true;
             }
+        }
 
-            a.sin_port = p_htons((short)port);
+        /*
+         * ... and failing that, go with one of the standard ones.
+         */
+        if (!got_addr) {
+            if (local_host_only)
+                a.sin_addr.s_addr = p_htonl(INADDR_LOOPBACK);
+            else
+                a.sin_addr.s_addr = p_htonl(INADDR_ANY);
         }
-#ifndef NO_IPV6
-        retcode = p_bind(s, (address_family == AF_INET6 ?
-                           (struct sockaddr *) &a6 :
-                           (struct sockaddr *) &a),
-                       (address_family ==
-                        AF_INET6 ? sizeof(a6) : sizeof(a)));
-#else
-        retcode = p_bind(s, (struct sockaddr *) &a, sizeof(a));
+
+        a.sin_port = p_htons((short)port);
+        bindaddr = (struct sockaddr *)&a;
+        bindsize = sizeof(a);
+        break;
+      }
+#if HAVE_AFUNIX_H
+      case AF_UNIX: {
+        au.sun_family = AF_UNIX;
+        strncpy(au.sun_path, srcaddr, sizeof(au.sun_path));
+        bindaddr = (struct sockaddr *)&au;
+        bindsize = sizeof(au);
+        break;
+      }
 #endif
-        if (retcode != SOCKET_ERROR) {
-            err = 0;
-        } else {
-            err = p_WSAGetLastError();
-        }
+      default:
+        unreachable("bad address family in sk_newlistener_internal");
+    }
+
+    retcode = p_bind(s, bindaddr, bindsize);
+    if (retcode != SOCKET_ERROR) {
+        err = 0;
+    } else {
+        err = p_WSAGetLastError();
+    }
 
     if (err) {
         p_closesocket(s);
@@ -1408,9 +1449,9 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
      * If we were given ADDRTYPE_UNSPEC, we must also create an
      * IPv6 listening socket and link it to this one.
      */
-    if (address_family == AF_INET && orig_address_family == ADDRTYPE_UNSPEC) {
-        Socket *other = sk_newlistener(srcaddr, port, plug,
-                                       local_host_only, ADDRTYPE_IPV6);
+    if (address_family == AF_INET && orig_address_family == AF_UNSPEC) {
+        Socket *other = sk_newlistener_internal(srcaddr, port, plug,
+                                                local_host_only, AF_INET6);
 
         if (other) {
             NetSocket *ns = container_of(other, NetSocket, sock);
@@ -1427,6 +1468,33 @@ Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
     return &ret->sock;
 }
 
+Socket *sk_newlistener(const char *srcaddr, int port, Plug *plug,
+                       bool local_host_only, int orig_address_family)
+{
+    /*
+     * Translate address_family from platform-independent constants
+     * into local reality.
+     */
+    int address_family = (orig_address_family == ADDRTYPE_IPV4 ? AF_INET :
+#ifndef NO_IPV6
+                          orig_address_family == ADDRTYPE_IPV6 ? AF_INET6 :
+#endif
+                          AF_UNSPEC);
+
+    return sk_newlistener_internal(srcaddr, port, plug, local_host_only,
+                                   address_family);
+}
+
+Socket *sk_newlistener_unix(const char *path, Plug *plug)
+{
+#if HAVE_AFUNIX_H
+    return sk_newlistener_internal(path, 0, plug, false, AF_UNIX);
+#else
+    return new_error_socket_fmt(
+        plug, "AF_UNIX support not compiled into this program");
+#endif
+}
+
 static void sk_net_close(Socket *sock)
 {
     NetSocket *s = container_of(sock, NetSocket, sock);
@@ -1780,7 +1848,7 @@ void select_result(WPARAM wParam, LPARAM lParam)
 #ifdef NO_IPV6
         struct sockaddr_in isa;
 #else
-        struct sockaddr_storage isa;
+        struct sockaddr_storage isa; // FIXME: also if Unix and no IPv6
 #endif
         int addrlen = sizeof(isa);
         SOCKET t;  /* socket of connection */
@@ -1836,7 +1904,7 @@ static SocketPeerInfo *sk_net_peer_info(Socket *sock)
 #ifdef NO_IPV6
     struct sockaddr_in addr;
 #else
-    struct sockaddr_storage addr;
+    struct sockaddr_storage addr; // FIXME: also if Unix and no IPv6
     char buf[INET6_ADDRSTRLEN];
 #endif
     int addrlen = sizeof(addr);
@@ -1963,7 +2031,7 @@ SockAddr *platform_get_x11_unix_address(const char *display, int displaynum)
 {
     SockAddr *ret = snew(SockAddr);
     memset(ret, 0, sizeof(SockAddr));
-    ret->error = "unix sockets not supported on this platform";
+    ret->error = "unix sockets for X11 not supported on this platform";
     ret->refcount = 1;
     return ret;
 }

+ 2 - 0
source/putty/windows/platform.h

@@ -302,6 +302,7 @@ void socket_reselect_all(void);
 SockAddr *sk_namedpipe_addr(const char *pipename);
 /* Turn a WinSock error code into a string. */
 const char *winsock_error_string(int error);
+Socket *sk_newlistener_unix(const char *socketpath, Plug *plug);
 
 /*
  * network.c dynamically loads WinSock 2 or WinSock 1 depending on
@@ -341,6 +342,7 @@ const char *do_select(Plug * plug, SOCKET skt, bool enable); // WINSCP
  */
 void winselgui_set_hwnd(HWND hwnd);
 void winselgui_clear_hwnd(void);
+void winselgui_response(WPARAM wParam, LPARAM lParam);
 
 void winselcli_setup(void);
 SOCKET winselcli_unique_socket(void);