Browse Source

PuTTY snapshot abfc751c (Update version number for 0.71 release - 2019-03-16)

Source commit: 86d690c216aba2a5c82254c67e397210870b01b3
Martin Prikryl 6 years ago
parent
commit
c8474497e4

+ 4 - 5
source/putty/WINDOWS/winhsock.c

@@ -33,8 +33,8 @@ typedef struct HandleSocket {
     /* We buffer data here if we receive it from winhandl while frozen. */
     bufchain inputdata;
 
-    /* Data received from stderr_H, if we have one. */
-    bufchain stderrdata;
+    /* Handle logging proxy error messages from stderr_H, if we have one. */
+    ProxyStderrBuf psb;
 
     bool defer_close, deferred_close;   /* in case of re-entrance */
 
@@ -86,7 +86,7 @@ static size_t handle_stderr(
     HandleSocket *hs = (HandleSocket *)handle_get_privdata(h);
 
     if (!err && len > 0)
-        log_proxy_stderr(hs->plug, &hs->stderrdata, data, len);
+        log_proxy_stderr(hs->plug, &hs->psb, data, len);
 
     return 0;
 }
@@ -127,7 +127,6 @@ static void sk_handle_close(Socket *s)
     if (hs->recv_H != hs->send_H)
         CloseHandle(hs->recv_H);
     bufchain_clear(&hs->inputdata);
-    bufchain_clear(&hs->stderrdata);
 
     sfree(hs);
 }
@@ -332,7 +331,7 @@ Socket *make_handle_socket(HANDLE send_H, HANDLE recv_H, HANDLE stderr_H,
     hs->error = NULL;
     hs->frozen = UNFROZEN;
     bufchain_init(&hs->inputdata);
-    bufchain_init(&hs->stderrdata);
+    psb_init(&hs->psb);
 
     hs->recv_H = recv_H;
     hs->recv_h = handle_input_new(hs->recv_H, handle_gotdata, hs, flags);

+ 69 - 32
source/putty/be_misc.c

@@ -59,12 +59,15 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
     }
 }
 
-void log_proxy_stderr(Plug *plug, bufchain *buf, const void *vdata, size_t len)
+void psb_init(ProxyStderrBuf *psb)
+{
+    psb->size = 0;
+}
+
+void log_proxy_stderr(Plug *plug, ProxyStderrBuf *psb,
+                      const void *vdata, size_t len)
 {
     const char *data = (const char *)vdata;
-    size_t pos = 0;
-    const char *nlpos;
-    char *msg, *fullmsg;
 
     /*
      * This helper function allows us to collect the data written to a
@@ -72,43 +75,77 @@ void log_proxy_stderr(Plug *plug, bufchain *buf, const void *vdata, size_t len)
      * happen to get from its pipe, and whenever we have a complete
      * line, we pass it to plug_log.
      *
-     * Prerequisites: a plug to log to, and a bufchain stored
-     * somewhere to collect the data in.
+     * (We also do this when the buffer in psb fills up, to avoid just
+     * allocating more and more memory forever, and also to keep Event
+     * Log lines reasonably bounded in size.)
+     *
+     * Prerequisites: a plug to log to, and a ProxyStderrBuf stored
+     * somewhere to collect any not-yet-output partial line.
      */
 
-    while (pos < len && (nlpos = memchr(data+pos, '\n', len-pos)) != NULL) {
+    while (len > 0) {
         /*
-         * Found a newline in the current input buffer. Append it to
-         * the bufchain (which may contain a partial line from last
-         * time).
+         * Copy as much data into psb->buf as will fit.
          */
-        bufchain_add(buf, data + pos, nlpos - (data + pos));
+        assert(psb->size < lenof(psb->buf));
+        size_t to_consume = lenof(psb->buf) - psb->size;
+        if (to_consume > len)
+            to_consume = len;
+        memcpy(psb->buf + psb->size, data, to_consume);
+        data += to_consume;
+        len -= to_consume;
+        psb->size += to_consume;
 
         /*
-         * Collect the resulting line of data and pass it to plug_log.
+         * Output any full lines in psb->buf.
          */
-        size_t msglen = bufchain_size(buf);
-        assert(msglen < ~(size_t)0);
-        msg = snewn(msglen+1, char);
-        bufchain_fetch(buf, msg, msglen);
-        bufchain_consume(buf, msglen);
-        while (msglen > 0 && (msg[msglen-1] == '\n' || msg[msglen-1] == '\r'))
-            msglen--;
-        msg[msglen] = '\0';
-        fullmsg = dupprintf("proxy: %s", msg);
-        plug_log(plug, 2, NULL, 0, fullmsg, 0);
-        sfree(fullmsg);
-        sfree(msg);
+        size_t pos = 0;
+        while (pos < psb->size) {
+            char *nlpos = memchr(psb->buf + pos, '\n', psb->size - pos);
+            if (!nlpos)
+                break;
+
+            /*
+             * Found a newline in the buffer, so we can output a line.
+             */
+            size_t endpos = nlpos - psb->buf;
+            while (endpos > pos && (psb->buf[endpos-1] == '\n' ||
+                                    psb->buf[endpos-1] == '\r'))
+                endpos--;
+            char *msg = dupprintf(
+                "proxy: %.*s", (int)(endpos - pos), psb->buf + pos);
+            plug_log(plug, 2, NULL, 0, msg, 0);
+            sfree(msg);
+
+            pos = nlpos - psb->buf + 1;
+            assert(pos <= psb->size);
+        }
 
         /*
-         * Advance past the newline.
+         * If the buffer is completely full and we didn't output
+         * anything, then output the whole thing, flagging it as a
+         * truncated line.
          */
-        pos += nlpos+1 - (data + pos);
-    }
+        if (pos == 0 && psb->size == lenof(psb->buf)) {
+            char *msg = dupprintf(
+                "proxy (partial line): %.*s", (int)psb->size, psb->buf);
+            plug_log(plug, 2, NULL, 0, msg, 0);
+            sfree(msg);
 
-    /*
-     * Now any remaining data is a partial line, which we save for
-     * next time.
-     */
-    bufchain_add(buf, data + pos, len - pos);
+            pos = psb->size = 0;
+        }
+
+        /*
+         * Now move any remaining data up to the front of the buffer.
+         */
+        size_t newsize = psb->size - pos;
+        if (newsize)
+            memmove(psb->buf, psb->buf + pos, newsize);
+        psb->size = newsize;
+
+        /*
+         * And loop round again if there's more data to be read from
+         * our input.
+         */
+    }
 }

+ 1 - 0
source/putty/defs.h

@@ -28,6 +28,7 @@
 
 typedef struct conf_tag Conf;
 typedef struct terminal_tag Terminal;
+typedef struct term_utf8_decode term_utf8_decode;
 
 typedef struct Filename Filename;
 typedef struct FontSpec FontSpec;

+ 6 - 4
source/putty/doc/Makefile

@@ -44,14 +44,16 @@ INPUTS = $(patsubst %,%.but,$(CHAPTERS))
 HALIBUT = halibut
 
 index.html: $(INPUTS)
-	$(HALIBUT) --text --html --winhelp --chm $(INPUTS)
+	$(HALIBUT) --text --html --chm $(INPUTS)
 
-# During formal builds it's useful to be able to build these ones alone.
-putty.hlp: $(INPUTS)
-	$(HALIBUT) --winhelp $(INPUTS)
+# During formal builds it's useful to be able to build this one alone.
 putty.chm: $(INPUTS)
 	$(HALIBUT) --chm $(INPUTS)
 
+# We don't ship this any more.
+putty.hlp: $(INPUTS)
+	$(HALIBUT) --winhelp $(INPUTS)
+
 putty.info: $(INPUTS)
 	$(HALIBUT) --info $(INPUTS)
 

+ 65 - 11
source/putty/doc/faq.but

@@ -173,17 +173,21 @@ time by double-clicking or using \c{REGEDIT}.
 \S{faq-server}{Question} Will you write an SSH server for the PuTTY
 suite, to go with the client?
 
-No. The only reason we might want to would be if we could easily
-re-use existing code and significantly cut down the effort. We don't
-believe this is the case; there just isn't enough common ground
-between an SSH client and server to make it worthwhile.
-
-If someone else wants to use bits of PuTTY in the process of writing
-a Windows SSH server, they'd be perfectly welcome to of course, but
-I really can't see it being a lot less effort for us to do that than
-it would be for us to write a server from the ground up. We don't
-have time, and we don't have motivation. The code is available if
-anyone else wants to try it.
+Not one that you'd want to use.
+
+While much of the protocol and networking code can be made common
+between a client and server, to make a \e{useful} general-purpose
+server requires all sorts of fiddly new code like interacting with OS
+authentication databases and the like.
+
+A special-purpose SSH server (called \i{Uppity}) can now be built from
+the PuTTY source code, and indeed it is not usable as a
+general-purpose server; it exists mainly as a test harness.
+
+If someone else wants to use this as a basis for writing a
+general-purpose SSH server, they'd be perfectly welcome to of course;
+but we don't have time, and we don't have motivation. The code is
+available if anyone else wants to try it.
 
 \S{faq-pscp-ascii}{Question} Can PSCP or PSFTP transfer files in
 \i{ASCII} mode?
@@ -419,6 +423,56 @@ You can ask PuTTY to delete all this data; see \k{faq-cleanup}.
 On Unix, PuTTY stores all of this data in a directory \cw{~/.putty}
 by default.
 
+\S{faq-trust-sigils} Why do small PuTTY icons appear next to the login
+prompts?
+
+As of PuTTY 0.71, some lines of text in the terminal window are marked
+with a small copy of the PuTTY icon (as far as pixels allow).
+
+This is to show trustworthiness. When the PuTTY icon appears next to a
+line of text, it indicates that that line of text was generated by
+PuTTY itself, and not generated by the server and sent to PuTTY.
+
+Text that comes from the server does not have this icon, and we've
+arranged that the server should not be able to fake it. (There's no
+control sequence the server can send which will make PuTTY draw its
+own icon, and if the server tries to move the cursor back up to a line
+that \e{already} has an icon and overwrite the text, the icon will
+disappear.)
+
+This lets you tell the difference between (for example) a legitimate
+prompt in which PuTTY itself asks you for your private key passphrase,
+and a fake prompt in which the server tries to send the identical text
+to trick you into telling \e{it} your private key passphrase.
+
+\S{faq-plink-pause} Why has Plink started saying \q{Press Return to
+begin session}?
+
+As of PuTTY 0.71, if you use Plink for an interactive SSH session,
+then after the login phase has finished, it will present a final
+interactive prompt saying \q{Access granted. Press Return to begin
+session}.
+
+This is another defence against servers trying to mimic the real
+authentication prompts after the session has started. When you pass
+through that prompt, you know that everything after it is generated by
+the server and not by Plink itself, so any request for your private
+key passphrase should be treated with suspicion.
+
+In Plink, we can't use the defence described in \k{faq-trust-sigils}:
+Plink is running \e{in} the terminal, so anything it can write into
+the terminal, the server could write in the same way after the session
+starts. And we can't just print a separator line without a pause,
+because then the server could simply move the cursor back up to it and
+overwrite it (probably with a brief flicker, but you might easily miss
+that). The only robust defence anyone has come up with involves this
+pause.
+
+If you trust your server not to be abusive, you can turn this off. It
+will also not appear in various other circumstances where Plink can be
+confident it isn't necessary. See \k{plink-option-antispoof} for
+details.
+
 \H{faq-howto} HOWTO questions
 
 \S{faq-login}{Question} What login name / password should I use?

+ 5 - 0
source/putty/doc/pgpkeys.but

@@ -16,6 +16,11 @@ option), such that if you have an executable you trust, you can use
 it to establish a trust path, for instance to a newer version
 downloaded from the Internet.
 
+As of release 0.67, the Windows executables and installer also contain
+built-in signatures that are automatically verified by Windows' own
+mechanism (\q{\i{Authenticode}}). The keys used for that are different,
+and are not covered here.
+
 (Note that none of the keys, signatures, etc mentioned here have
 anything to do with keys used with SSH - they are purely for verifying
 the origin of files distributed by the PuTTY team.)

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

@@ -41,7 +41,7 @@ use Plink:
 
 \c Z:\sysosd>plink
 \c Plink: command-line connection utility
-\c Release 0.70
+\c Release 0.71
 \c Usage: plink [options] [user@]host [command]
 \c        ("host" can also be a PuTTY saved session name)
 \c Options:
@@ -81,6 +81,7 @@ use Plink:
 \c             manually specify a host key (may be repeated)
 \c   -sanitise-stderr, -sanitise-stdout, -no-sanitise-stderr, -no-sanitise-stdout
 \c             do/don't strip control chars from standard output/error
+\c   -no-antispoof   omit anti-spoofing prompt after authentication
 \c   -m file   read remote command(s) from file
 \c   -s        remote command is an SSH subsystem (SSH-2 only)
 \c   -N        don't start a shell/command (SSH-2 only)
@@ -328,6 +329,46 @@ channel.
 \dt Do not sanitise server data written to Plink's standard output
 channel.
 
