Browse Source

PuTTY 2022-02-22.d73a6d6

Latest version as on now, but primarily to get "HTTP proxy fixes"

Source commit: 78ea3fb48af64f32a6f306da5bc81d659932cf6e
Martin Prikryl 3 years ago
parent
commit
6c875ff226

+ 10 - 0
source/putty/doc/config.but

@@ -2057,6 +2057,16 @@ is a protocol extension (SOCKS 4A) which does support it, but not
 all SOCKS 4 servers provide this extension. If you enable proxy DNS
 and your SOCKS 4 server cannot deal with it, this might be why.
 
+If you want to avoid PuTTY making \e{any} DNS query related to your
+destination host name (for example, because your local DNS resolver is
+very slow to return a negative response in that situation), then as
+well as setting this control to \q{Yes}, you may also need to turn off
+GSSAPI authentication and GSSAPI key exchange in SSH (see
+\k{config-ssh-auth-gssapi} and \k{config-ssh-gssapi-kex}
+respectively). This is because GSSAPI setup also involves a DNS query
+for the destination host name, and that query is performed by the
+separate GSSAPI library, so PuTTY can't override or reconfigure it.
+
 \S{config-proxy-auth} \I{proxy username}Username and \I{proxy password}password
 
 If your proxy requires \I{proxy authentication}authentication, you can

+ 68 - 4
source/putty/doc/pageant.but

@@ -172,6 +172,11 @@ by the command, like this:
 
 \S{pageant-cmdline-openssh} Integrating with Windows OpenSSH
 
+Windows's own port of OpenSSH uses the same mechanism as Pageant to
+talk to its SSH agent (Windows named pipes). This means that Windows
+OpenSSH can talk directly to Pageant, if it knows where to find
+Pageant's named pipe.
+
 When Pageant starts up, it can optionally write out a file containing
 an OpenSSH configuration directive that tells the Windows \c{ssh.exe}
 where to find Pageant. If you include this file from your Windows SSH
@@ -183,14 +188,73 @@ The option is \c{--openssh-config}, and you follow it with a filename.
 
 To refer to this file from your main OpenSSH configuration, you can
 use the \cq{Include} directive. For example, you might run Pageant
-like this:
+like this (with your own username substituted, of course):
 
 \c pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf
 
