Dave Hinton's modifications to the network layer interface, which
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index f087bd9..3af4ba9 100644 (file)
--- a/ssh.c
+++ b/ssh.c
 
 #define SSH2_EXTENDED_DATA_STDERR                 1    /* 0x1 */
 
+/*
+ * Various remote-bug flags.
+ */
+#define BUG_CHOKES_ON_SSH1_IGNORE                 1
+#define BUG_SSH2_HMAC                             2
+
 #define GET_32BIT(cp) \
     (((unsigned long)(unsigned char)(cp)[0] << 24) | \
     ((unsigned long)(unsigned char)(cp)[1] << 16) | \
@@ -173,9 +179,7 @@ const static struct ssh2_ciphers *ciphers[] = {
 };
 
 const static struct ssh_kex *kex_algs[] = {
-#ifdef DO_DIFFIE_HELLMAN_GEX
     &ssh_diffiehellman_gex,
-#endif
     &ssh_diffiehellman };
 
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
@@ -196,10 +200,12 @@ static int ssh_comp_none_block(unsigned char *block, int len,
                               unsigned char **outblock, int *outlen) {
     return 0;
 }
+static int ssh_comp_none_disable(void) { return 0; }
 const static struct ssh_compress ssh_comp_none = {
     "none",
     ssh_comp_none_init, ssh_comp_none_block,
-    ssh_comp_none_init, ssh_comp_none_block
+    ssh_comp_none_init, ssh_comp_none_block,
+    ssh_comp_none_disable
 };
 extern const struct ssh_compress ssh_zlib;
 const static struct ssh_compress *compressions[] = {
@@ -252,6 +258,7 @@ static unsigned char session_key[32];
 static int ssh1_compressing;
 static int ssh_agentfwd_enabled;
 static int ssh_X11_fwd_enabled;
+static int ssh_remote_bugs;
 static const struct ssh_cipher *cipher = NULL;
 static const struct ssh2_cipher *cscipher = NULL;
 static const struct ssh2_cipher *sccipher = NULL;
@@ -262,7 +269,8 @@ static const struct ssh_compress *sccomp = NULL;
 static const struct ssh_kex *kex = NULL;
 static const struct ssh_signkey *hostkey = NULL;
 static unsigned char ssh2_session_id[20];
-int (*ssh_get_password)(const char *prompt, char *str, int maxlen) = NULL;
+int (*ssh_get_line)(const char *prompt, char *str, int maxlen,
+                    int is_pw) = NULL;
 
 static char *savedhost;
 static int savedport;
@@ -653,21 +661,22 @@ static void s_wrpkt_start(int type, int len) {
     pktout.type = type;
 }
 
-static void s_wrpkt(void) {
+static int s_wrpkt_prepare(void) {
     int pad, len, biglen, i;
     unsigned long crc;
 
     pktout.body[-1] = pktout.type;
 
+#if 0
+    debug(("Packet payload pre-compression:\n"));
+    for (i = -1; i < pktout.length; i++)
+        debug(("  %02x", (unsigned char)pktout.body[i]));
+    debug(("\r\n"));
+#endif
+
     if (ssh1_compressing) {
        unsigned char *compblk;
        int complen;
-#if 0
-       debug(("Packet payload pre-compression:\n"));
-       for (i = -1; i < pktout.length; i++)
-           debug(("  %02x", (unsigned char)pktout.body[i]));
-       debug(("\r\n"));
-#endif
        zlib_compress_block(pktout.body-1, pktout.length+1,
                            &compblk, &complen);
        ssh1_pktout_size(complen-1);
@@ -700,93 +709,118 @@ static void s_wrpkt(void) {
     if (cipher)
        cipher->encrypt(pktout.data+4, biglen);
 
-    sk_write(s, pktout.data, biglen+4);
+    return biglen+4;
+}
+
+static void s_wrpkt(void) {
+    int len;
+    len = s_wrpkt_prepare();
+    sk_write(s, pktout.data, len);
+}
+
+static void s_wrpkt_defer(void) {
+    int len;
+    len = s_wrpkt_prepare();
+    if (deferred_len + len > deferred_size) {
+        deferred_size = deferred_len + len + 128;
+        deferred_send_data = srealloc(deferred_send_data, deferred_size);
+    }
+    memcpy(deferred_send_data+deferred_len, pktout.data, len);
+    deferred_len += len;
 }
 
 /*
- * Construct a packet with the specified contents and
- * send it to the server.
+ * Construct a packet with the specified contents.
  */
-static void send_packet(int pkttype, ...)
+static void construct_packet(int pkttype, va_list ap1, va_list ap2)
 {
-    va_list args;
     unsigned char *p, *argp, argchar;
     unsigned long argint;
     int pktlen, argtype, arglen;
     Bignum bn;
 
     pktlen = 0;
-    va_start(args, pkttype);
-    while ((argtype = va_arg(args, int)) != PKT_END) {
+    while ((argtype = va_arg(ap1, int)) != PKT_END) {
        switch (argtype) {
          case PKT_INT:
-           (void) va_arg(args, int);
+           (void) va_arg(ap1, int);
            pktlen += 4;
            break;
          case PKT_CHAR:
-           (void) va_arg(args, char);
+           (void) va_arg(ap1, char);
            pktlen++;
            break;
          case PKT_DATA:
-           (void) va_arg(args, unsigned char *);
-           arglen = va_arg(args, int);
+           (void) va_arg(ap1, unsigned char *);
+           arglen = va_arg(ap1, int);
            pktlen += arglen;
            break;
          case PKT_STR:
-           argp = va_arg(args, unsigned char *);
+           argp = va_arg(ap1, unsigned char *);
            arglen = strlen(argp);
            pktlen += 4 + arglen;
            break;
          case PKT_BIGNUM:
-           bn = va_arg(args, Bignum);
+           bn = va_arg(ap1, Bignum);
             pktlen += ssh1_bignum_length(bn);
            break;
          default:
            assert(0);
        }
     }
-    va_end(args);
 
     s_wrpkt_start(pkttype, pktlen);
     p = pktout.body;
 
-    va_start(args, pkttype);
-    while ((argtype = va_arg(args, int)) != PKT_END) {
+    while ((argtype = va_arg(ap2, int)) != PKT_END) {
        switch (argtype) {
          case PKT_INT:
-           argint = va_arg(args, int);
+           argint = va_arg(ap2, int);
            PUT_32BIT(p, argint);
            p += 4;
            break;
          case PKT_CHAR:
-           argchar = va_arg(args, unsigned char);
+           argchar = va_arg(ap2, unsigned char);
            *p = argchar;
            p++;
            break;
          case PKT_DATA:
-           argp = va_arg(args, unsigned char *);
-           arglen = va_arg(args, int);
+           argp = va_arg(ap2, unsigned char *);
+           arglen = va_arg(ap2, int);
            memcpy(p, argp, arglen);
            p += arglen;
            break;
          case PKT_STR:
-           argp = va_arg(args, unsigned char *);
+           argp = va_arg(ap2, unsigned char *);
            arglen = strlen(argp);
            PUT_32BIT(p, arglen);
            memcpy(p + 4, argp, arglen);
            p += 4 + arglen;
            break;
          case PKT_BIGNUM:
-           bn = va_arg(args, Bignum);
+           bn = va_arg(ap2, Bignum);
             p += ssh1_write_bignum(p, bn);
            break;
        }
     }
-    va_end(args);
+}
 
+static void send_packet(int pkttype, ...) {
+    va_list ap1, ap2;
+    va_start(ap1, pkttype);
+    va_start(ap2, pkttype);
+    construct_packet(pkttype, ap1, ap2);
     s_wrpkt();
 }
 
+static void defer_packet(int pkttype, ...) {
+    va_list ap1, ap2;
+    va_start(ap1, pkttype);
+    va_start(ap2, pkttype);
+    construct_packet(pkttype, ap1, ap2);
+    s_wrpkt_defer();
+}
+
 static int ssh_versioncmp(char *a, char *b) {
     char *ae, *be;
     unsigned long av, bv;
@@ -989,9 +1023,9 @@ static void ssh2_pkt_defer(void) {
 
 /*
  * Send the whole deferred data block constructed by
- * ssh2_pkt_defer().
+ * ssh2_pkt_defer() or SSH1's defer_packet().
  */
-static void ssh2_pkt_defersend(void) {
+static void ssh_pkt_defersend(void) {
     sk_write(s, deferred_send_data, deferred_len);
     deferred_len = deferred_size = 0;
     sfree(deferred_send_data);
@@ -1065,6 +1099,41 @@ static Bignum ssh2_pkt_getmp(void) {
     return b;
 }
 
+/*
+ * Examine the remote side's version string and compare it against
+ * a list of known buggy implementations.
+ */
+static void ssh_detect_bugs(char *vstring) {
+    char *imp;                         /* pointer to implementation part */
+    imp = vstring;
+    imp += strcspn(imp, "-");
+    imp += strcspn(imp, "-");
+
+    ssh_remote_bugs = 0;
+
+    if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
+        !strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
+        !strcmp(imp, "1.2.22")) {
+        /*
+         * These versions don't support SSH1_MSG_IGNORE, so we have
+         * to use a different defence against password length
+         * sniffing.
+         */
+        ssh_remote_bugs |= BUG_CHOKES_ON_SSH1_IGNORE;
+        logevent("We believe remote version has SSH1 ignore bug");
+    }
+
+    if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
+        !strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
+        !strncmp(imp, "2.1 ", 4)) {
+        /*
+         * These versions have the HMAC bug.
+         */
+        ssh_remote_bugs |= BUG_SSH2_HMAC;
+        logevent("We believe remote version has SSH2 HMAC bug");
+    }
+}
+
 static int do_ssh_init(unsigned char c) {
     static char *vsp;
     static char version[10];
@@ -1112,6 +1181,7 @@ static int do_ssh_init(unsigned char c) {
 
     *vsp = 0;
     sprintf(vlog, "Server version: %s", vstring);
+    ssh_detect_bugs(vstring);
     vlog[strcspn(vlog, "\r\n")] = '\0';
     logevent(vlog);
 
@@ -1200,21 +1270,20 @@ static void ssh_gotdata(unsigned char *data, int datalen)
     crFinishV;
 }
 
-static int ssh_receive(Socket skt, int urgent, char *data, int len) {
-    if (urgent==3) {
+static int ssh_closing (Plug plug, char *error_msg, int error_code, int calling_back) {
+    ssh_state = SSH_STATE_CLOSED;
+    sk_close(s);
+    s = NULL;
+    if (error_msg) {
         /* A socket error has occurred. */
-        ssh_state = SSH_STATE_CLOSED;
-        sk_close(s);
-        s = NULL;
-        connection_fatal(data);
-        return 0;
-    } else if (!len) {
-       /* Connection has closed. */
-       ssh_state = SSH_STATE_CLOSED;
-       sk_close(s);
-       s = NULL;
-       return 0;
+        connection_fatal (error_msg);
+    } else {
+       /* Otherwise, the remote side closed the connection normally. */
     }
+    return 0;
+}
+
+static int ssh_receive(Plug plug, int urgent, char *data, int len) {
     ssh_gotdata (data, len);
     if (ssh_state == SSH_STATE_CLOSED) {
         if (s) {
@@ -1233,6 +1302,11 @@ static int ssh_receive(Socket skt, int urgent, char *data, int len) {
  */
 static char *connect_to_host(char *host, int port, char **realhost)
 {
+    static struct plug_function_table fn_table = {
+       ssh_closing,
+       ssh_receive
+    }, *fn_table_ptr = &fn_table;
+
     SockAddr addr;
     char *err;
 #ifdef FWHACK
@@ -1270,7 +1344,7 @@ static char *connect_to_host(char *host, int port, char **realhost)
     /*
      * Open socket.
      */
-    s = sk_new(addr, port, 0, 1, ssh_receive);
+    s = sk_new(addr, port, 0, 1, &fn_table_ptr);
     if ( (err = sk_socket_error(s)) )
        return err;
 
@@ -1434,43 +1508,56 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
        static int pos = 0;
        static char c;
        if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           c_write_str("login as: ");
-            ssh_send_ok = 1;
-           while (pos >= 0) {
-               crWaitUntil(!ispkt);
-               while (inlen--) switch (c = *in++) {
-                 case 10: case 13:
-                   username[pos] = 0;
-                   pos = -1;
-                   break;
-                 case 8: case 127:
-                   if (pos > 0) {
-                       c_write_str("\b \b");
-                       pos--;
-                   }
-                   break;
-                 case 21: case 27:
-                   while (pos > 0) {
-                       c_write_str("\b \b");
-                       pos--;
-                   }
-                   break;
-                 case 3: case 4:
-                   random_save_seed();
-                   exit(0);
-                   break;
-                 default:
-                   if (((c >= ' ' && c <= '~') ||
-                         ((unsigned char)c >= 160)) && pos < 40) {
-                       username[pos++] = c;
-                       c_write(&c, 1);
-                   }
-                   break;
-               }
-           }
-           c_write_str("\r\n");
-           username[strcspn(username, "\n\r")] = '\0';
-       } else {
+            if (ssh_get_line) {
+                if (!ssh_get_line("login as: ",
+                                  username, sizeof(username), FALSE)) {
+                    /*
+                     * get_line failed to get a username.
+                     * Terminate.
+                     */
+                    logevent("No username provided. Abandoning session.");
+                    ssh_state = SSH_STATE_CLOSED;
+                    crReturn(1);
+                }
+            } else {
+                c_write_str("login as: ");
+                ssh_send_ok = 1;
+                while (pos >= 0) {
+                    crWaitUntil(!ispkt);
+                    while (inlen--) switch (c = *in++) {
+                      case 10: case 13:
+                        username[pos] = 0;
+                        pos = -1;
+                        break;
+                      case 8: case 127:
+                        if (pos > 0) {
+                            c_write_str("\b \b");
+                            pos--;
+                        }
+                        break;
+                      case 21: case 27:
+                        while (pos > 0) {
+                            c_write_str("\b \b");
+                            pos--;
+                        }
+                        break;
+                      case 3: case 4:
+                        random_save_seed();
+                        exit(0);
+                        break;
+                      default:
+                        if (((c >= ' ' && c <= '~') ||
+                             ((unsigned char)c >= 160)) && pos < 40) {
+                            username[pos++] = c;
+                            c_write(&c, 1);
+                        }
+                        break;
+                    }
+                }
+                c_write_str("\r\n");
+                username[strcspn(username, "\n\r")] = '\0';
+            }
+        } else {
            strncpy(username, cfg.username, 99);
            username[99] = '\0';
        }
@@ -1524,7 +1611,7 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             if (response) {
                 p = response + 5;
                 nkeys = GET_32BIT(p); p += 4;
-                { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+                { char buf[64]; sprintf(buf, "Pageant has %d SSH1 keys", nkeys);
                     logevent(buf); }
                 for (i = 0; i < nkeys; i++) {
                     static struct RSAKey key;
@@ -1672,13 +1759,12 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
             sfree(comment);
         }
 
-       if (ssh_get_password) {
-           if (!ssh_get_password(prompt, password, sizeof(password))) {
+       if (ssh_get_line) {
+           if (!ssh_get_line(prompt, password, sizeof(password), TRUE)) {
                 /*
-                 * get_password failed to get a password (for
-                 * example because one was supplied on the command
-                 * line which has already failed to work).
-                 * Terminate.
+                 * get_line failed to get a password (for example
+                 * because one was supplied on the command line
+                 * which has already failed to work). Terminate.
                  */
                 logevent("No more passwords to try");
                 ssh_state = SSH_STATE_CLOSED;
@@ -1784,7 +1870,94 @@ static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
 
             break;                     /* we're through! */
         } else {
-            send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+            if (pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+                /*
+                 * Defence against traffic analysis: we send a
+                 * whole bunch of packets containing strings of
+                 * different lengths. One of these strings is the
+                 * password, in a SSH1_CMSG_AUTH_PASSWORD packet.
+                 * The others are all random data in
+                 * SSH1_MSG_IGNORE packets. This way a passive
+                 * listener can't tell which is the password, and
+                 * hence can't deduce the password length.
+                 * 
+                 * Anybody with a password length greater than 16
+                 * bytes is going to have enough entropy in their
+                 * password that a listener won't find it _that_
+                 * much help to know how long it is. So what we'll
+                 * do is:
+                 * 
+                 *  - if password length < 16, we send 15 packets
+                 *    containing string lengths 1 through 15
+                 * 
+                 *  - otherwise, we let N be the nearest multiple
+                 *    of 8 below the password length, and send 8
+                 *    packets containing string lengths N through
+                 *    N+7. This won't obscure the order of
+                 *    magnitude of the password length, but it will
+                 *    introduce a bit of extra uncertainty.
+                 * 
+                 * A few servers (the old 1.2.18 through 1.2.22)
+                 * can't deal with SSH1_MSG_IGNORE. For these
+                 * servers, we need an alternative defence. We make
+                 * use of the fact that the password is interpreted
+                 * as a C string: so we can append a NUL, then some
+                 * random data.
+                 */
+                if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
+                    char string[64];
+                    char *s;
+                    int len;
+
+                    len = strlen(password);
+                    if (len < sizeof(string)) {
+                        s = string;
+                        strcpy(string, password);
+                        len++;         /* cover the zero byte */
+                        while (len < sizeof(string)) {
+                            string[len++] = (char)random_byte();
+                        }
+                    } else {
+                        s = password;
+                    }
+                    send_packet(pwpkt_type, PKT_INT, len,
+                                PKT_DATA, s, len, PKT_END);
+                } else {
+                    int bottom, top, pwlen, i;
+                    char *randomstr;
+
+                    pwlen = strlen(password);
+                    if (pwlen < 16) {
+                        bottom = 0;    /* zero length passwords are OK! :-) */
+                        top = 15;
+                    } else {
+                        bottom = pwlen &~ 7;
+                        top = bottom + 7;
+                    }
+
+                    assert(pwlen >= bottom && pwlen <= top);
+
+                    randomstr = smalloc(top+1);
+
+                    for (i = bottom; i <= top; i++) {
+                        if (i == pwlen)
+                            defer_packet(pwpkt_type, PKT_STR, password, PKT_END);
+                        else {
+                            for (j = 0; j < i; j++) {
+                                do {
+                                    randomstr[j] = random_byte();
+                                } while (randomstr[j] == '\0');
+                            }
+                            randomstr[i] = '\0';
+                            defer_packet(SSH1_MSG_IGNORE,
+                                         PKT_STR, randomstr, PKT_END);
+                        }
+                    }
+                    ssh_pkt_defersend();
+                }
+            } else {
+                send_packet(pwpkt_type, PKT_STR, password, PKT_END);
+            }
         }
        logevent("Sent password");
        memset(password, 0, strlen(password));
@@ -2173,7 +2346,7 @@ static void ssh2_mkkey(Bignum K, char *H, char *sessid, char chr, char *keyspace
  */
 static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 {
-    static int i, j, len, nbits;
+    static int i, j, len, nbits, pbits;
     static char *str;
     static Bignum p, g, e, f, K;
     static int kex_init_value, kex_reply_value;
@@ -2222,7 +2395,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /*
      * Be prepared to work around the buggy MAC problem.
      */
-    if (cfg.buggymac)
+    if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
         maclist = buggymacs, nmacs = lenof(buggymacs);
     else
         maclist = macs, nmacs = lenof(macs);
@@ -2396,30 +2569,34 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
+     * Work out the number of bits of key we will need from the key
+     * exchange. We start with the maximum key length of either
+     * cipher...
+     */
+    {
+        int csbits, scbits;
+
+       csbits = cscipher_tobe->keylen;
+       scbits = sccipher_tobe->keylen;
+       nbits = (csbits > scbits ? csbits : scbits);
+    }
+    /* The keys only have 160-bit entropy, since they're based on
+     * a SHA-1 hash. So cap the key size at 160 bits. */
+    if (nbits > 160) nbits = 160;
+
+    /*
      * If we're doing Diffie-Hellman group exchange, start by
      * requesting a group.
      */
     if (kex == &ssh_diffiehellman_gex) {
-        int csbits, scbits;
-
         logevent("Doing Diffie-Hellman group exchange");
         /*
-         * Work out number of bits. We start with the maximum key
-         * length of either cipher...
-         */
-        csbits = cscipher_tobe->keylen;
-        scbits = sccipher_tobe->keylen;
-        nbits = (csbits > scbits ? csbits : scbits);
-        /* The keys only have 160-bit entropy, since they're based on
-         * a SHA-1 hash. So cap the key size at 160 bits. */
-        if (nbits > 160) nbits = 160;
-        /*
-         * ... and then work out how big a DH group we will need to
-         * allow that much data.
-         */
-        nbits = 512 << ((nbits-1) / 64);
+         * Work out how big a DH group we will need to allow that
+         * much data.
+        */
+        pbits = 512 << ((nbits-1) / 64);
         ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
-        ssh2_pkt_adduint32(nbits);
+        ssh2_pkt_adduint32(pbits);
         ssh2_pkt_send();
 
         crWaitUntil(ispkt);
@@ -2442,7 +2619,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
     /*
      * Now generate and send e for Diffie-Hellman.
      */
-    e = dh_create_e();
+    e = dh_create_e(nbits*2);
     ssh2_pkt_init(kex_init_value);
     ssh2_pkt_addmp(e);
     ssh2_pkt_send();
@@ -2460,7 +2637,7 @@ static int do_ssh2_transport(unsigned char *in, int inlen, int ispkt)
 
     sha_string(&exhash, hostkeydata, hostkeylen);
     if (kex == &ssh_diffiehellman_gex) {
-        sha_uint32(&exhash, nbits);
+        sha_uint32(&exhash, pbits);
         sha_mpint(&exhash, p);
         sha_mpint(&exhash, g);
     }
@@ -2683,40 +2860,53 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
         */
        pos = 0;
        if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
-           c_write_str("login as: ");
-           ssh_send_ok = 1;
-           while (pos >= 0) {
-               crWaitUntilV(!ispkt);
-               while (inlen--) switch (c = *in++) {
-                 case 10: case 13:
-                   username[pos] = 0;
-                   pos = -1;
-                   break;
-                 case 8: case 127:
-                   if (pos > 0) {
-                       c_write_str("\b \b");
-                       pos--;
-                   }
-                   break;
-                 case 21: case 27:
-                   while (pos > 0) {
-                       c_write_str("\b \b");
-                       pos--;
-                   }
-                   break;
-                 case 3: case 4:
-                   random_save_seed();
-                   exit(0);
-                   break;
-                 default:
-                   if (((c >= ' ' && c <= '~') ||
-                        ((unsigned char)c >= 160)) && pos < 40) {
-                       username[pos++] = c;
-                       c_write(&c, 1);
-                   }
-                   break;
-               }
-           }
+            if (ssh_get_line) {
+                if (!ssh_get_line("login as: ",
+                                  username, sizeof(username), FALSE)) {
+                    /*
+                     * get_line failed to get a username.
+                     * Terminate.
+                     */
+                    logevent("No username provided. Abandoning session.");
+                    ssh_state = SSH_STATE_CLOSED;
+                    crReturn(1);
+                }
+            } else {
+                c_write_str("login as: ");
+                ssh_send_ok = 1;
+                while (pos >= 0) {
+                    crWaitUntilV(!ispkt);
+                    while (inlen--) switch (c = *in++) {
+                      case 10: case 13:
+                        username[pos] = 0;
+                        pos = -1;
+                        break;
+                      case 8: case 127:
+                        if (pos > 0) {
+                            c_write_str("\b \b");
+                            pos--;
+                        }
+                        break;
+                      case 21: case 27:
+                        while (pos > 0) {
+                            c_write_str("\b \b");
+                            pos--;
+                        }
+                        break;
+                      case 3: case 4:
+                        random_save_seed();
+                        exit(0);
+                        break;
+                      default:
+                        if (((c >= ' ' && c <= '~') ||
+                             ((unsigned char)c >= 160)) && pos < 40) {
+                            username[pos++] = c;
+                            c_write(&c, 1);
+                        }
+                        break;
+                    }
+                }
+            }
            c_write_str("\r\n");
            username[strcspn(username, "\n\r")] = '\0';
        } else {
@@ -2848,7 +3038,7 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                if (response) {
                    p = response + 5;
                    nkeys = GET_32BIT(p); p += 4;
-                   { char buf[64]; sprintf(buf, "Pageant has %d keys", nkeys);
+                   { char buf[64]; sprintf(buf, "Pageant has %d SSH2 keys", nkeys);
                        logevent(buf); }
                    for (i = 0; i < nkeys; i++) {
                        static char *pkblob, *alg, *commentp;
@@ -2998,13 +3188,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
            }
 
            if (need_pw) {
-               if (ssh_get_password) {
-                   if (!ssh_get_password(pwprompt, password, sizeof(password))) {
+               if (ssh_get_line) {
+                   if (!ssh_get_line(pwprompt, password,
+                                      sizeof(password), TRUE)) {
                        /*
-                        * get_password failed to get a password (for
-                        * example because one was supplied on the command
-                        * line which has already failed to work).
-                        * Terminate.
+                        * get_line failed to get a password (for
+                        * example because one was supplied on the
+                        * command line which has already failed to
+                        * work). Terminate.
                         */
                        logevent("No more passwords to try");
                        ssh_state = SSH_STATE_CLOSED;
@@ -3139,20 +3330,34 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                 * reason, we don't do this trick at all because we gain
                 * nothing by it.
                 */
-               if (cscipher) {
-                   int i, j;
+                if (cscipher) {
+                    int stringlen, i;
+
+                    stringlen = (256 - deferred_len);
+                    stringlen += cscipher->blksize - 1;
+                    stringlen -= (stringlen % cscipher->blksize);
+                    if (cscomp) {
+                        /*
+                         * Temporarily disable actual compression,
+                         * so we can guarantee to get this string
+                         * exactly the length we want it. The
+                         * compression-disabling routine should
+                         * return an integer indicating how many
+                         * bytes we should adjust our string length
+                         * by.
+                         */
+                        stringlen -= cscomp->disable_compression();
+                    }
                    ssh2_pkt_init(SSH2_MSG_IGNORE);
                    ssh2_pkt_addstring_start();
-                   for (i = deferred_len; i <= 256; i += cscipher->blksize) {
-                       for (j = 0; j < cscipher->blksize; j++) {
-                           char c = (char)random_byte();
-                           ssh2_pkt_addstring_data(&c, 1);
-                       }
+                   for (i = 0; i < stringlen; i++) {
+                        char c = (char)random_byte();
+                        ssh2_pkt_addstring_data(&c, 1);
                    }
                    ssh2_pkt_defer();
                }
-               ssh2_pkt_defersend();
-               logevent("Sent password\r\n");
+               ssh_pkt_defersend();
+               logevent("Sent password");
                type = AUTH_TYPE_PASSWORD;
            } else {
                c_write_str("No supported authentication methods left to try!\r\n");
@@ -3248,6 +3453,43 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
     }
 
     /*
+     * Potentially enable agent forwarding.
+     */
+    if (cfg.agentfwd && agent_exists()) {
+        char proto[20], data[64];
+        logevent("Requesting OpenSSH-style agent forwarding");
+        x11_invent_auth(proto, sizeof(proto), data, sizeof(data));
+        ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+        ssh2_pkt_adduint32(mainchan->remoteid);
+        ssh2_pkt_addstring("auth-agent-req@openssh.com");
+        ssh2_pkt_addbool(1);           /* want reply */
+        ssh2_pkt_send();
+
+        do {
+            crWaitUntilV(ispkt);
+            if (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST) {
+                unsigned i = ssh2_pkt_getuint32();
+                struct ssh_channel *c;
+                c = find234(ssh_channels, &i, ssh_channelfind);
+                if (!c)
+                    continue;          /* nonexistent channel */
+                c->v2.remwindow += ssh2_pkt_getuint32();
+            }
+        } while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+
+        if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
+            if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
+                bombout(("Server got confused by agent forwarding request"));
+                crReturnV;
+            }
+            logevent("Agent forwarding refused");
+        } else {
+            logevent("Agent forwarding enabled");
+           ssh_agentfwd_enabled = TRUE;
+        }
+    }
+
+    /*
      * Now allocate a pty for the session.
      */
     if (!cfg.nopty) {
@@ -3369,6 +3611,45 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                       case CHAN_X11:
                         x11_send(c->u.x11.s, data, length);
                         break;
+                     case CHAN_AGENT:
+                        while (length > 0) {
+                            if (c->u.a.lensofar < 4) {
+                                int l = min(4 - c->u.a.lensofar, length);
+                                memcpy(c->u.a.msglen + c->u.a.lensofar, data, l);
+                                data += l; length -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == 4) {
+                                c->u.a.totallen = 4 + GET_32BIT(c->u.a.msglen);
+                                c->u.a.message = smalloc(c->u.a.totallen);
+                                memcpy(c->u.a.message, c->u.a.msglen, 4);
+                            }
+                            if (c->u.a.lensofar >= 4 && length > 0) {
+                                int l = min(c->u.a.totallen - c->u.a.lensofar,
+                                           length);
+                                memcpy(c->u.a.message + c->u.a.lensofar, data, l);
+                                data += l; length -= l; c->u.a.lensofar += l;
+                            }
+                            if (c->u.a.lensofar == c->u.a.totallen) {
+                                void *reply, *sentreply;
+                                int replylen;
+                                agent_query(c->u.a.message, c->u.a.totallen,
+                                            &reply, &replylen);
+                                if (reply)
+                                    sentreply = reply;
+                                else {
+                                    /* Fake SSH_AGENT_FAILURE. */
+                                    sentreply = "\0\0\0\1\5";
+                                    replylen = 5;
+                                }
+                               ssh2_add_channel_data(c, sentreply, replylen);
+                               try_send = TRUE;
+                                if (reply)
+                                    sfree(reply);
+                                sfree(c->u.a.message);
+                                c->u.a.lensofar = 0;
+                            }
+                        }
+                        break;
                     }
                     /*
                      * Enlarge the window again at the remote
@@ -3401,7 +3682,9 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                      */
                     x11_close(c->u.x11.s);
                     sshfwd_close(c);
-                }
+                } else if (c->type == CHAN_AGENT) {
+                   sshfwd_close(c);
+               }
            } else if (pktin.type == SSH2_MSG_CHANNEL_CLOSE) {
                 unsigned i = ssh2_pkt_getuint32();
                 struct ssh_channel *c;
@@ -3421,6 +3704,8 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                     break;             /* nothing to see here, move along */
                   case CHAN_X11:
                     break;
+                  case CHAN_AGENT:
+                    break;
                 }
                 del234(ssh_channels, c);
                 sfree(c->v2.outbuffer);
@@ -3465,6 +3750,14 @@ static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
                     } else {
                         c->type = CHAN_X11;
                     }
+                } else if (typelen == 22 &&
+                          !memcmp(type, "auth-agent@openssh.com", 3)) {
+                    if (!ssh_agentfwd_enabled)
+                        error = "Agent forwarding is not enabled";
+                   else {
+                       c->type = CHAN_AGENT;   /* identify channel type */
+                       c->u.a.lensofar = 0;
+                    }
                 } else {
                     error = "Unsupported channel type requested";
                 }