+\S2{plink-option-antispoof} \I{-no-antispoof}: turn off authentication spoofing protection prompt
+
+In SSH, some possible server authentication methods require user input
+(for example, password authentication, or entering a private key
+passphrase), and others do not (e.g. a private key held in Pageant).
+
+If you use Plink to run an interactive login session, and if Plink
+authenticates without needing any user interaction, and if the server
+is malicious or compromised, it could try to trick you into giving it
+authentication data that should not go to the server (such as your
+private key passphrase), by sending what \e{looks} like one of Plink's
+local prompts, as if Plink had not already authenticated.
+
+To protect against this, Plink's default policy is to finish the
+authentication phase with a final trivial prompt looking like this:
+
+\c Access granted. Press Return to begin session.
+
+so that if you saw anything that looked like an authentication prompt
+\e{after} that line, you would know it was not from Plink.
+
+That extra interactive step is inconvenient. So Plink will turn it off
+in as many situations as it can:
+
+\b If Plink's standard input is not pointing at a console or terminal
+device \dash for example, if you're using Plink as a transport for
+some automated application like version control \dash then you
+\e{can't} type passphrases into the server anyway. In that situation,
+Plink won't try to protect you from the server trying to fool you into
+doing so.
+
+\b If Plink is in batch mode (see \k{plink-usage-batch}), then it
+\e{never} does any interactive authentication. So anything looking
+like an interactive authentication prompt is automatically suspect,
+and so Plink omits the anti-spoofing prompt.
+
+But if you still find the protective prompt inconvenient, and you
+trust the server not to try a trick like this, you can turn it off
+using the \cq{-no-antispoof} option.
+
 \H{plink-batch} Using Plink in \i{batch files} and \i{scripts}
 
 Once you have set up Plink to be able to log in to a remote server

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

@@ -39,7 +39,7 @@ use PSCP:
 
 \c Z:\owendadmin>pscp
 \c PuTTY Secure Copy client
-\c Release 0.70
+\c Release 0.71
 \c Usage: pscp [options] [user@]host:source target
 \c        pscp [options] source [source...] [user@]host:target
 \c        pscp [options] -ls [user@]host:filespec

+ 15 - 15
source/putty/import.c

@@ -313,7 +313,7 @@ static struct openssh_pem_key *load_openssh_pem_key(const Filename *filename,
     int base64_chars = 0;
 
     ret = snew(struct openssh_pem_key);
-    ret->keyblob = strbuf_new();
+    ret->keyblob = strbuf_new_nm();
 
     fp = f_open(filename, "r", false);
     if (!fp) {
@@ -535,7 +535,7 @@ static ssh2_userkey *openssh_pem_read(
     int i, num_integers;
     ssh2_userkey *retval = NULL;
     const char *errmsg;
-    strbuf *blob = strbuf_new();
+    strbuf *blob = strbuf_new_nm();
     int privptr = 0, publen;
 
     if (!key)
@@ -794,11 +794,11 @@ static bool openssh_pem_write(
      */
     pubblob = strbuf_new();
     ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
-    privblob = strbuf_new();
+    privblob = strbuf_new_nm();
     ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
     spareblob = NULL;
 
-    outblob = strbuf_new();
+    outblob = strbuf_new_nm();
 
     /*
      * Encode the OpenSSH key blob, and also decide on the header
@@ -903,7 +903,7 @@ static bool openssh_pem_write(
             footer = "-----END DSA PRIVATE KEY-----\n";
         }
 
-        seq = strbuf_new();
+        seq = strbuf_new_nm();
         for (i = 0; i < nnumbers; i++) {
             put_ber_id_len(seq, 2, numbers[i].len, 0);
             put_datapl(seq, numbers[i]);
@@ -933,7 +933,7 @@ static bool openssh_pem_write(
         oid = ec_alg_oid(ssh_key_alg(key->key), &oidlen);
         pointlen = (ec->curve->fieldBits + 7) / 8 * 2;
 
-        seq = strbuf_new();
+        seq = strbuf_new_nm();
 
         /* INTEGER 1 */
         put_ber_id_len(seq, 2, 1, 0);
@@ -1102,7 +1102,7 @@ static struct openssh_new_key *load_openssh_new_key(const Filename *filename,
     unsigned key_index;
 
     ret = snew(struct openssh_new_key);
-    ret->keyblob = strbuf_new();
+    ret->keyblob = strbuf_new_nm();
 
     fp = f_open(filename, "r", false);
     if (!fp) {
@@ -1493,13 +1493,13 @@ static bool openssh_new_write(
      */
     pubblob = strbuf_new();
     ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
-    privblob = strbuf_new();
+    privblob = strbuf_new_nm();
     ssh_key_openssh_blob(key->key, BinarySink_UPCAST(privblob));
 
     /*
      * Construct the cleartext version of the blob.
      */
-    cblob = strbuf_new();
+    cblob = strbuf_new_nm();
 
     /* Magic number. */
     put_asciz(cblob, "openssh-key-v1");
@@ -1516,7 +1516,7 @@ static bool openssh_new_write(
         random_read(bcrypt_salt, sizeof(bcrypt_salt));
         put_stringz(cblob, "aes256-ctr");
         put_stringz(cblob, "bcrypt");
-        substr = strbuf_new();
+        substr = strbuf_new_nm();
         put_string(substr, bcrypt_salt, sizeof(bcrypt_salt));
         put_uint32(substr, bcrypt_rounds);
         put_stringsb(cblob, substr);
@@ -1530,7 +1530,7 @@ static bool openssh_new_write(
 
     /* Private section. */
     {
-        strbuf *cpblob = strbuf_new();
+        strbuf *cpblob = strbuf_new_nm();
 
         /* checkint. */
         uint8_t checkint_buf[4];
@@ -1718,7 +1718,7 @@ static struct sshcom_key *load_sshcom_key(const Filename *filename,
 
     ret = snew(struct sshcom_key);
     ret->comment[0] = '\0';
-    ret->keyblob = strbuf_new();
+    ret->keyblob = strbuf_new_nm();
 
     fp = f_open(filename, "r", false);
     if (!fp) {
@@ -2060,7 +2060,7 @@ static ssh2_userkey *sshcom_read(
      * construct public and private blobs in our own format, and
      * end up feeding them to ssh_key_new_priv().
      */
-    blob = strbuf_new();
+    blob = strbuf_new_nm();
     if (type == RSA) {
         ptrlen n, e, d, u, p, q;
 
@@ -2157,7 +2157,7 @@ static bool sshcom_write(
      */
     pubblob = strbuf_new();
     ssh_key_public_blob(key->key, BinarySink_UPCAST(pubblob));
-    privblob = strbuf_new();
+    privblob = strbuf_new_nm();
     ssh_key_private_blob(key->key, BinarySink_UPCAST(privblob));
     outblob = NULL;
 
@@ -2225,7 +2225,7 @@ static bool sshcom_write(
         goto error;                    /* unsupported key type */
     }
 
-    outblob = strbuf_new();
+    outblob = strbuf_new_nm();
 
     /*
      * Create the unencrypted key blob.

+ 52 - 0
source/putty/memory.c

@@ -2,11 +2,13 @@
  * PuTTY's memory allocation wrappers.
  */
 
+#include <assert.h>
 #include <stdlib.h>
 #include <limits.h>
 
 #include "defs.h"
 #include "puttymem.h"
+#include "misc.h"
 
 void *safemalloc(size_t n, size_t size)
 {
@@ -69,3 +71,53 @@ void safefree(void *ptr)
 #endif
     }
 }
+
+void *safegrowarray(void *ptr, size_t *allocated, size_t eltsize,
+                    size_t oldlen, size_t extralen, bool secret)
+{
+    /* The largest value we can safely multiply by eltsize */
+    assert(eltsize > 0);
+    size_t maxsize = (~(size_t)0) / eltsize;
+
+    size_t oldsize = *allocated;
+
+    /* Range-check the input values */
+    assert(oldsize <= maxsize);
+    assert(oldlen <= maxsize);
+    assert(extralen <= maxsize - oldlen);
+
+    /* If the size is already enough, don't bother doing anything! */
+    if (oldsize > oldlen + extralen)
+        return ptr;
+
+    /* Find out how much we need to grow the array by. */
+    size_t increment = (oldlen + extralen) - oldsize;
+
+    /* Invent a new size. We want to grow the array by at least
+     * 'increment' elements; by at least a fixed number of bytes (to
+     * get things started when sizes are small); and by some constant
+     * factor of its old size (to avoid repeated calls to this
+     * function taking quadratic time overall). */
+    if (increment < 256 / eltsize)
+        increment = 256 / eltsize;
+    if (increment < oldsize / 16)
+        increment = oldsize / 16;
+
+    /* But we also can't grow beyond maxsize. */
+    size_t maxincr = maxsize - oldsize;
+    if (increment > maxincr)
+        increment = maxincr;
+
+    size_t newsize = oldsize + increment;
+    void *toret;
+    if (secret) {
+        toret = safemalloc(newsize, eltsize);
+        memcpy(toret, ptr, oldsize * eltsize);
+        smemclr(ptr, oldsize * eltsize);
+        sfree(ptr);
+    } else {
+        toret = saferealloc(ptr, newsize, eltsize);
+    }
+    *allocated = newsize;
+    return toret;
+}

+ 15 - 4
source/putty/misc.c

@@ -39,7 +39,7 @@ prompts_t *new_prompts(void)
 {
     prompts_t *p = snew(prompts_t);
     p->prompts = NULL;
-    p->n_prompts = 0;
+    p->n_prompts = p->prompts_size = 0;
     p->data = NULL;
     p->to_server = true; /* to be on the safe side */
     p->name = p->instruction = NULL;
@@ -53,9 +53,8 @@ void add_prompt(prompts_t *p, char *promptstr, bool echo)
     pr->echo = echo;
     pr->result = NULL;
     pr->resultsize = 0;
-    p->n_prompts++;
-    p->prompts = sresize(p->prompts, p->n_prompts, prompt_t *);
-    p->prompts[p->n_prompts-1] = pr;
+    sgrowarray(p->prompts, p->prompts_size, p->n_prompts);
+    p->prompts[p->n_prompts++] = pr;
 }
 void prompt_ensure_result_size(prompt_t *pr, int newlen)
 {
@@ -273,6 +272,14 @@ char *buildinfo(const char *newline)
         }
     }
 #endif
+#if defined _WINDOWS
+    {
+        int echm = has_embedded_chm();
+        if (echm >= 0)
+            strbuf_catf(buf, "%sEmbedded HTML Help file: %s", newline,
+                        echm ? "yes" : "no");
+    }
+#endif
 
 #if defined _WINDOWS && defined MINEFIELD
     strbuf_catf(buf, "%sBuild option: MINEFIELD", newline);
@@ -334,6 +341,10 @@ const char *nullseat_get_x_display(Seat *seat) { return NULL; }
 bool nullseat_get_windowid(Seat *seat, long *id_out) { return false; }
 bool nullseat_get_window_pixel_size(
     Seat *seat, int *width, int *height) { return false; }
+StripCtrlChars *nullseat_stripctrl_new(
+    Seat *seat, BinarySink *bs_out, SeatInteractionContext sic) {return NULL;}
+bool nullseat_set_trust_status(Seat *seat, bool tr) { return false; }
+bool nullseat_set_trust_status_vacuously(Seat *seat, bool tr) { return true; }
 
 void sk_free_peer_info(SocketPeerInfo *pi)
 {

+ 32 - 7
source/putty/misc.h

@@ -34,14 +34,26 @@ char *dupprintf(const char *fmt, ...)
 char *dupvprintf(const char *fmt, va_list ap);
 void burnstr(char *string);
 
+/*
+ * The visible part of a strbuf structure. There's a surrounding
+ * implementation struct in misc.c, which isn't exposed to client
+ * code.
+ */
 struct strbuf {
     char *s;
     unsigned char *u;
-    int len;
+    size_t len;
     BinarySink_IMPLEMENTATION;
-    /* (also there's a surrounding implementation struct in misc.c) */
 };
+
+/* strbuf constructors: strbuf_new_nm and strbuf_new differ in that a
+ * strbuf constructed using the _nm version will resize itself by
+ * alloc/copy/smemclr/free instead of realloc. Use that version for
+ * data sensitive enough that it's worth costing performance to
+ * avoid copies of it lingering in process memory. */
 strbuf *strbuf_new(void);
+strbuf *strbuf_new_nm(void);
+
 void strbuf_free(strbuf *buf);
 void *strbuf_append(strbuf *buf, size_t len);
 char *strbuf_to_str(strbuf *buf); /* does free buf, but you must free result */
@@ -125,8 +137,6 @@ static inline void bufchain_set_callback(bufchain *ch, IdempotentCallback *ic)
     bufchain_set_callback_inner(ch, ic, queue_idempotent_callback);
 }
 
-void sanitise_term_data(bufchain *out, const void *vdata, size_t len);
-
 bool validate_manual_hostkey(char *key);
 
 struct tm ltime(void);
@@ -159,6 +169,7 @@ bool ptrlen_eq_string(ptrlen pl, const char *str);
 bool ptrlen_eq_ptrlen(ptrlen pl1, ptrlen pl2);
 int ptrlen_strcmp(ptrlen pl1, ptrlen pl2);
 bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail);
+bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail);
 char *mkstr(ptrlen pl);
 int string_length_for_printf(size_t);
 /* Derive two printf arguments from a ptrlen, suitable for "%.*s" */
@@ -187,6 +198,11 @@ void smemclr(void *b, size_t len);
  * hinted at by the 'eq' in the name. */
 bool smemeq(const void *av, const void *bv, size_t len);
 
+/* Encode a single UTF-8 character. Assumes that illegal characters
+ * (such as things in the surrogate range, or > 0x10FFFF) have already
+ * been removed. */
+size_t encode_utf8(void *output, unsigned long ch);
+
 char *buildinfo(const char *newline);
 
 /*
@@ -362,11 +378,20 @@ struct StripCtrlChars {
 };
 StripCtrlChars *stripctrl_new(
     BinarySink *bs_out, bool permit_cr, wchar_t substitution);
+StripCtrlChars *stripctrl_new_term_fn(
+    BinarySink *bs_out, bool permit_cr, wchar_t substitution,
+    Terminal *term, unsigned long (*translate)(
+        Terminal *, term_utf8_decode *, unsigned char));
+#define stripctrl_new_term(bs, cr, sub, term) \
+    stripctrl_new_term_fn(bs, cr, sub, term, term_translate)
+void stripctrl_retarget(StripCtrlChars *sccpub, BinarySink *new_bs_out);
+void stripctrl_reset(StripCtrlChars *sccpub);
 void stripctrl_free(StripCtrlChars *sanpub);
-char *stripctrl_string_ptrlen(ptrlen str);
-static inline char *stripctrl_string(const char *str)
+void stripctrl_enable_line_limiting(StripCtrlChars *sccpub);
+char *stripctrl_string_ptrlen(StripCtrlChars *sccpub, ptrlen str);
+static inline char *stripctrl_string(StripCtrlChars *sccpub, const char *str)
 {
-    return stripctrl_string_ptrlen(ptrlen_from_asciz(str));
+    return stripctrl_string_ptrlen(sccpub, ptrlen_from_asciz(str));
 }
 
 #endif

+ 7 - 1
source/putty/network.h

@@ -290,7 +290,13 @@ void backend_socket_log(Seat *seat, LogContext *logctx,
                         int type, SockAddr *addr, int port,
                         const char *error_msg, int error_code, Conf *conf,
                         bool session_started);
+
+typedef struct ProxyStderrBuf {
+    char buf[8192];
+    size_t size;
+} ProxyStderrBuf;
+void psb_init(ProxyStderrBuf *psb);
 void log_proxy_stderr(
-    Plug *plug, bufchain *buf, const void *vdata, size_t len);
+    Plug *plug, ProxyStderrBuf *psb, const void *vdata, size_t len);
 
 #endif

+ 1 - 1
source/putty/proxy.c

@@ -1223,7 +1223,7 @@ int proxy_socks5_negotiate (ProxySocket *p, int change)
             const char *username = conf_get_str(p->conf, CONF_proxy_username);
             const char *password = conf_get_str(p->conf, CONF_proxy_password);
 	    if (username[0] || password[0]) {
-                strbuf *auth = strbuf_new();
+                strbuf *auth = strbuf_new_nm();
 		put_byte(auth, 1); /* version number of subnegotiation */
                 if (!put_pstring(auth, username)) {
                     p->error = "Proxy error: SOCKS 5 authentication cannot "

+ 67 - 4
source/putty/putty.h

@@ -659,12 +659,22 @@ typedef struct {
      * sufficient).
      */
     bool to_server;
+
+    /*
+     * Indicates whether the prompts originated _at_ the server, so
+     * that the front end can display some kind of trust sigil that
+     * distinguishes (say) a legit private-key passphrase prompt from
+     * a fake one sent by a malicious server.
+     */
+    bool from_server;
+
     char *name;		/* Short description, perhaps for dialog box title */
     bool name_reqd;     /* Display of `name' required or optional? */
     char *instruction;	/* Long description, maybe with embedded newlines */
     bool instr_reqd;    /* Display of `instruction' required or optional? */
     size_t n_prompts;   /* May be zero (in which case display the foregoing,
                          * if any, and return success) */
+    size_t prompts_size; /* allocated storage capacity for prompts[] */
     prompt_t **prompts;
     void *data;		/* slot for housekeeping data, managed by
 			 * seat_get_userpass_input(); initially NULL */
@@ -739,6 +749,10 @@ typedef enum BusyStatus {
                      * suspended */
 } BusyStatus;
 
+typedef enum SeatInteractionContext {
+    SIC_BANNER, SIC_KI_PROMPTS
+} SeatInteractionContext;
+
 /*
  * Data type 'Seat', which is an API intended to contain essentially
  * everything that a back end might need to talk to its client for:
@@ -929,6 +943,29 @@ struct SeatVtable {
      * true.
      */
     bool (*get_window_pixel_size)(Seat *seat, int *width, int *height);
+
+    /*
+     * Return a StripCtrlChars appropriate for sanitising untrusted
+     * terminal data (e.g. SSH banners, prompts) being sent to the
+     * user of this seat. May return NULL if no sanitisation is
+     * needed.
+     */
+    StripCtrlChars *(*stripctrl_new)(
+        Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+
+    /*
+     * Set the seat's current idea of where output is coming from.
+     * True means that output is being generated by our own code base
+     * (and hence, can be trusted if it's asking you for secrets such
+     * as your passphrase); false means output is coming from the
+     * server.
+     *
+     * Returns true if the seat has a way to indicate this
+     * distinction. Returns false if not, in which case the backend
+     * should use a fallback defence against spoofing of PuTTY's local
+     * prompts by malicious servers.
+     */
+    bool (*set_trust_status)(Seat *seat, bool trusted);
 };
 
 static inline size_t seat_output(
@@ -969,6 +1006,11 @@ static inline bool seat_get_windowid(Seat *seat, long *id_out)
 { return seat->vt->get_windowid(seat, id_out); }
 static inline bool seat_get_window_pixel_size(Seat *seat, int *w, int *h)
 { return seat->vt->get_window_pixel_size(seat, w, h); }
+static inline StripCtrlChars *seat_stripctrl_new(
+    Seat *seat, BinarySink *bs, SeatInteractionContext sic)
+{ return seat->vt->stripctrl_new(seat, bs, sic); }
+static inline bool seat_set_trust_status(Seat *seat, bool trusted)
+{ return  seat->vt->set_trust_status(seat, trusted); }
 
 /* Unlike the seat's actual method, the public entry point
  * seat_connection_fatal is a wrapper function with a printf-like API,
@@ -978,8 +1020,12 @@ void seat_connection_fatal(Seat *seat, const char *fmt, ...);
 /* Handy aliases for seat_output which set is_stderr to a fixed value. */
 static inline size_t seat_stdout(Seat *seat, const void *data, size_t len)
 { return seat_output(seat, false, data, len); }
+static inline size_t seat_stdout_pl(Seat *seat, ptrlen data)
+{ return seat_output(seat, false, data.ptr, data.len); }
 static inline size_t seat_stderr(Seat *seat, const void *data, size_t len)
 { return seat_output(seat, true, data, len); }
+static inline size_t seat_stderr_pl(Seat *seat, ptrlen data)
+{ return seat_output(seat, true, data.ptr, data.len); }
 
 /*
  * Stub methods for seat implementations that want to use the obvious
@@ -1013,6 +1059,10 @@ void nullseat_echoedit_update(Seat *seat, bool echoing, bool editing);
 const char *nullseat_get_x_display(Seat *seat);
 bool nullseat_get_windowid(Seat *seat, long *id_out);
 bool nullseat_get_window_pixel_size(Seat *seat, int *width, int *height);
+StripCtrlChars *nullseat_stripctrl_new(
+        Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+bool nullseat_set_trust_status(Seat *seat, bool trusted);
+bool nullseat_set_trust_status_vacuously(Seat *seat, bool trusted);
 
 /*
  * Seat functions provided by the platform's console-application
@@ -1030,6 +1080,9 @@ int console_confirm_weak_crypto_primitive(
 int console_confirm_weak_cached_hostkey(
     Seat *seat, const char *algname, const char *betteralgs,
     void (*callback)(void *ctx, int result), void *ctx);
+StripCtrlChars *console_stripctrl_new(
+        Seat *seat, BinarySink *bs_out, SeatInteractionContext sic);
+bool console_set_trust_status(Seat *seat, bool trusted);
 
 /*
  * Other centralised seat functions.
@@ -1065,6 +1118,10 @@ struct TermWinVtable {
      * redraw it in different colours). */
     void (*draw_cursor)(TermWin *, int x, int y, wchar_t *text, int len,
                         unsigned long attrs, int line_attrs, truecolour tc);
+    /* Draw the sigil indicating that a line of text has come from
+     * PuTTY itself rather than the far end (defence against end-of-
+     * authentication spoofing) */
+    void (*draw_trust_sigil)(TermWin *, int x, int y);
     int (*char_width)(TermWin *, int uc);
     void (*free_draw_ctx)(TermWin *);
 
@@ -1117,6 +1174,8 @@ static inline void win_draw_cursor(
     TermWin *win, int x, int y, wchar_t *text, int len,
     unsigned long attrs, int line_attrs, truecolour tc)
 { win->vt->draw_cursor(win, x, y, text, len, attrs, line_attrs, tc); }
+static inline void win_draw_trust_sigil(TermWin *win, int x, int y)
+{ win->vt->draw_trust_sigil(win, x, y); }
 static inline int win_char_width(TermWin *win, int uc)
 { return win->vt->char_width(win, uc); }
 static inline void win_free_draw_ctx(TermWin *win)
@@ -1587,6 +1646,7 @@ void term_provide_logctx(Terminal *term, LogContext *logctx);
 void term_set_focus(Terminal *term, bool has_focus);
 char *term_get_ttymode(Terminal *term, const char *mode);
 int term_get_userpass_input(Terminal *term, prompts_t *p, bufchain *input);
+void term_set_trust_status(Terminal *term, bool trusted);
 
 typedef enum SmallKeypadKey {
     SKK_HOME, SKK_END, SKK_INSERT, SKK_DELETE, SKK_PGUP, SKK_PGDN,
@@ -1866,7 +1926,7 @@ bool have_ssh_host_key(const char *host, int port, const char *keytype);
  * Exports from console frontends (wincons.c, uxcons.c)
  * that aren't equivalents to things in windlg.c et al.
  */
-extern bool console_batch_mode;
+extern bool console_batch_mode, console_antispoof_prompt;
 int console_get_userpass_input(prompts_t *p);
 bool is_interactive(void);
 void console_print_error_msg(const char *prefix, const char *msg);
@@ -1937,6 +1997,7 @@ void setup_config_box(struct controlbox *b, bool midsession,
 /*
  * Exports from minibidi.c.
  */
+#define BIDI_CHAR_INDEX_NONE ((unsigned short)-1)
 typedef struct bidi_char {
     unsigned int origwc, wc;
     unsigned short index, nchars;
@@ -2027,7 +2088,7 @@ bool open_for_write_would_lose_data(const Filename *fn);
  * The reason for this is that an OS's system clock might not agree
  * exactly with the timing mechanisms it supplies to wait for a
  * given interval. I'll illustrate this by the simple example of
- * Unix Plink, which uses timeouts to select() in a way which for
+ * Unix Plink, which uses timeouts to poll() in a way which for
  * these purposes can simply be considered to be a wait() function.
  * Suppose, for the sake of argument, that this wait() function
  * tends to return early by 1%. Then a possible sequence of actions
@@ -2099,12 +2160,12 @@ unsigned long timing_last_clock(void);
  * instead request notifications when a callback is available, so that
  * it knows to ask its delegate event loop to do the same thing. Also,
  * if a front end needs to know whether a callback is pending without
- * actually running it (e.g. so as to put a zero timeout on a select()
+ * actually running it (e.g. so as to put a zero timeout on a poll()
  * call) then it can call toplevel_callback_pending(), which will
  * return true if at least one callback is in the queue.
  *
  * run_toplevel_callbacks() returns true if it ran any actual code.
- * This can be used as a means of speculatively terminating a select
+ * This can be used as a means of speculatively terminating a poll
  * loop, as in PSFTP, for example - if a callback has run then perhaps
  * it might have done whatever the loop's caller was waiting for.
  */
@@ -2146,10 +2207,12 @@ void request_callback_notifications(toplevel_callback_notify_fn_t notify,
 #endif
 
 /* SURROGATE PAIR */
+#ifndef HIGH_SURROGATE_START /* in some toolchains <winnls.h> defines these */
 #define HIGH_SURROGATE_START 0xd800
 #define HIGH_SURROGATE_END 0xdbff
 #define LOW_SURROGATE_START 0xdc00
 #define LOW_SURROGATE_END 0xdfff
+#endif
 
 /* These macros exist in the Windows API, so the environment may
  * provide them. If not, define them in terms of the above. */

+ 68 - 0
source/putty/puttymem.h

@@ -48,6 +48,63 @@ void safefree(void *);
 #define snew_plus(type, extra) ((type *)snmalloc(1, sizeof(type) + (extra)))
 #define snew_plus_get_aux(ptr) ((void *)((ptr) + 1))
 
+/*
+ * Helper macros to deal with the common use case of growing an array.
+ *
+ * The common setup is that 'array' is a pointer to the first element
+ * of a dynamic array of some type, and 'size' represents the current
+ * allocated size of that array (in elements). Both of those macro
+ * parameters are implicitly written back to.
+ *
+ * Then sgrowarray(array, size, n) means: make sure the nth element of
+ * the array exists (i.e. the size is at least n+1). You call that
+ * before writing to the nth element, if you're looping round
+ * appending to the array.
+ *
+ * If you need to grow the array by more than one element, you can
+ * instead call sgrowarrayn(array, size, n, m), which will ensure the
+ * size of the array is at least n+m. (So sgrowarray is just the
+ * special case of that in which m == 1.)
+ *
+ * It's common to call sgrowarrayn with one of n,m equal to the
+ * previous logical length of the array, and the other equal to the
+ * new number of logical entries you want to add, so that n <= size on
+ * entry. But that's not actually a mandatory precondition: the two
+ * length parameters are just arbitrary integers that get added
+ * together with an initial check for overflow, and the semantics are
+ * simply 'make sure the array is big enough to take their sum, no
+ * matter how big it was to start with'.)
+ *
+ * Another occasionally useful idiom is to call sgrowarray with n ==
+ * size, i.e. sgrowarray(array, size, size). That just means: make
+ * array bigger by _some_ amount, I don't particularly mind how much.
+ * You might use that style if you were repeatedly calling an API
+ * function outside your control, which would either fill your buffer
+ * and return success, or else return a 'too big' error without
+ * telling you how much bigger it needed to be.
+ *
+ * The _nm variants of the macro set the 'private' flag in the
+ * underlying function, which forces array resizes to be done by a
+ * manual allocate/copy/free instead of realloc, with careful clearing
+ * of the previous memory block before we free it. This costs
+ * performance, but if the block contains important secrets such as
+ * private keys or passwords, it avoids the risk that a realloc that
+ * moves the memory block might leave a copy of the data visible in
+ * the freed memory at the previous location.
+ */
+void *safegrowarray(void *array, size_t *size, size_t eltsize,
+                    size_t oldlen, size_t extralen, bool private);
+
+/* The master macro wrapper, of which all others are special cases */
+#define sgrowarray_general(array, size, n, m, priv)                     \
+    ((array) = safegrowarray(array, &(size), sizeof(*array), n, m, priv))
+
+/* The special-case macros that are easier to use in most situations */
+#define sgrowarrayn(   a, s, n, m) sgrowarray_general(a, s, n, m, false)
+#define sgrowarray(    a, s, n   ) sgrowarray_general(a, s, n, 1, false)
+#define sgrowarrayn_nm(a, s, n, m) sgrowarray_general(a, s, n, m, true )
+#define sgrowarray_nm( a, s, n   ) sgrowarray_general(a, s, n, 1, true )
+
 /*
  * This function is called by the innermost safemalloc/saferealloc
  * functions when allocation fails. Usually it's provided by misc.c
@@ -57,4 +114,15 @@ void safefree(void *);
  */
 NORETURN void out_of_memory(void);
 
+#ifdef MINEFIELD
+/*
+ * Definitions for Minefield, PuTTY's own Windows-specific malloc
+ * debugger in the style of Electric Fence. Implemented in winmisc.c,
+ * and referred to by the main malloc wrappers in memory.c.
+ */
+void *minefield_c_malloc(size_t size);
+void minefield_c_free(void *p);
+void *minefield_c_realloc(void *p, size_t size);
+#endif
+
 #endif

+ 5 - 8
source/putty/ssh.c

@@ -715,7 +715,7 @@ static const char *connect_to_host(
              * behave in quite the usual way. */
             const char *msg =
                 "Reusing a shared connection to this server.\r\n";
-            seat_stderr(ssh->seat, msg, strlen(msg));
+            seat_stderr_pl(ssh->seat, ptrlen_from_asciz(msg));
         }
     } else {
         /*
@@ -900,6 +900,8 @@ static void ssh_free(Backend *be)
 	ssh_gss_cleanup(ssh->gss_state.libs);
 #endif
 
+    delete_callbacks_for_context(ssh); /* likely to catch ic_out_raw */
+
     need_random_unref = ssh->need_random_unref;
     sfree(ssh);
 
@@ -981,7 +983,7 @@ static void ssh_size(Backend *be, int width, int height)
 
 struct ssh_add_special_ctx {
     SessionSpecial *specials;
-    int nspecials, specials_size;
+    size_t nspecials, specials_size;
 };
 
 static void ssh_add_special(void *vctx, const char *text,
@@ -990,12 +992,7 @@ static void ssh_add_special(void *vctx, const char *text,
     struct ssh_add_special_ctx *ctx = (struct ssh_add_special_ctx *)vctx;
     SessionSpecial *spec;
 
-    if (ctx->nspecials >= ctx->specials_size) {
-        ctx->specials_size = ctx->nspecials * 5 / 4 + 32;
-        ctx->specials = sresize(ctx->specials, ctx->specials_size,
-                                SessionSpecial);
-    }
-
+    sgrowarray(ctx->specials, ctx->specials_size, ctx->nspecials);
     spec = &ctx->specials[ctx->nspecials++];
     spec->name = text;
     spec->code = code;

+ 4 - 4
source/putty/ssh.h

@@ -63,12 +63,12 @@ typedef struct PktIn {
 } PktIn;
 
 typedef struct PktOut {
-    long prefix;            /* bytes up to and including type field */
-    long length;            /* total bytes, including prefix */
+    size_t prefix;          /* bytes up to and including type field */
+    size_t length;          /* total bytes, including prefix */
     int type;
-    long minlen;            /* SSH-2: ensure wire length is at least this */
+    size_t minlen;          /* SSH-2: ensure wire length is at least this */
     unsigned char *data;    /* allocated storage */
-    long maxlen;	    /* amount of storage allocated for `data' */
+    size_t maxlen;          /* amount of storage allocated for `data' */
 
     /* Extra metadata used in SSH packet logging mode, allowing us to
      * log in the packet header line that the packet came from a

+ 64 - 35
source/putty/ssh1login.c

@@ -57,6 +57,9 @@ struct ssh1_login_state {
     RSAKey servkey, hostkey;
     bool want_user_input;
 
+    StripCtrlChars *tis_scc;
+    bool tis_scc_initialised;
+
     PacketProtocolLayer ppl;
 };
 
@@ -135,6 +138,8 @@ static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
     return pq_pop(s->ppl.in_pq);
 }
 
+static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s);
+
 static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
 {
     struct ssh1_login_state *s =
@@ -383,6 +388,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
     if ((s->username = get_remote_username(s->conf)) == NULL) {
         s->cur_prompt = new_prompts();
         s->cur_prompt->to_server = true;
+        s->cur_prompt->from_server = false;
         s->cur_prompt->name = dupstr("SSH login name");
         add_prompt(s->cur_prompt, dupstr("login as: "), true);
         s->userpass_ret = seat_get_userpass_input(
@@ -641,6 +647,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 } else {
                     s->cur_prompt = new_prompts(s->ppl.seat);
                     s->cur_prompt->to_server = false;
+                    s->cur_prompt->from_server = false;
                     s->cur_prompt->name = dupstr("SSH key passphrase");
                     add_prompt(s->cur_prompt,
                                dupprintf("Passphrase for key \"%s\": ",
@@ -782,6 +789,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
         if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
             (s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
             !s->tis_auth_refused) {
+            ssh1_login_setup_tis_scc(s);
             s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
             ppl_logevent("Requested TIS authentication");
             pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
@@ -794,10 +802,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 s->tis_auth_refused = true;
                 continue;
             } else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
-                ptrlen challenge;
-                char *instr_suf, *prompt;
-
-                challenge = get_string(pktin);
+                ptrlen challenge = get_string(pktin);
                 if (get_err(pktin)) {
                     ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
                                     "badly formed");
@@ -805,22 +810,30 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 }
                 ppl_logevent("Received TIS challenge");
                 s->cur_prompt->to_server = true;
+                s->cur_prompt->from_server = true;
                 s->cur_prompt->name = dupstr("SSH TIS authentication");
-                /* Prompt heuristic comes from OpenSSH */
-                if (!memchr(challenge.ptr, '\n', challenge.len)) {
-                    instr_suf = dupstr("");
-                    prompt = mkstr(challenge);
+
+                strbuf *sb = strbuf_new();
+                put_datapl(sb, PTRLEN_LITERAL("\
+-- TIS authentication challenge from server: ---------------------------------\
+\r\n"));
+                if (s->tis_scc) {
+                    stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
+                    put_datapl(s->tis_scc, challenge);
+                    stripctrl_retarget(s->tis_scc, NULL);
                 } else {
-                    instr_suf = mkstr(challenge);
-                    prompt = dupstr("Response: ");
+                    put_datapl(sb, challenge);
                 }
-                s->cur_prompt->instruction =
-                    dupprintf("Using TIS authentication.%s%s",
-                              (*instr_suf) ? "\n" : "",
-                              instr_suf);
+                if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
+                    put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+                put_datapl(sb, PTRLEN_LITERAL("\
+-- End of TIS authentication challenge from server: --------------------------\
+\r\n"));
+
+                s->cur_prompt->instruction = strbuf_to_str(sb);
                 s->cur_prompt->instr_reqd = true;
-                add_prompt(s->cur_prompt, prompt, false);
-                sfree(instr_suf);
+                add_prompt(s->cur_prompt, dupstr(
+                               "TIS authentication response: "), false);
             } else {
                 ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
                                 " in response to TIS authentication, "
@@ -831,6 +844,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
         } else if (conf_get_bool(s->conf, CONF_try_tis_auth) &&
             (s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
             !s->ccard_auth_refused) {
+            ssh1_login_setup_tis_scc(s);
             s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
             ppl_logevent("Requested CryptoCard authentication");
             pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
@@ -842,10 +856,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 s->ccard_auth_refused = true;
                 continue;
             } else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
-                ptrlen challenge;
-                char *instr_suf, *prompt;
-
-                challenge = get_string(pktin);
+                ptrlen challenge = get_string(pktin);
                 if (get_err(pktin)) {
                     ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
                                     "was badly formed");
@@ -853,23 +864,30 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 }
                 ppl_logevent("Received CryptoCard challenge");
                 s->cur_prompt->to_server = true;
+                s->cur_prompt->from_server = true;
                 s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
-                s->cur_prompt->name_reqd = false;
-                /* Prompt heuristic comes from OpenSSH */
-                if (!memchr(challenge.ptr, '\n', challenge.len)) {
-                    instr_suf = dupstr("");
-                    prompt = mkstr(challenge);
+
+                strbuf *sb = strbuf_new();
+                put_datapl(sb, PTRLEN_LITERAL("\
+-- CryptoCard authentication challenge from server: --------------------------\
+\r\n"));
+                if (s->tis_scc) {
+                    stripctrl_retarget(s->tis_scc, BinarySink_UPCAST(sb));
+                    put_datapl(s->tis_scc, challenge);
+                    stripctrl_retarget(s->tis_scc, NULL);
                 } else {
-                    instr_suf = mkstr(challenge);
-                    prompt = dupstr("Response: ");
+                    put_datapl(sb, challenge);
                 }
-                s->cur_prompt->instruction =
-                    dupprintf("Using CryptoCard authentication.%s%s",
-                              (*instr_suf) ? "\n" : "",
-                              instr_suf);
+                if (!ptrlen_endswith(challenge, PTRLEN_LITERAL("\n"), NULL))
+                    put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+                put_datapl(sb, PTRLEN_LITERAL("\
+-- End of CryptoCard authentication challenge from server: -------------------\
+\r\n"));
+
+                s->cur_prompt->instruction = strbuf_to_str(sb);
                 s->cur_prompt->instr_reqd = true;
-                add_prompt(s->cur_prompt, prompt, false);
-                sfree(instr_suf);
+                add_prompt(s->cur_prompt, dupstr(
+                               "CryptoCard authentication response: "), false);
             } else {
                 ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
                                 " in response to TIS authentication, "
@@ -885,6 +903,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                 return;
             }
             s->cur_prompt->to_server = true;
+            s->cur_prompt->from_server = false;
             s->cur_prompt->name = dupstr("SSH password");
             add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
                                                 s->username, s->savedhost),
@@ -984,7 +1003,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                         put_stringz(pkt, s->cur_prompt->prompts[0]->result);
                         pq_push(s->ppl.out_pq, pkt);
                     } else {
-                        strbuf *random_data = strbuf_new();
+                        strbuf *random_data = strbuf_new_nm();
                         random_read(strbuf_append(random_data, i), i);
 
                         pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
@@ -1000,7 +1019,7 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
                  * but can deal with padded passwords, so we
                  * can use the secondary defence.
                  */
-                strbuf *padded_pw = strbuf_new();
+                strbuf *padded_pw = strbuf_new_nm();
 
                 ppl_logevent("Sending length-padded password");
                 pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
@@ -1080,6 +1099,16 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
     crFinishV;
 }
 
+static void ssh1_login_setup_tis_scc(struct ssh1_login_state *s)
+{
+    if (s->tis_scc_initialised)
+        return;
+    s->tis_scc = seat_stripctrl_new(s->ppl.seat, NULL, SIC_KI_PROMPTS);
+    if (s->tis_scc)
+        stripctrl_enable_line_limiting(s->tis_scc);
+    s->tis_scc_initialised = true;
+}
+
 static void ssh1_login_dialog_callback(void *loginv, int ret)
 {
     struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;

+ 7 - 0
source/putty/ssh2bpp.c

@@ -645,6 +645,13 @@ static void ssh2_bpp_handle_input(BinaryPacketProtocol *bpp)
     }
 
   eof:
+    /*
+     * We've seen EOF. But we might have pushed stuff on the outgoing
+     * packet queue first, and that stuff _might_ include a DISCONNECT
+     * message, in which case we'd like to use that as the diagnostic.
+     * So first wait for the queue to have been processed.
+     */
+    crMaybeWaitUntilV(!pq_peek(&s->bpp.in_pq));
     if (!s->bpp.expect_close) {
         ssh_remote_error(s->bpp.ssh,
                          "Remote side unexpectedly closed network connection");

+ 5 - 0
source/putty/ssh2connection-client.c

@@ -474,3 +474,8 @@ void ssh2channel_send_terminal_size_change(SshChannel *sc, int w, int h)
     put_uint32(pktout, 0);	       /* pixel height */
     pq_push(s->ppl.out_pq, pktout);
 }
+
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s)
+{
+    return !seat_set_trust_status(s->ppl.seat, false);
+}

+ 60 - 23
source/putty/ssh2connection.c

@@ -953,6 +953,41 @@ static void ssh2_connection_process_queue(PacketProtocolLayer *ppl)
     if (s->connshare)
         share_activate(s->connshare, s->peer_verstring);
 
+    /*
+     * Signal the seat that authentication is done, so that it can
+     * deploy spoofing defences. If it doesn't have any, deploy our
+     * own fallback one.
+     *
+     * We do this here rather than at the end of userauth, because we
+     * might not have gone through userauth at all (if we're a
+     * connection-sharing downstream).
+     */
+    if (ssh2_connection_need_antispoof_prompt(s)) {
+        s->antispoof_prompt = new_prompts();
+        s->antispoof_prompt->to_server = true;
+        s->antispoof_prompt->from_server = false;
+        s->antispoof_prompt->name = dupstr("Authentication successful");
+        add_prompt(
+            s->antispoof_prompt,
+            dupstr("Access granted. Press Return to begin session. "), false);
+        s->antispoof_ret = seat_get_userpass_input(
+            s->ppl.seat, s->antispoof_prompt, NULL);
+        while (1) {
+            while (s->antispoof_ret < 0 &&
+                   bufchain_size(s->ppl.user_input) > 0)
+                s->antispoof_ret = seat_get_userpass_input(
+                    s->ppl.seat, s->antispoof_prompt, s->ppl.user_input);
+
+            if (s->antispoof_ret >= 0)
+                break;
+
+            s->want_user_input = true;
+            crReturnV;
+            s->want_user_input = false;
+        }
+        free_prompts(s->antispoof_prompt);
+    }
+
     /*
      * Enable port forwardings.
      */
@@ -1060,30 +1095,32 @@ static size_t ssh2_try_send(struct ssh2_channel *c)
     PktOut *pktout;
     size_t bufsize;
 
-    while (c->remwindow > 0 &&
-           (bufchain_size(&c->outbuffer) > 0 ||
-            bufchain_size(&c->errbuffer) > 0)) {
-        bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ?
-                         &c->errbuffer : &c->outbuffer);
-
-	ptrlen data = bufchain_prefix(buf);
-	if (data.len > c->remwindow)
-	    data.len = c->remwindow;
-	if (data.len > c->remmaxpkt)
-	    data.len = c->remmaxpkt;
-        if (buf == &c->errbuffer) {
-            pktout = ssh_bpp_new_pktout(
-                s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA);
-            put_uint32(pktout, c->remoteid);
-            put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR);
-        } else {
-            pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA);
-            put_uint32(pktout, c->remoteid);
+    if (!c->halfopen) {
+        while (c->remwindow > 0 &&
+               (bufchain_size(&c->outbuffer) > 0 ||
+                bufchain_size(&c->errbuffer) > 0)) {
+            bufchain *buf = (bufchain_size(&c->errbuffer) > 0 ?
+                             &c->errbuffer : &c->outbuffer);
+
+            ptrlen data = bufchain_prefix(buf);
+            if (data.len > c->remwindow)
+                data.len = c->remwindow;
+            if (data.len > c->remmaxpkt)
+                data.len = c->remmaxpkt;
+            if (buf == &c->errbuffer) {
+                pktout = ssh_bpp_new_pktout(
+                    s->ppl.bpp, SSH2_MSG_CHANNEL_EXTENDED_DATA);
+                put_uint32(pktout, c->remoteid);
+                put_uint32(pktout, SSH2_EXTENDED_DATA_STDERR);
+            } else {
+                pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH2_MSG_CHANNEL_DATA);
+                put_uint32(pktout, c->remoteid);
+            }
+            put_stringpl(pktout, data);
+            pq_push(s->ppl.out_pq, pktout);
+            bufchain_consume(buf, data.len);
+            c->remwindow -= data.len;
         }
-        put_stringpl(pktout, data);
-        pq_push(s->ppl.out_pq, pktout);
-	bufchain_consume(buf, data.len);
-	c->remwindow -= data.len;
     }
 
     /*

+ 5 - 0
source/putty/ssh2connection.h

@@ -37,6 +37,9 @@ struct ssh2_connection_state {
     PortFwdManager *portfwdmgr;
     bool portfwdmgr_configured;
 
+    prompts_t *antispoof_prompt;
+    int antispoof_ret;
+
     const SftpServerVtable *sftpserver_vt;
 
     /*
@@ -228,4 +231,6 @@ ChanopenResult ssh2_connection_parse_channel_open(
 bool ssh2_connection_parse_global_request(
     struct ssh2_connection_state *s, ptrlen type, PktIn *pktin);
 
+bool ssh2_connection_need_antispoof_prompt(struct ssh2_connection_state *s);
+
 #endif /* PUTTY_SSH2CONNECTION_H */

+ 24 - 1
source/putty/ssh2kex-client.c

@@ -474,6 +474,15 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
                  s->gss_stat == SSH_GSS_S_CONTINUE_NEEDED ||
                  !s->complete_rcvd);
 
+        {
+            const char *err = dh_validate_f(s->dh_ctx, s->f);
+            if (err) {
+                ssh_proto_error(s->ppl.ssh, "GSSAPI reply failed "
+                                "validation: %s", err);
+                *aborted = true;
+                return;
+            }
+        }
         s->K = dh_find_K(s->dh_ctx, s->f);
 
         /* We assume everything from now on will be quick, and it might
@@ -554,7 +563,21 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
          */
         {
             int klen = ssh_rsakex_klen(s->rsa_kex_key);
+
+            const struct ssh_rsa_kex_extra *extra =
+                (const struct ssh_rsa_kex_extra *)s->kex_alg->extra;
+            if (klen < extra->minklen) {
+                ssh_proto_error(s->ppl.ssh, "Server sent %d-bit RSA key, "
+                                "less than the minimum size %d for %s "
+                                "key exchange", klen, extra->minklen,
+                                s->kex_alg->name);
+                *aborted = true;
+                return;
+            }
+
             int nbits = klen - (2*s->kex_alg->hash->hlen*8 + 49);
+            assert(nbits > 0);
+
             strbuf *buf, *outstr;
 
             mp_int *tmp = mp_random_bits(nbits - 1);
@@ -565,7 +588,7 @@ void ssh2kex_coroutine(struct ssh2_transport_state *s, bool *aborted)
             /*
              * Encode this as an mpint.
              */
-            buf = strbuf_new();
+            buf = strbuf_new_nm();
             put_mp_ssh2(buf, s->K);
 
             /*

+ 11 - 9
source/putty/ssh2transport.c

@@ -347,7 +347,7 @@ bool ssh2_common_filter_queue(PacketProtocolLayer *ppl)
                 ((reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
                  ssh2_disconnect_reasons[reason] : "unknown"),
                 PTRLEN_PRINTF(msg));
-            pq_pop(ppl->in_pq);
+            /* don't try to pop the queue, because we've been freed! */
             return true;               /* indicate that we've been freed */
 
           case SSH2_MSG_DEBUG:
@@ -408,7 +408,8 @@ static bool ssh2_transport_filter_queue(struct ssh2_transport_state *s)
 
 PktIn *ssh2_transport_pop(struct ssh2_transport_state *s)
 {
-    ssh2_transport_filter_queue(s);
+    if (ssh2_transport_filter_queue(s))
+        return NULL;   /* we've been freed */
     return pq_pop(s->ppl.in_pq);
 }
 
@@ -988,7 +989,8 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * from, even if we're _not_ looping on pq_pop. That way we can
      * still proactively handle those messages even if we're waiting
      * for a user response. */
-    ssh2_transport_filter_queue(s);
+    if (ssh2_transport_filter_queue(s))
+        return;   /* we've been freed */
 
     crBegin(s->crState);
 
@@ -1283,9 +1285,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * session keys.
      */
     {
-        strbuf *cipher_key = strbuf_new();
-        strbuf *cipher_iv = strbuf_new();
-        strbuf *mac_key = strbuf_new();
+        strbuf *cipher_key = strbuf_new_nm();
+        strbuf *cipher_iv = strbuf_new_nm();
+        strbuf *mac_key = strbuf_new_nm();
 
         if (s->out.cipher) {
             ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,
@@ -1338,9 +1340,9 @@ static void ssh2_transport_process_queue(PacketProtocolLayer *ppl)
      * incoming session keys.
      */
     {
-        strbuf *cipher_key = strbuf_new();
-        strbuf *cipher_iv = strbuf_new();
-        strbuf *mac_key = strbuf_new();
+        strbuf *cipher_key = strbuf_new_nm();
+        strbuf *cipher_iv = strbuf_new_nm();
+        strbuf *mac_key = strbuf_new_nm();
 
         if (s->in.cipher) {
             ssh2_mkkey(s, cipher_iv, s->K, s->exchange_hash,

+ 168 - 51
source/putty/ssh2userauth.c

@@ -77,6 +77,13 @@ struct ssh2_userauth_state {
 
     agent_pending_query *auth_agent_query;
     bufchain banner;
+    bufchain_sink banner_bs;
+    StripCtrlChars *banner_scc;
+    bool banner_scc_initialised;
+
+    StripCtrlChars *ki_scc;
+    bool ki_scc_initialised;
+    bool ki_printed_header;
 
     PacketProtocolLayer ppl;
 };
@@ -101,6 +108,8 @@ static void ssh2_userauth_add_session_id(
 static PktOut *ssh2_userauth_gss_packet(
     struct ssh2_userauth_state *s, const char *authtype);
 #endif
+static void ssh2_userauth_antispoof_msg(
+    struct ssh2_userauth_state *s, const char *msg);
 
 static const struct PacketProtocolLayerVtable ssh2_userauth_vtable = {
     ssh2_userauth_free,
@@ -139,6 +148,7 @@ PacketProtocolLayer *ssh2_userauth_new(
     s->shgss = shgss;
     s->last_methods_string = strbuf_new();
     bufchain_init(&s->banner);
+    bufchain_sink_init(&s->banner_bs, &s->banner);
 
     return &s->ppl;
 }
@@ -168,6 +178,10 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl)
     sfree(s->hostname);
     sfree(s->fullhostname);
     strbuf_free(s->last_methods_string);
+    if (s->banner_scc)
+        stripctrl_free(s->banner_scc);
+    if (s->ki_scc)
+        stripctrl_free(s->ki_scc);
     sfree(s);
 }
 
@@ -182,7 +196,17 @@ static void ssh2_userauth_filter_queue(struct ssh2_userauth_state *s)
             string = get_string(pktin);
             if (string.len > BANNER_LIMIT - bufchain_size(&s->banner))
                 string.len = BANNER_LIMIT - bufchain_size(&s->banner);
-	    sanitise_term_data(&s->banner, string.ptr, string.len);
+            if (!s->banner_scc_initialised) {
+                s->banner_scc = seat_stripctrl_new(
+                    s->ppl.seat, BinarySink_UPCAST(&s->banner_bs), SIC_BANNER);
+                if (s->banner_scc)
+                    stripctrl_enable_line_limiting(s->banner_scc);
+                s->banner_scc_initialised = true;
+            }
+            if (s->banner_scc)
+                put_datapl(s->banner_scc, string);
+            else
+                put_datapl(&s->banner_bs, string);
             pq_pop(s->ppl.in_pq);
             break;
 
@@ -367,6 +391,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
         } else if ((s->username = s->default_username) == NULL) {
             s->cur_prompt = new_prompts();
             s->cur_prompt->to_server = true;
+            s->cur_prompt->from_server = false;
             s->cur_prompt->name = dupstr("SSH login name");
             add_prompt(s->cur_prompt, dupstr("login as: "), true); 
             s->userpass_ret = seat_get_userpass_input(
@@ -447,30 +472,45 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
              * that we've accumulated. (This should ensure that when
              * we exit the auth loop, we haven't any left to deal
              * with.)
+             *
+             * Don't show the banner if we're operating in non-verbose
+             * non-interactive mode. (It's probably a script, which
+             * means nobody will read the banner _anyway_, and
+             * moreover the printing of the banner will screw up
+             * processing on the output of (say) plink.)
+             *
+             * The banner data has been sanitised already by this
+             * point, but we still need to precede and follow it with
+             * anti-spoofing header lines.
              */
-            {
-                /*
-                 * Don't show the banner if we're operating in
-                 * non-verbose non-interactive mode. (It's probably
-                 * a script, which means nobody will read the
-                 * banner _anyway_, and moreover the printing of
-                 * the banner will screw up processing on the
-                 * output of (say) plink.)
-                 *
-                 * The banner data has been sanitised already by this
-                 * point, so we can safely pass it straight to
-                 * seat_stderr.
-                 */
-                if (bufchain_size(&s->banner) &&
-                    (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
-                    while (bufchain_size(&s->banner) > 0) {
-                        ptrlen data = bufchain_prefix(&s->banner);
-                        seat_stderr(s->ppl.seat, data.ptr, data.len);
-                        bufchain_consume(&s->banner, data.len);
-                    }
+            if (bufchain_size(&s->banner) &&
+                (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+                if (s->banner_scc) {
+                    ssh2_userauth_antispoof_msg(
+                        s, "Pre-authentication banner message from server:");
+                    seat_set_trust_status(s->ppl.seat, false);
+                }
+
+                bool mid_line = false;
+                while (bufchain_size(&s->banner) > 0) {
+                    ptrlen data = bufchain_prefix(&s->banner);
+                    seat_stderr_pl(s->ppl.seat, data);
+                    bufchain_consume(&s->banner, data.len);
+                    mid_line =
+                        (((const char *)data.ptr)[data.len-1] != '\n');
                 }
                 bufchain_clear(&s->banner);
+
+                if (mid_line)
+                    seat_stderr_pl(s->ppl.seat, PTRLEN_LITERAL("\r\n"));
+
+                if (s->banner_scc) {
+                    seat_set_trust_status(s->ppl.seat, true);
+                    ssh2_userauth_antispoof_msg(
+                        s, "End of banner message from server");
+                }
             }
+
             if (pktin && pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                 ppl_logevent("Access granted");
                 goto userauth_success;
@@ -793,6 +833,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                          */
                         s->cur_prompt = new_prompts();
                         s->cur_prompt->to_server = false;
+                        s->cur_prompt->from_server = false;
                         s->cur_prompt->name = dupstr("SSH key passphrase");
                         add_prompt(s->cur_prompt,
                                    dupprintf("Passphrase for key \"%s\": ",
@@ -1143,6 +1184,14 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                 
                 ppl_logevent("Attempting keyboard-interactive authentication");
 
+                if (!s->ki_scc_initialised) {
+                    s->ki_scc = seat_stripctrl_new(
+                        s->ppl.seat, NULL, SIC_KI_PROMPTS);
+                    if (s->ki_scc)
+                        stripctrl_enable_line_limiting(s->ki_scc);
+                    s->ki_scc_initialised = true;
+                }
+
                 crMaybeWaitUntilV((pktin = ssh2_userauth_pop(s)) != NULL);
                 if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
                     /* Server is not willing to do keyboard-interactive
@@ -1155,12 +1204,15 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     continue;
                 }
 
+                s->ki_printed_header = false;
+
                 /*
                  * Loop while the server continues to send INFO_REQUESTs.
                  */
                 while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
 
                     ptrlen name, inst;
+                    strbuf *sb;
                     int i;
 
                     /*
@@ -1172,58 +1224,86 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
                     get_string(pktin); /* skip language tag */
                     s->cur_prompt = new_prompts();
                     s->cur_prompt->to_server = true;
+                    s->cur_prompt->from_server = true;
 
                     /*
                      * Get any prompt(s) from the packet.
                      */
                     s->num_prompts = get_uint32(pktin);
                     for (i = 0; i < s->num_prompts; i++) {
-                        ptrlen prompt;
-                        bool echo;
-                        static char noprompt[] =
-                            "<server failed to send prompt>: ";
+                        ptrlen prompt = get_string(pktin);
+                        bool echo = get_bool(pktin);
 
-                        prompt = get_string(pktin);
-                        echo = get_bool(pktin);
+                        sb = strbuf_new();
                         if (!prompt.len) {
-                            prompt.ptr = noprompt;
-                            prompt.len = lenof(noprompt)-1;
+                            put_datapl(sb, PTRLEN_LITERAL(
+                                "<server failed to send prompt>: "));
+                        } else if (s->ki_scc) {
+                            stripctrl_retarget(
+                                s->ki_scc, BinarySink_UPCAST(sb));
+                            put_datapl(s->ki_scc, prompt);
+                            stripctrl_retarget(s->ki_scc, NULL);
+                        } else {
+                            put_datapl(sb, prompt);
                         }
-                        add_prompt(s->cur_prompt, mkstr(prompt), echo);
+                        add_prompt(s->cur_prompt, strbuf_to_str(sb), echo);
                     }
 
+                    /*
+                     * Make the header strings. This includes the
+                     * 'name' (optional dialog-box title) and
+                     * 'instruction' from the server.
+                     *
+                     * First, display our disambiguating header line
+                     * if this is the first time round the loop -
+                     * _unless_ the server has sent a completely empty
+                     * k-i packet with no prompts _or_ text, which
+                     * apparently some do. In that situation there's
+                     * no need to alert the user that the following
+                     * text is server- supplied, because, well, _what_
+                     * text?
+                     *
+                     * We also only do this if we got a stripctrl,
+                     * because if we didn't, that suggests this is all
+                     * being done via dialog boxes anyway.
+                     */
+                    if (!s->ki_printed_header && s->ki_scc &&
+                        (s->num_prompts || name.len || inst.len)) {
+                        ssh2_userauth_antispoof_msg(
+                            s, "Keyboard-interactive authentication "
+                            "prompts from server:");
+                        s->ki_printed_header = true;
+                        seat_set_trust_status(s->ppl.seat, false);
+                    }
+
+                    sb = strbuf_new();
                     if (name.len) {
-                        /* FIXME: better prefix to distinguish from
-                         * local prompts? */
-                        s->cur_prompt->name =
-                            dupprintf("SSH server: %.*s", PTRLEN_PRINTF(name));
+                        stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
+                        put_datapl(s->ki_scc, name);
+                        stripctrl_retarget(s->ki_scc, NULL);
+
                         s->cur_prompt->name_reqd = true;
                     } else {
-                        s->cur_prompt->name =
-                            dupstr("SSH server authentication");
+                        put_datapl(sb, PTRLEN_LITERAL(
+                            "SSH server authentication"));
                         s->cur_prompt->name_reqd = false;
                     }
-                    /* We add a prefix to try to make it clear that a prompt
-                     * has come from the server.
-                     * FIXME: ugly to print "Using..." in prompt _every_
-                     * time round. Can this be done more subtly? */
-                    /* Special case: for reasons best known to themselves,
-                     * some servers send k-i requests with no prompts and
-                     * nothing to display. Keep quiet in this case. */
-                    if (s->num_prompts || name.len || inst.len) {
-                        s->cur_prompt->instruction =
-                            dupprintf("Using keyboard-interactive "
-                                      "authentication.%s%.*s",
-                                      inst.len ? "\n" : "",
-                                      PTRLEN_PRINTF(inst));
+                    s->cur_prompt->name = strbuf_to_str(sb);
+
+                    sb = strbuf_new();
+                    if (inst.len) {
+                        stripctrl_retarget(s->ki_scc, BinarySink_UPCAST(sb));
+                        put_datapl(s->ki_scc, inst);
+                        stripctrl_retarget(s->ki_scc, NULL);
+
                         s->cur_prompt->instr_reqd = true;
                     } else {
                         s->cur_prompt->instr_reqd = false;
                     }
 
                     /*
-                     * Display any instructions, and get the user's
-                     * response(s).
+                     * Our prompts_t is fully constructed now. Get the
+                     * user's response(s).
                      */
                     s->userpass_ret = seat_get_userpass_input(
                         s->ppl.seat, s->cur_prompt, NULL);
@@ -1281,6 +1361,15 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
 
                 }
 
+                /*
+                 * Print our trailer line, if we printed a header.
+                 */
+                if (s->ki_printed_header) {
+                    seat_set_trust_status(s->ppl.seat, true);
+                    ssh2_userauth_antispoof_msg(
+                        s, "End of keyboard-interactive prompts from server");
+                }
+
                 /*
                  * We should have SUCCESS or FAILURE now.
                  */
@@ -1297,6 +1386,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
 
                 s->cur_prompt = new_prompts();
                 s->cur_prompt->to_server = true;
+                s->cur_prompt->from_server = false;
                 s->cur_prompt->name = dupstr("SSH password");
                 add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
                                                     s->username, s->hostname),
@@ -1391,6 +1481,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
 
                     s->cur_prompt = new_prompts();
                     s->cur_prompt->to_server = true;
+                    s->cur_prompt->from_server = false;
                     s->cur_prompt->name = dupstr("New SSH password");
                     s->cur_prompt->instruction = mkstr(prompt);
                     s->cur_prompt->instr_reqd = true;
@@ -1746,3 +1837,29 @@ static void ssh2_userauth_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
         container_of(ppl, struct ssh2_userauth_state, ppl);
     ssh_ppl_reconfigure(s->successor_layer, conf);
 }
+
+static void ssh2_userauth_antispoof_msg(
+    struct ssh2_userauth_state *s, const char *msg)
+{
+    strbuf *sb = strbuf_new();
+    if (seat_set_trust_status(s->ppl.seat, true)) {
+        /*
+         * If the seat can directly indicate that this message is
+         * generated by the client, then we can just use the message
+         * unmodified as an unspoofable header.
+         */
+        put_datapl(sb, ptrlen_from_asciz(msg));
+    } else {
+        /*
+         * Otherwise, add enough padding around it that the server
+         * wouldn't be able to mimic it within our line-length
+         * constraint.
+         */
+        strbuf_catf(sb, "-- %s ", msg);
+        while (sb->len < 78)
+            put_byte(sb, '-');
+    }
+    put_datapl(sb, PTRLEN_LITERAL("\r\n"));
+    seat_stderr_pl(s->ppl.seat, ptrlen_from_strbuf(sb));
+    strbuf_free(sb);
+}

+ 3 - 10
source/putty/sshcommon.c

@@ -230,18 +230,11 @@ PktOut *ssh_new_packet(void)
     return pkt;
 }
 
-static void ssh_pkt_ensure(PktOut *pkt, int length)
-{
-    if (pkt->maxlen < length) {
-        pkt->maxlen = length + 256;
-        pkt->data = sresize(pkt->data, pkt->maxlen, unsigned char);
-    }
-}
 static void ssh_pkt_adddata(PktOut *pkt, const void *data, int len)
 {
+    sgrowarrayn_nm(pkt->data, pkt->maxlen, pkt->length, len);
+    memcpy(pkt->data + pkt->length, data, len);
     pkt->length += len;
-    ssh_pkt_ensure(pkt, pkt->length);
-    memcpy(pkt->data + pkt->length - len, data, len);
 }
 
 static void ssh_pkt_BinarySink_write(BinarySink *bs,
@@ -811,7 +804,7 @@ void ssh_ppl_user_output_string_and_free(PacketProtocolLayer *ppl, char *text)
     /* Messages sent via this function are from the SSH layer, not
      * from the server-side process, so they always have the stderr
      * flag set. */
-    seat_stderr(ppl->seat, text, strlen(text));
+    seat_stderr_pl(ppl->seat, ptrlen_from_asciz(text));
     sfree(text);
 }
 

+ 23 - 2
source/putty/sshecc.c

@@ -770,7 +770,7 @@ static void eddsa_openssh_blob(ssh_key *key, BinarySink *bs)
     put_epoint(pub_sb, ek->publicKey, ek->curve, false);
     ptrlen pub = make_ptrlen(pub_sb->s + 4, pub_sb->len - 4);
 
-    strbuf *priv_sb = strbuf_new();
+    strbuf *priv_sb = strbuf_new_nm();
     put_mp_le_unsigned(priv_sb, ek->privateKey);
     ptrlen priv = make_ptrlen(priv_sb->s + 4, priv_sb->len - 4);
 
@@ -1279,7 +1279,7 @@ static void ssh_ecdhkex_w_setup(ecdh_key *dh)
 
 static void ssh_ecdhkex_m_setup(ecdh_key *dh)
 {
-    strbuf *bytes = strbuf_new();
+    strbuf *bytes = strbuf_new_nm();
     random_read(strbuf_append(bytes, dh->curve->fieldBytes),
                 dh->curve->fieldBytes);
 
@@ -1329,6 +1329,12 @@ static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey)
     if (!remote_p)
         return NULL;
 
+    if (ecc_weierstrass_is_identity(remote_p)) {
+        /* Not a sensible Diffie-Hellman input value */
+        ecc_weierstrass_point_free(remote_p);
+        return NULL;
+    }
+
     WeierstrassPoint *p = ecc_weierstrass_multiply(remote_p, dh->private);
 
     mp_int *x;
@@ -1343,6 +1349,21 @@ static mp_int *ssh_ecdhkex_w_getkey(ecdh_key *dh, ptrlen remoteKey)
 static mp_int *ssh_ecdhkex_m_getkey(ecdh_key *dh, ptrlen remoteKey)
 {
     mp_int *remote_x = mp_from_bytes_le(remoteKey);
+    if (mp_eq_integer(remote_x, 0)) {
+        /*
+         * The libssh spec for Curve25519 key exchange says that
+         * 'every possible public key maps to a valid ECC Point' and
+         * therefore no validation needs to be done on the server's
+         * provided x-coordinate. However, I don't believe it: an
+         * x-coordinate of zero doesn't work sensibly, because you end
+         * up dividing by zero in the doubling formula
+         * (x+1)^2(x-1)^2/(4(x^3+ax^2+x)). (Put another way, although
+         * that point P is not the _identity_ of the curve, it is a
+         * torsion point such that 2P is the identity.)
+         */
+        mp_free(remote_x);
+        return NULL;
+    }
     MontgomeryPoint *remote_p = ecc_montgomery_point_new(
         dh->curve->m.mc, remote_x);
     mp_free(remote_x);

+ 1 - 1
source/putty/sshhmac.c

@@ -110,7 +110,7 @@ static void hmac_key(ssh2_mac *mac, ptrlen key)
          * the underlying hash, then we start by hashing the key, and
          * use that hash as the 'true' key for the HMAC construction.
          */
-        sb = strbuf_new();
+        sb = strbuf_new_nm();
         strbuf_append(sb, ctx->hashalg->hlen);
 
         ssh_hash *htmp = ssh_hash_new(ctx->hashalg);

+ 40 - 10
source/putty/sshpubk.c

@@ -24,6 +24,18 @@
 
 static int key_type_fp(FILE *fp);
 
+/*
+ * Fairly arbitrary size limit on any public or private key blob.
+ * Chosen to match AGENT_MAX_MSGLEN, on the basis that any key too
+ * large to transfer over the ssh-agent protocol is probably too large
+ * to be useful in general.
+ *
+ * MAX_KEY_BLOB_LINES is the corresponding limit on the Public-Lines
+ * or Private-Lines header field in a key file.
+ */
+#define MAX_KEY_BLOB_SIZE 262144
+#define MAX_KEY_BLOB_LINES (MAX_KEY_BLOB_SIZE / 48)
+
 static int rsa_ssh1_load_main(FILE * fp, RSAKey *key, bool pub_only,
                               char **commentptr, const char *passphrase,
                               const char **error)
@@ -37,7 +49,7 @@ static int rsa_ssh1_load_main(FILE * fp, RSAKey *key, bool pub_only,
     *error = NULL;
 
     /* Slurp the whole file (minus the header) into a buffer. */
-    buf = strbuf_new();
+    buf = strbuf_new_nm();
     {
         int ch;
         while ((ch = fgetc(fp)) != EOF)
@@ -310,7 +322,7 @@ int rsa_ssh1_loadpub(const Filename *filename, BinarySink *bs,
 bool rsa_ssh1_savekey(const Filename *filename, RSAKey *key,
                       char *passphrase)
 {
-    strbuf *buf = strbuf_new();
+    strbuf *buf = strbuf_new_nm();
     int estart;
     FILE *fp;
 
@@ -490,7 +502,7 @@ static bool read_header(FILE * fp, char *header)
 
 static char *read_body(FILE * fp)
 {
-    strbuf *buf = strbuf_new();
+    strbuf *buf = strbuf_new_nm();
 
     while (1) {
 	int c = fgetc(fp);
@@ -514,7 +526,9 @@ static bool read_blob(FILE *fp, int nlines, BinarySink *bs)
     int i, j, k;
 
     /* We expect at most 64 base64 characters, ie 48 real bytes, per line. */
+    assert(nlines < MAX_KEY_BLOB_LINES);
     blob = snewn(48 * nlines, unsigned char);
+
     for (i = 0; i < nlines; i++) {
 	line = read_body(fp);
 	if (!line) {
@@ -584,6 +598,16 @@ static void ssh2_ppk_derivekey(ptrlen passphrase, uint8_t *key)
     ssh_hash_final(h, key + 20);
 }
 
+static int userkey_parse_line_counter(const char *text)
+{
+    char *endptr;
+    unsigned long ul = strtoul(text, &endptr, 10);
+    if (*text && !*endptr && ul < MAX_KEY_BLOB_LINES)
+        return ul;
+    else
+        return -1;
+}
+
 ssh2_userkey *ssh2_load_userkey(
     const Filename *filename, const char *passphrase, const char **errorstr)
 {
@@ -665,8 +689,10 @@ ssh2_userkey *ssh2_load_userkey(
 	goto error;
     if ((b = read_body(fp)) == NULL)
 	goto error;
-    i = atoi(b);
+    i = userkey_parse_line_counter(b);
     sfree(b);
+    if (i < 0)
+        goto error;
     public_blob = strbuf_new();
     if (!read_blob(fp, i, BinarySink_UPCAST(public_blob)))
 	goto error;
@@ -676,9 +702,11 @@ ssh2_userkey *ssh2_load_userkey(
 	goto error;
     if ((b = read_body(fp)) == NULL)
 	goto error;
-    i = atoi(b);
+    i = userkey_parse_line_counter(b);
     sfree(b);
-    private_blob = strbuf_new();
+    if (i < 0)
+        goto error;
+    private_blob = strbuf_new_nm();
     if (!read_blob(fp, i, BinarySink_UPCAST(private_blob)))
 	goto error;
 
@@ -728,7 +756,7 @@ ssh2_userkey *ssh2_load_userkey(
 	    macdata = private_blob;
 	    free_macdata = false;
 	} else {
-            macdata = strbuf_new();
+            macdata = strbuf_new_nm();
 	    put_stringz(macdata, alg->ssh_id);
 	    put_stringz(macdata, encryption);
 	    put_stringz(macdata, comment);
@@ -1106,8 +1134,10 @@ bool ssh2_userkey_loadpub(const Filename *filename, char **algorithm,
 	goto error;
     if ((b = read_body(fp)) == NULL)
 	goto error;
-    i = atoi(b);
+    i = userkey_parse_line_counter(b);
     sfree(b);
+    if (i < 0)
+        goto error;
     if (!read_blob(fp, i, bs))
 	goto error;
 
@@ -1236,7 +1266,7 @@ bool ssh2_save_userkey(
      */
     pub_blob = strbuf_new();
     ssh_key_public_blob(key->key, BinarySink_UPCAST(pub_blob));
-    priv_blob = strbuf_new();
+    priv_blob = strbuf_new_nm();
     ssh_key_private_blob(key->key, BinarySink_UPCAST(priv_blob));
 
     /*
@@ -1267,7 +1297,7 @@ bool ssh2_save_userkey(
 	unsigned char mackey[20];
 	char header[] = "putty-private-key-file-mac-key";
 
-	macdata = strbuf_new();
+	macdata = strbuf_new_nm();
 	put_stringz(macdata, ssh_key_ssh_id(key->key));
 	put_stringz(macdata, cipherstr);
 	put_stringz(macdata, key->comment);

+ 2 - 2
source/putty/sshrsa.c

@@ -197,7 +197,7 @@ mp_int *rsa_ssh1_decrypt(mp_int *input, RSAKey *key)
 bool rsa_ssh1_decrypt_pkcs1(mp_int *input, RSAKey *key,
                             strbuf *outbuf)
 {
-    strbuf *data = strbuf_new();
+    strbuf *data = strbuf_new_nm();
     bool success = false;
     BinarySource src[1];
 
@@ -872,7 +872,7 @@ strbuf *ssh_rsakex_encrypt(RSAKey *rsa, const ssh_hashalg *h, ptrlen in)
     assert(in.len > 0 && in.len <= k - 2*HLEN - 2);
 
     /* The length of the output data wants to be precisely k. */
-    strbuf *toret = strbuf_new();
+    strbuf *toret = strbuf_new_nm();
     int outlen = k;
     unsigned char *out = strbuf_append(toret, outlen);
 

+ 4 - 4
source/putty/sshshare.c

@@ -769,7 +769,7 @@ static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
             int this_len = (data.len > chan->downstream_maxpkt ?
                             chan->downstream_maxpkt : data.len);
 
-            packet = strbuf_new();
+            packet = strbuf_new_nm();
             put_uint32(packet, 0);     /* placeholder for length field */
             put_byte(packet, type);
             put_uint32(packet, channel);
@@ -785,7 +785,7 @@ static void send_packet_to_downstream(struct ssh_sharing_connstate *cs,
         /*
          * Just do the obvious thing.
          */
-        packet = strbuf_new();
+        packet = strbuf_new_nm();
         put_uint32(packet, 0);     /* placeholder for length field */
         put_byte(packet, type);
         put_data(packet, pkt, pktlen);
@@ -1122,7 +1122,7 @@ void share_setup_x11_channel(ssh_sharing_connstate *cs, share_channel *chan,
                                  chan->x11_auth_proto,
                                  chan->x11_auth_data, chan->x11_auth_datalen,
                                  peer_addr, peer_port, &greeting_len);
-    packet = strbuf_new();
+    packet = strbuf_new_nm();
     put_uint32(packet, 0); /* leave the channel id field unfilled - we
                             * don't know the downstream id yet */
     put_uint32(packet, greeting_len + initial_len);
@@ -1691,7 +1691,7 @@ static void share_got_pkt_from_downstream(struct ssh_sharing_connstate *cs,
                  * containing our own auth data, and send that to the
                  * server.
                  */
-                packet = strbuf_new();
+                packet = strbuf_new_nm();
                 put_uint32(packet, server_id);
                 put_stringz(packet, "x11-req");
                 put_bool(packet, want_reply);

+ 2 - 2
source/putty/sshzlib.c

@@ -615,7 +615,7 @@ void zlib_compress_block(ssh_compressor *sc,
     bool in_block;
 
     assert(!out->outbuf);
-    out->outbuf = strbuf_new();
+    out->outbuf = strbuf_new_nm();
 
     /*
      * If this is the first block, output the Zlib (RFC1950) header
@@ -955,7 +955,7 @@ bool zlib_decompress_block(ssh_decompressor *dc,
     };
 
     assert(!dctx->outblk);
-    dctx->outblk = strbuf_new();
+    dctx->outblk = strbuf_new_nm();
 
     while (len > 0 || dctx->nbits > 0) {
 	while (dctx->nbits < 24 && len > 0) {

+ 85 - 75
source/putty/utils.c

@@ -207,11 +207,21 @@ char *host_strduptrim(const char *s)
                 break;
             p++;
         }
+        if (*p == '%') {
+            /*
+             * This delimiter character introduces an RFC 4007 scope
+             * id suffix (e.g. suffixing the address literal with
+             * %eth1 or %2 or some such). There's no syntax
+             * specification for the scope id, so just accept anything
+             * except the closing ].
+             */
+            p += strcspn(p, "]");
+        }
         if (*p == ']' && !p[1] && colons > 1) {
             /*
              * This looks like an IPv6 address literal (hex digits and
-             * at least two colons, contained in square brackets).
-             * Trim off the brackets.
+             * at least two colons, plus optional scope id, contained
+             * in square brackets). Trim off the brackets.
              */
             return dupprintf("%.*s", (int)(p - (s+1)), s+1);
         }
@@ -290,6 +300,18 @@ int string_length_for_printf(size_t s)
     return s;
 }
 
+/* Work around lack of va_copy in old MSC */
+#if defined _MSC_VER && !defined va_copy
+#define va_copy(a, b) TYPECHECK(                        \
+        (va_list *)0 == &(a) && (va_list *)0 == &(b),   \
+        memcpy(&a, &b, sizeof(va_list)))
+#endif
+
+/* Also lack of vsnprintf before VS2015 */
+#if defined _WINDOWS && !defined __WINE__ && _MSC_VER < 1900
+#define vsnprintf _vsnprintf
+#endif
+
 /*
  * Do an sprintf(), but into a custom-allocated buffer.
  * 
@@ -325,65 +347,38 @@ int string_length_for_printf(size_t s)
  *    directive we don't know about, we should panic and die rather
  *    than run any risk.
  */
-static char *dupvprintf_inner(char *buf, int oldlen, int *oldsize,
+static char *dupvprintf_inner(char *buf, size_t oldlen, size_t *sizeptr,
                               const char *fmt, va_list ap)
 {
-    int len, size, newsize;
-
-    assert(*oldsize >= oldlen);
-    size = *oldsize - oldlen;
-    if (size == 0) {
-        size = 512;
-        newsize = oldlen + size;
-        buf = sresize(buf, newsize, char);
-    } else {
-        newsize = *oldsize;
-    }
+    size_t size = *sizeptr;
+    sgrowarrayn_nm(buf, size, oldlen, 512);
 
     while (1) {
-#if defined _WINDOWS && !defined __WINE__ && _MSC_VER < 1900 /* 1900 == VS2015 has real snprintf */
-#define vsnprintf _vsnprintf
-#endif
-#ifdef va_copy
-	/* Use the `va_copy' macro mandated by C99, if present.
-	 * XXX some environments may have this as __va_copy() */
 	va_list aq;
 	va_copy(aq, ap);
-	len = vsnprintf(buf + oldlen, size, fmt, aq);
+	int len = vsnprintf(buf + oldlen, size - oldlen, fmt, aq);
 	va_end(aq);
-#else
-	/* Ugh. No va_copy macro, so do something nasty.
-	 * Technically, you can't reuse a va_list like this: it is left
-	 * unspecified whether advancing a va_list pointer modifies its
-	 * value or something it points to, so on some platforms calling
-	 * vsnprintf twice on the same va_list might fail hideously
-	 * (indeed, it has been observed to).
-	 * XXX the autoconf manual suggests that using memcpy() will give
-	 *     "maximum portability". */
-	len = vsnprintf(buf + oldlen, size, fmt, ap);
-#endif
+
 	if (len >= 0 && len < size) {
 	    /* This is the C99-specified criterion for snprintf to have
 	     * been completely successful. */
-            *oldsize = newsize;
+            *sizeptr = size;
 	    return buf;
 	} else if (len > 0) {
 	    /* This is the C99 error condition: the returned length is
 	     * the required buffer size not counting the NUL. */
-	    size = len + 1;
+	    sgrowarrayn_nm(buf, size, oldlen + 1, len);
 	} else {
 	    /* This is the pre-C99 glibc error condition: <0 means the
 	     * buffer wasn't big enough, so we enlarge it a bit and hope. */
-	    size += 512;
+	    sgrowarray_nm(buf, size, size);
 	}
-        newsize = oldlen + size;
-        buf = sresize(buf, newsize, char);
     }
 }
 
 char *dupvprintf(const char *fmt, va_list ap)
 {
-    int size = 0;
+    size_t size = 0;
     return dupvprintf_inner(NULL, 0, &size, fmt, ap);
 }
 char *dupprintf(const char *fmt, ...)
@@ -397,22 +392,23 @@ char *dupprintf(const char *fmt, ...)
 }
 
 struct strbuf_impl {
-    int size;
+    size_t size;
     struct strbuf visible;
+    bool nm;          /* true if we insist on non-moving buffer resizes */
 };
 
+#define STRBUF_SET_UPTR(buf)                                    \
+    ((buf)->visible.u = (unsigned char *)(buf)->visible.s)
 #define STRBUF_SET_PTR(buf, ptr)                                \
-    ((buf)->visible.s = (ptr),                                  \
-     (buf)->visible.u = (unsigned char *)(buf)->visible.s)
+    ((buf)->visible.s = (ptr), STRBUF_SET_UPTR(buf))
 
 void *strbuf_append(strbuf *buf_o, size_t len)
 {
     struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
     char *toret;
-    if (buf->size < buf->visible.len + len + 1) {
-        buf->size = (buf->visible.len + len + 1) * 5 / 4 + 512;
-        STRBUF_SET_PTR(buf, sresize(buf->visible.s, buf->size, char));
-    }
+    sgrowarray_general(
+        buf->visible.s, buf->size, buf->visible.len + 1, len, buf->nm);
+    STRBUF_SET_UPTR(buf);
     toret = buf->visible.s + buf->visible.len;
     buf->visible.len += len;
     buf->visible.s[buf->visible.len] = '\0';
@@ -426,16 +422,19 @@ static void strbuf_BinarySink_write(
     memcpy(strbuf_append(buf_o, len), data, len);
 }
 
-strbuf *strbuf_new(void)
+static strbuf *strbuf_new_general(bool nm)
 {
     struct strbuf_impl *buf = snew(struct strbuf_impl);
     BinarySink_INIT(&buf->visible, strbuf_BinarySink_write);
     buf->visible.len = 0;
     buf->size = 512;
+    buf->nm = nm;
     STRBUF_SET_PTR(buf, snewn(buf->size, char));
     *buf->visible.s = '\0';
     return &buf->visible;
 }
+strbuf *strbuf_new(void) { return strbuf_new_general(false); }
+strbuf *strbuf_new_nm(void) { return strbuf_new_general(true); }
 void strbuf_free(strbuf *buf_o)
 {
     struct strbuf_impl *buf = container_of(buf_o, struct strbuf_impl, visible);
@@ -487,13 +486,12 @@ void strbuf_finalise_agent_query(strbuf *buf_o)
 char *fgetline(FILE *fp)
 {
     char *ret = snewn(512, char);
-    int size = 512, len = 0;
+    size_t size = 512, len = 0;
     while (fgets(ret + len, size - len, fp)) {
 	len += strlen(ret + len);
 	if (len > 0 && ret[len-1] == '\n')
 	    break;		       /* got a newline, we're done */
-	size = len + 512;
-	ret = sresize(ret, size, char);
+        sgrowarrayn_nm(ret, size, len, 512);
     }
     if (len == 0) {		       /* first fgets returned NULL */
 	sfree(ret);
@@ -636,6 +634,7 @@ void bufchain_clear(bufchain *ch)
     while (ch->head) {
 	b = ch->head;
 	ch->head = ch->head->next;
+        smemclr(b, sizeof(*b));
 	sfree(b);
     }
     ch->tail = NULL;
@@ -706,6 +705,7 @@ void bufchain_consume(bufchain *ch, size_t len)
 	    ch->head = tmp->next;
 	    if (!ch->head)
 		ch->tail = NULL;
+            smemclr(tmp, sizeof(*tmp));
 	    sfree(tmp);
 	} else
 	    ch->head->bufpos += remlen;
@@ -766,32 +766,6 @@ size_t bufchain_fetch_consume_up_to(bufchain *ch, void *data, size_t len)
     return len;
 }
 
-/* ----------------------------------------------------------------------
- * Sanitise terminal output that we have reason not to trust, e.g.
- * because it appears in the login banner or password prompt from a
- * server, which we'd rather not permit to use arbitrary escape
- * sequences.
- */
-
-void sanitise_term_data(bufchain *out, const void *vdata, size_t len)
-{
-    const char *data = (const char *)vdata;
-
-    /*
-     * FIXME: this method of sanitisation is ASCII-centric. It would
-     * be nice to permit SSH banners and the like to contain printable
-     * Unicode, but that would need a lot more complicated code here
-     * (not to mention knowing what character set it should interpret
-     * the data as).
-     */
-    for (size_t i = 0; i < len; i++) {
-        if (data[i] == '\n')
-            bufchain_add(out, "\r\n", 2);
-        else if (data[i] >= ' ' && data[i] < 0x7F)
-            bufchain_add(out, data + i, 1);
-    }
-}
-
 /* ----------------------------------------------------------------------
  * Debugging routines.
  */
@@ -950,6 +924,20 @@ bool ptrlen_startswith(ptrlen whole, ptrlen prefix, ptrlen *tail)
     return false;
 }
 
+bool ptrlen_endswith(ptrlen whole, ptrlen suffix, ptrlen *tail)
+{
+    if (whole.len >= suffix.len &&
+        !memcmp((char *)whole.ptr + (whole.len - suffix.len),
+                suffix.ptr, suffix.len)) {
+        if (tail) {
+            tail->ptr = whole.ptr;
+            tail->len = whole.len - suffix.len;
+        }
+        return true;
+    }
+    return false;
+}
+
 char *mkstr(ptrlen pl)
 {
     char *p = snewn(pl.len + 1, char);
@@ -968,3 +956,25 @@ bool strendswith(const char *s, const char *t)
     size_t slen = strlen(s), tlen = strlen(t);
     return slen >= tlen && !strcmp(s + (slen - tlen), t);
 }
+
+size_t encode_utf8(void *output, unsigned long ch)
+{
+    unsigned char *start = (unsigned char *)output, *p = start;
+
+    if (ch < 0x80) {
+        *p++ = ch;
+    } else if (ch < 0x800) {
+        *p++ = 0xC0 | (ch >> 6);
+        *p++ = 0x80 | (ch & 0x3F);
+    } else if (ch < 0x10000) {
+        *p++ = 0xE0 | (ch >> 12);
+        *p++ = 0x80 | ((ch >> 6) & 0x3F);
+        *p++ = 0x80 | (ch & 0x3F);
+    } else {
+        *p++ = 0xF0 | (ch >> 18);
+        *p++ = 0x80 | ((ch >> 12) & 0x3F);
+        *p++ = 0x80 | ((ch >> 6) & 0x3F);
+        *p++ = 0x80 | (ch & 0x3F);
+    }
+    return p - start;
+}

+ 3 - 5
source/putty/windows/winhandl.c

@@ -541,7 +541,8 @@ HANDLE *handle_get_events(int *nevents)
 {
     HANDLE *ret;
     struct handle *h;
-    int i, n, size;
+    int i;
+    size_t n, size;
 
     /*
      * Go through our tree counting the handle objects currently
@@ -552,10 +553,7 @@ HANDLE *handle_get_events(int *nevents)
     if (handles_by_evtomain) {
 	for (i = 0; (h = index234(handles_by_evtomain, i)) != NULL; i++) {
 	    if (h->u.g.busy) {
-		if (n >= size) {
-		    size += 32;
-		    ret = sresize(ret, size, HANDLE);
-		}
+                sgrowarray(ret, size, n);
 		ret[n++] = h->u.g.ev_to_main;
 	    }
 	}

+ 41 - 6
source/putty/windows/winmisc.c

@@ -223,16 +223,14 @@ HMODULE load_system32_dll(const char *libname)
      * path.)
      */
     static char *sysdir = NULL;
+    static size_t sysdirsize = 0;
     char *fullpath;
     HMODULE ret;
 
     if (!sysdir) {
-	int size = 0, len;
-	do {
-	    size = 3*size/2 + 512;
-	    sysdir = sresize(sysdir, size, char);
-	    len = GetSystemDirectory(sysdir, size);
-	} while (len >= size);
+        size_t len;
+        while ((len = GetSystemDirectory(sysdir, sysdirsize)) >= sysdirsize)
+            sgrowarray(sysdir, sysdirsize, len);
     }
 
     fullpath = dupcat(sysdir, "\\", libname, NULL);
@@ -432,3 +430,40 @@ void dputs(const char *buf)
     fflush(debug_fp);
 }
 #endif
+
+char *registry_get_string(HKEY root, const char *path, const char *leaf)
+{
+    HKEY key = root;
+    bool need_close_key = false;
+    char *toret = NULL, *str = NULL;
+
+    if (path) {
+        if (RegCreateKey(key, path, &key) != ERROR_SUCCESS)
+            goto out;
+        need_close_key = true;
+    }
+
+    DWORD type, size;
+    if (RegQueryValueEx(key, leaf, 0, &type, NULL, &size) != ERROR_SUCCESS)
+        goto out;
+    if (type != REG_SZ)
+        goto out;
+
+    str = snewn(size + 1, char);
+    DWORD size_got = size;
+    if (RegQueryValueEx(key, leaf, 0, &type, (LPBYTE)str,
+                        &size_got) != ERROR_SUCCESS)
+        goto out;
+    if (type != REG_SZ || size_got > size)
+        goto out;
+    str[size_got] = '\0';
+
+    toret = str;
+    str = NULL;
+
+  out:
+    if (need_close_key)
+        RegCloseKey(key);
+    sfree(str);
+    return toret;
+}

+ 4 - 12
source/putty/windows/winnet.c

@@ -1816,18 +1816,10 @@ int net_service_lookup(char *service)
 
 char *get_hostname(void)
 {
-    int len = 128;
-    char *hostname = NULL;
-    do {
-	len *= 2;
-	hostname = sresize(hostname, len, char);
-	if (p_gethostname(hostname, len) < 0) {
-	    sfree(hostname);
-	    hostname = NULL;
-	    break;
-	}
-    } while (strlen(hostname) >= (size_t)(len-1));
-    return hostname;
+    char hostbuf[256]; /* MSDN docs for gethostname() promise this is enough */
+    if (p_gethostname(hostbuf, sizeof(hostbuf)) < 0)
+        return NULL;
+    return dupstr(hostbuf);
 }
 
 SockAddr *platform_get_x11_unix_address(const char *display, int displaynum,

+ 1 - 2
source/putty/windows/winstore.c

@@ -298,8 +298,7 @@ bool enum_settings_next(settings_e *e, strbuf *sb)
             success = (retd == ERROR_SUCCESS);
             break;
         }
-        regbuf_size = regbuf_size * 5 / 4 + 256;
-        regbuf = sresize(regbuf, regbuf_size, char);
+        sgrowarray(regbuf, regbuf_size, regbuf_size);
     }
 
     if (success)

+ 3 - 12
source/putty/windows/winstuff.h

@@ -181,9 +181,7 @@ struct FontSpec *fontspec_new(
 #define JUMPLISTREG_ERROR_VALUEWRITE_FAILURE 4
 #define JUMPLISTREG_ERROR_INVALID_VALUE 5
 
-#define PUTTY_HELP_FILE "putty.hlp"
 #define PUTTY_CHM_FILE "putty.chm"
-#define PUTTY_HELP_CONTENTS "putty.cnt"
 
 #define GETTICKCOUNT GetTickCount
 #define CURSORBLINK GetCaretBlinkTime()
@@ -228,6 +226,7 @@ void shutdown_help(void);
 bool has_help(void);
 void launch_help(HWND hwnd, const char *topic);
 void quit_help(HWND hwnd);
+int has_embedded_chm(void);            /* 1 = yes, 0 = no, -1 = N/A */
 
 /*
  * The terminal and logging context are notionally local to the
@@ -700,15 +699,7 @@ char *get_jumplist_registry_entries(void);
 #define CLIPUI_DEFAULT_MOUSE CLIPUI_EXPLICIT
 #define CLIPUI_DEFAULT_INS CLIPUI_EXPLICIT
 
-#ifdef MINEFIELD
-/*
- * Definitions for Minefield, PuTTY's own Windows-specific malloc
- * debugger in the style of Electric Fence. Implemented in winmisc.c,
- * and referred to by the main malloc wrappers in memory.c.
- */
-void *minefield_c_malloc(size_t size);
-void minefield_c_free(void *p);
-void *minefield_c_realloc(void *p, size_t size);
-#endif
+/* In winmisc.c */
+char *registry_get_string(HKEY root, const char *path, const char *leaf);
 
 #endif