-and then add a directive like this to your main \cq{.ssh\\config}
-file:
+and then add a directive like this to your main \cq{.ssh\\config} file
+(assuming that lives in the same directory that you just put
+\cw{pageant.conf}):
+
+\c Include pageant.conf
+
+\s{Note}: this technique only works with \e{Windows's} port of
+OpenSSH, which lives at \cw{C:\\Windows\\System32\\OpenSSH\\ssh.exe}
+if you have it installed. (If not, it can be installed as a Windows
+optional feature, e.g., via Settings > Apps & features > Optional
+features > Add a feature > OpenSSH Client.)
+
+There are other versions of OpenSSH for Windows, notably the one that
+comes with Windows \cw{git}. Those will likely not work with the same
+configuration, because they tend to depend on Unix emulation layers
+like MinGW or MSys, so they won't speak Windows native pathname syntax
+or understand named pipes. The above instructions will only work with
+Windows's own version of OpenSSH.
+
+So, if you want to use Windows \cw{git} with an SSH key held in
+Pageant, you'll have to set the environment variable \cw{GIT_SSH}, to
+point at a different program. You could point it at
+\cw{c:\\Windows\\System32\\OpenSSH\\ssh.exe} once you've done this
+setup \dash but it's just as easy to point it at Plink!
+
+\S{pageant-cmdline-unix} Unix-domain sockets: integrating with WSL 1
+
+Pageant can listen on the WinSock implementation of \q{Unix-domain
+sockets}. These interoperate with the Unix-domain sockets found in the
+original Windows Subsystem for Linux (now known as WSL 1). So if you
+ask Pageant to listen on one of these, then your WSL 1 processes can
+talk directly to Pageant.
+
+To configure this, run Pageant with the option \c{--unix}, followed
+with a pathname. Then, in WSL 1, set the environment variable
+\cw{SSH_AUTH_SOCK} to point at the WSL translation of that pathname.
+
+For example, you might run
+
+\c pageant --unix C:\Users\Simon\.ssh\agent.sock
+
+and in WSL 1, set the environment variable
+
+\c SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock
+
+Alternatively, you can add a line to your \cw{.ssh/config} file inside
+WSL that says
+
+\c IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock
+
+although doing it like that may mean that \cw{ssh-add} commands won't
+find the agent, even though \cw{ssh} itself will.
+
+\s{Security note}: Unix-domain sockets are protected against access by
+other users by the file protections on their containing directory. So
+if your Windows machine is multiuser, make sure you create the socket
+inside a directory that other users can't access at all. (In fact,
+that's a good idea on general principles.)
 
-\c Include C:\Users\Simon\.ssh\pageant.conf
+\s{Compatibility note}: WSL 2 processes cannot talk to Pageant by this
+mechanism, because WSL 2's Unix-domain sockets are managed by a
+separate Linux kernel, and not by the same kernel that WinSock talks
+to.
 
 \S{pageant-cmdline-keylist} Starting with the key list visible
 

+ 1 - 1
source/putty/doc/plink.but

@@ -61,7 +61,7 @@ use Plink:
 \c   -sercfg configuration-string (e.g. 19200,8,n,1,X)
 \c             Specify the serial configuration (serial only)
 \c The following options only apply to SSH connections:
-\c   -pw passw login with specified password
+\c   -pwfile file   login with password read from specified file
 \c   -D [listen-IP:]listen-port
 \c             Dynamic SOCKS-based port forwarding
 \c   -L [listen-IP:]listen-port:host:port

+ 1 - 1
source/putty/doc/pscp.but

@@ -53,7 +53,7 @@ use PSCP:
 \c   -load sessname  Load settings from saved session
 \c   -P port   connect to specified port
 \c   -l user   connect with specified username
-\c   -pw passw login with specified password
+\c   -pwfile file   login with password read from specified file
 \c   -1 -2     force use of particular SSH protocol version
 \c   -ssh -ssh-connection
 \c             force use of particular SSH protocol variant

+ 83 - 8
source/putty/doc/puttydoc.txt

@@ -3414,6 +3414,16 @@ Chapter 4: Configuring PuTTY
        all SOCKS 4 servers provide this extension. If you enable proxy DNS
        and your SOCKS 4 server cannot deal with it, this might be why.
 
+       If you want to avoid PuTTY making _any_ DNS query related to your
+       destination host name (for example, because your local DNS resolver
+       is very slow to return a negative response in that situation), then
+       as well as setting this control to `Yes', you may also need to
+       turn off GSSAPI authentication and GSSAPI key exchange in SSH (see
+       section 4.22 and section 4.18.1.1 respectively). This is because
+       GSSAPI setup also involves a DNS query for the destination host
+       name, and that query is performed by the separate GSSAPI library, so
+       PuTTY can't override or reconfigure it.
+
 4.16.4 Username and password
 
        If your proxy requires authentication, you can enter a username and
@@ -5212,7 +5222,7 @@ Chapter 5: Using PSCP to transfer files securely
            -load sessname  Load settings from saved session
            -P port   connect to specified port
            -l user   connect with specified username
-           -pw passw login with specified password
+           -pwfile file   login with password read from specified file
            -1 -2     force use of particular SSH protocol version
            -ssh -ssh-connection
                      force use of particular SSH protocol variant
@@ -6140,7 +6150,7 @@ Chapter 7: Using the command-line connection tool Plink
            -sercfg configuration-string (e.g. 19200,8,n,1,X)
                      Specify the serial configuration (serial only)
          The following options only apply to SSH connections:
-           -pw passw login with specified password
+           -pwfile file   login with password read from specified file
            -D [listen-IP:]listen-port
                      Dynamic SOCKS-based port forwarding
            -L [listen-IP:]listen-port:host:port
@@ -7282,6 +7292,11 @@ Chapter 9: Using Pageant for authentication
 
  9.3.3 Integrating with Windows OpenSSH
 
+       Windows's own port of OpenSSH uses the same mechanism as Pageant to
+       talk to its SSH agent (Windows named pipes). This means that Windows
+       OpenSSH can talk directly to Pageant, if it knows where to find
+       Pageant's named pipe.
+
        When Pageant starts up, it can optionally write out a file
        containing an OpenSSH configuration directive that tells the Windows
        `ssh.exe' where to find Pageant. If you include this file from your
@@ -7293,20 +7308,80 @@ Chapter 9: Using Pageant for authentication
 
        To refer to this file from your main OpenSSH configuration, you can
        use the `Include' directive. For example, you might run Pageant like
-       this:
+       this (with your own username substituted, of course):
 
          pageant --openssh-config C:\Users\Simon\.ssh\pageant.conf
 
-       and then add a directive like this to your main `.ssh\config' file:
+       and then add a directive like this to your main `.ssh\config'
+       file (assuming that lives in the same directory that you just put
+       pageant.conf):
+
+         Include pageant.conf
+
+       *Note*: this technique only works with _Windows's_ port of OpenSSH,
+       which lives at C:\Windows\System32\OpenSSH\ssh.exe if you have
+       it installed. (If not, it can be installed as a Windows optional
+       feature, e.g., via Settings > Apps & features > Optional features >
+       Add a feature > OpenSSH Client.)
+
+       There are other versions of OpenSSH for Windows, notably the one
+       that comes with Windows git. Those will likely not work with the
+       same configuration, because they tend to depend on Unix emulation
+       layers like MinGW or MSys, so they won't speak Windows native
+       pathname syntax or understand named pipes. The above instructions
+       will only work with Windows's own version of OpenSSH.
+
+       So, if you want to use Windows git with an SSH key held in
+       Pageant, you'll have to set the environment variable GIT_SSH,
+       to point at a different program. You could point it at
+       c:\Windows\System32\OpenSSH\ssh.exe once you've done this setup -
+       but it's just as easy to point it at Plink!
+
+ 9.3.4 Unix-domain sockets: integrating with WSL 1
+
+       Pageant can listen on the WinSock implementation of `Unix-domain
+       sockets'. These interoperate with the Unix-domain sockets found in
+       the original Windows Subsystem for Linux (now known as WSL 1). So if
+       you ask Pageant to listen on one of these, then your WSL 1 processes
+       can talk directly to Pageant.
+
+       To configure this, run Pageant with the option `--unix', followed
+       with a pathname. Then, in WSL 1, set the environment variable
+       SSH_AUTH_SOCK to point at the WSL translation of that pathname.
 
-         Include C:\Users\Simon\.ssh\pageant.conf
+       For example, you might run
+
+         pageant --unix C:\Users\Simon\.ssh\agent.sock
+
+       and in WSL 1, set the environment variable
+
+         SSH_AUTH_SOCK=/mnt/c/Users/Simon/.ssh/agent.sock
+
+       Alternatively, you can add a line to your .ssh/config file inside
+       WSL that says
+
+         IdentityAgent /mnt/c/Users/Simon/.ssh/agent.sock
+
+       although doing it like that may mean that ssh-add commands won't
+       find the agent, even though ssh itself will.
+
+       *Security note*: Unix-domain sockets are protected against access by
+       other users by the file protections on their containing directory.
+       So if your Windows machine is multiuser, make sure you create the
+       socket inside a directory that other users can't access at all. (In
+       fact, that's a good idea on general principles.)
+
+       *Compatibility note*: WSL 2 processes cannot talk to Pageant by this
+       mechanism, because WSL 2's Unix-domain sockets are managed by a
+       separate Linux kernel, and not by the same kernel that WinSock talks
+       to.
 
- 9.3.4 Starting with the key list visible
+ 9.3.5 Starting with the key list visible
 
        Start Pageant with the `--keylist' option to show the main window as
        soon as it starts up.
 
- 9.3.5 Restricting the Windows process ACL
+ 9.3.6 Restricting the Windows process ACL
 
        Pageant supports the same `-restrict-acl' option as the other PuTTY
        utilities to lock down the Pageant process's access control; see
@@ -11373,5 +11448,5 @@ Appendix G: SSH-2 names specified for PuTTY
                user will have to supply a passphrase before the key can be
                used).
 
-       ersionid PuTTY pre-release 0.77:2022-01-23.f11b201
+       ersionid PuTTY development snapshot 2022-02-23.d73a6d6
 

+ 1 - 1
source/putty/doc/version.but

@@ -1 +1 @@
-ersionid PuTTY pre-release 0.77:2022-01-23.f11b201
+ersionid PuTTY development snapshot 2022-02-23.d73a6d6

+ 85 - 9
source/putty/proxy/http.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");
                 bool found = false;
@@ -460,6 +463,7 @@ static void proxy_http_process_queue(ProxyNegotiator *pn)
         crReturnV;
 
         s->content_length = 0;
+        s->chunked_transfer = false;
         s->connection_close = false;
 
         /*
@@ -479,7 +483,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;
             }
@@ -530,7 +534,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"))
@@ -584,8 +606,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 */
@@ -594,9 +670,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);
@@ -601,10 +612,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);
         if (sk_socket_error(ps->sub_socket) != NULL)
             return &ps->sock;
 

+ 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;
 };
 
 struct ProxyNegotiatorVT {

+ 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

@@ -20,6 +20,10 @@
 
 #include <ws2tcpip.h>
 
+#if HAVE_AFUNIX_H
+#include <afunix.h>
+#endif
+
 #ifndef NO_IPV6
 #ifdef __clang__
 #pragma clang diagnostic push
@@ -79,12 +83,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
@@ -95,18 +115,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
@@ -446,9 +475,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';
 
@@ -471,7 +499,7 @@ SockAddr *sk_namelookup(const char *host, char **canonicalname,
                 sfree(trimmed_host);
             }
             if (err == 0)
-                ret->resolved = true;
+                ret->superfamily = IP;
         } else
 #endif
         {
@@ -480,12 +508,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" :
@@ -536,7 +564,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';
@@ -544,40 +572,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
@@ -619,7 +646,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]);
@@ -650,7 +677,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
@@ -662,7 +689,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)
@@ -710,7 +741,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) {
@@ -746,7 +777,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
@@ -760,7 +791,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
@@ -904,7 +935,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
@@ -1120,21 +1151,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.
@@ -1154,16 +1190,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,
@@ -1189,85 +1215,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);
@@ -1298,9 +1339,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);
@@ -1317,6 +1358,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);
@@ -1656,7 +1724,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 */
@@ -1712,7 +1780,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);
@@ -1831,7 +1899,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

@@ -293,6 +293,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
@@ -332,6 +333,7 @@ const char *do_select(SOCKET skt, bool enable);
  */
 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);