Try to make it slightly clearer that TIS/CryptoCard are generic, since
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 8c7f775..24391d3 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -166,6 +166,110 @@ static const char *const ssh2_disconnect_reasons[] = {
 #define BUG_SSH2_REKEY                           64
 #define BUG_SSH2_PK_SESSIONID                   128
 
+/*
+ * Codes for terminal modes.
+ * Most of these are the same in SSH-1 and SSH-2.
+ * This list is derived from draft-ietf-secsh-connect-25 and
+ * SSH-1 RFC-1.2.31.
+ */
+static const struct {
+    const char* const mode;
+    int opcode;
+    enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
+} ssh_ttymodes[] = {
+    /* "V" prefix discarded for special characters relative to SSH specs */
+    { "INTR",        1, TTY_OP_CHAR },
+    { "QUIT",        2, TTY_OP_CHAR },
+    { "ERASE",       3, TTY_OP_CHAR },
+    { "KILL",        4, TTY_OP_CHAR },
+    { "EOF",         5, TTY_OP_CHAR },
+    { "EOL",         6, TTY_OP_CHAR },
+    { "EOL2",        7, TTY_OP_CHAR },
+    { "START",       8, TTY_OP_CHAR },
+    { "STOP",        9, TTY_OP_CHAR },
+    { "SUSP",       10, TTY_OP_CHAR },
+    { "DSUSP",      11, TTY_OP_CHAR },
+    { "REPRINT",     12, TTY_OP_CHAR },
+    { "WERASE",             13, TTY_OP_CHAR },
+    { "LNEXT",      14, TTY_OP_CHAR },
+    { "FLUSH",      15, TTY_OP_CHAR },
+    { "SWTCH",      16, TTY_OP_CHAR },
+    { "STATUS",             17, TTY_OP_CHAR },
+    { "DISCARD",     18, TTY_OP_CHAR },
+    { "IGNPAR",             30, TTY_OP_BOOL },
+    { "PARMRK",             31, TTY_OP_BOOL },
+    { "INPCK",      32, TTY_OP_BOOL },
+    { "ISTRIP",             33, TTY_OP_BOOL },
+    { "INLCR",      34, TTY_OP_BOOL },
+    { "IGNCR",      35, TTY_OP_BOOL },
+    { "ICRNL",      36, TTY_OP_BOOL },
+    { "IUCLC",      37, TTY_OP_BOOL },
+    { "IXON",       38, TTY_OP_BOOL },
+    { "IXANY",      39, TTY_OP_BOOL },
+    { "IXOFF",      40, TTY_OP_BOOL },
+    { "IMAXBEL",     41, TTY_OP_BOOL },
+    { "ISIG",       50, TTY_OP_BOOL },
+    { "ICANON",             51, TTY_OP_BOOL },
+    { "XCASE",      52, TTY_OP_BOOL },
+    { "ECHO",       53, TTY_OP_BOOL },
+    { "ECHOE",      54, TTY_OP_BOOL },
+    { "ECHOK",      55, TTY_OP_BOOL },
+    { "ECHONL",             56, TTY_OP_BOOL },
+    { "NOFLSH",             57, TTY_OP_BOOL },
+    { "TOSTOP",             58, TTY_OP_BOOL },
+    { "IEXTEN",             59, TTY_OP_BOOL },
+    { "ECHOCTL",     60, TTY_OP_BOOL },
+    { "ECHOKE",             61, TTY_OP_BOOL },
+    { "PENDIN",             62, TTY_OP_BOOL }, /* XXX is this a real mode? */
+    { "OPOST",      70, TTY_OP_BOOL },
+    { "OLCUC",      71, TTY_OP_BOOL },
+    { "ONLCR",      72, TTY_OP_BOOL },
+    { "OCRNL",      73, TTY_OP_BOOL },
+    { "ONOCR",      74, TTY_OP_BOOL },
+    { "ONLRET",             75, TTY_OP_BOOL },
+    { "CS7",        90, TTY_OP_BOOL },
+    { "CS8",        91, TTY_OP_BOOL },
+    { "PARENB",             92, TTY_OP_BOOL },
+    { "PARODD",             93, TTY_OP_BOOL }
+};
+
+/* Miscellaneous other tty-related constants. */
+#define SSH_TTY_OP_END           0
+/* The opcodes for ISPEED/OSPEED differ between SSH-1 and SSH-2. */
+#define SSH1_TTY_OP_ISPEED     192
+#define SSH1_TTY_OP_OSPEED     193
+#define SSH2_TTY_OP_ISPEED     128
+#define SSH2_TTY_OP_OSPEED     129
+
+/* Helper functions for parsing tty-related config. */
+static unsigned int ssh_tty_parse_specchar(char *s)
+{
+    unsigned int ret;
+    if (*s) {
+       char *next = NULL;
+       ret = ctrlparse(s, &next);
+       if (!next) ret = s[0];
+    } else {
+       ret = 255; /* special value meaning "don't set" */
+    }
+    return ret;
+}
+static unsigned int ssh_tty_parse_boolean(char *s)
+{
+    if (stricmp(s, "yes") == 0 ||
+       stricmp(s, "on") == 0 ||
+       stricmp(s, "true") == 0 ||
+       stricmp(s, "+") == 0)
+       return 1; /* true */
+    else if (stricmp(s, "no") == 0 ||
+            stricmp(s, "off") == 0 ||
+            stricmp(s, "false") == 0 ||
+            stricmp(s, "-") == 0)
+       return 0; /* false */
+    else
+       return (atoi(s) != 0);
+}
+
 #define translate(x) if (type == x) return #x
 #define translatec(x,ctx) if (type == x && (pkt_ctx & ctx)) return #x
 static char *ssh1_pkt_type(int type)
@@ -307,14 +411,19 @@ enum {
 typedef struct ssh_tag *Ssh;
 struct Packet;
 
+static struct Packet *ssh1_pkt_init(int pkt_type);
 static struct Packet *ssh2_pkt_init(int pkt_type);
+static void ssh_pkt_ensure(struct Packet *, int length);
+static void ssh_pkt_adddata(struct Packet *, void *data, int len);
+static void ssh_pkt_addbyte(struct Packet *, unsigned char value);
 static void ssh2_pkt_addbool(struct Packet *, unsigned char value);
-static void ssh2_pkt_adduint32(struct Packet *, unsigned long value);
-static void ssh2_pkt_addstring_start(struct Packet *);
-static void ssh2_pkt_addstring_str(struct Packet *, char *data);
-static void ssh2_pkt_addstring_data(struct Packet *, char *data, int len);
-static void ssh2_pkt_addstring(struct Packet *, char *data);
+static void ssh_pkt_adduint32(struct Packet *, unsigned long value);
+static void ssh_pkt_addstring_start(struct Packet *);
+static void ssh_pkt_addstring_str(struct Packet *, char *data);
+static void ssh_pkt_addstring_data(struct Packet *, char *data, int len);
+static void ssh_pkt_addstring(struct Packet *, char *data);
 static unsigned char *ssh2_mpint_fmt(Bignum b, int *len);
+static void ssh1_pkt_addmp(struct Packet *, Bignum b);
 static void ssh2_pkt_addmp(struct Packet *, Bignum b);
 static int ssh2_pkt_construct(Ssh, struct Packet *);
 static void ssh2_pkt_send(Ssh, struct Packet *);
@@ -352,34 +461,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
 const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 
-static void *nullmac_make_context(void)
-{
-    return NULL;
-}
-static void nullmac_free_context(void *handle)
-{
-}
-static void nullmac_key(void *handle, unsigned char *key)
-{
-}
-static void nullmac_generate(void *handle, unsigned char *blk, int len,
-                            unsigned long seq)
-{
-}
-static int nullmac_verify(void *handle, unsigned char *blk, int len,
-                         unsigned long seq)
-{
-    return 1;
-}
-const static struct ssh_mac ssh_mac_none = {
-    nullmac_make_context, nullmac_free_context, nullmac_key,
-    nullmac_generate, nullmac_verify, "none", 0
-};
 const static struct ssh_mac *macs[] = {
-    &ssh_sha1, &ssh_md5, &ssh_mac_none
+    &ssh_sha1, &ssh_md5
 };
 const static struct ssh_mac *buggymacs[] = {
-    &ssh_sha1_buggy, &ssh_md5, &ssh_mac_none
+    &ssh_sha1_buggy, &ssh_md5
 };
 
 static void *ssh_comp_none_init(void)
@@ -522,15 +608,15 @@ struct ssh_portfwd {
             sfree((pf)->sserv), sfree((pf)->dserv)) : (void)0 ), sfree(pf) )
 
 struct Packet {
-    long length;
-    long forcepad; /* Force padding to at least this length */
-    int type;
-    unsigned long sequence;
-    unsigned char *data;
-    unsigned char *body;
-    long savedpos;
-    long maxlen;
-    long encrypted_len;                       /* for SSH-2 total-size counting */
+    long length;           /* length of `data' actually used */
+    long forcepad;         /* SSH-2: force padding to at least this length */
+    int type;              /* only used for incoming packets */
+    unsigned long sequence; /* SSH-2 incoming sequence number */
+    unsigned char *data;    /* allocated storage */
+    unsigned char *body;    /* offset of payload within `data' */
+    long savedpos;         /* temporary index into `data' (for strings) */
+    long maxlen;           /* amount of storage allocated for `data' */
+    long encrypted_len;            /* for SSH-2 total-size counting */
 
     /*
      * State associated with packet logging
@@ -635,6 +721,7 @@ struct ssh_tag {
     struct ssh_channel *mainchan;      /* primary session channel */
     int exitcode;
     int close_expected;
+    int clean_exit;
 
     tree234 *rportfwds, *portfwds;
 
@@ -662,6 +749,7 @@ struct ssh_tag {
      */
     int fallback_cmd;
 
+    bufchain banner;   /* accumulates banners during do_ssh2_authconn */
     /*
      * Used for username and password input.
      */
@@ -803,6 +891,29 @@ static void end_log_omission(Ssh ssh, struct Packet *pkt)
     pkt->logmode = PKTLOG_EMIT;
 }
 
+/* Helper function for common bits of parsing cfg.ttymodes. */
+static void parse_ttymodes(Ssh ssh, char *modes,
+                          void (*do_mode)(void *data, char *mode, char *val),
+                          void *data)
+{
+    while (*modes) {
+       char *t = strchr(modes, '\t');
+       char *m = snewn(t-modes+1, char);
+       char *val;
+       strncpy(m, modes, t-modes);
+       m[t-modes] = '\0';
+       if (*(t+1) == 'A')
+           val = get_ttymode(ssh->frontend, m);
+       else
+           val = dupstr(t+2);
+       if (val)
+           do_mode(data, m, val);
+       sfree(m);
+       sfree(val);
+       modes += strlen(modes) + 1;
+    }
+}
+
 static int ssh_channelcmp(void *av, void *bv)
 {
     struct ssh_channel *a = (struct ssh_channel *) av;
@@ -970,7 +1081,7 @@ static struct Packet *ssh_new_packet(void)
 {
     struct Packet *pkt = snew(struct Packet);
 
-    pkt->data = NULL;
+    pkt->body = pkt->data = NULL;
     pkt->maxlen = 0;
     pkt->logmode = PKTLOG_EMIT;
     pkt->nblanks = 0;
@@ -1156,7 +1267,8 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
      * _Completely_ silly lengths should be stomped on before they
      * do us any more damage.
      */
-    if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
+    if (st->len < 0 || st->len > 35000 || st->pad < 4 ||
+       st->len - st->pad < 1 || (st->len + 4) % st->cipherblk != 0) {
        bombout(("Incoming packet was garbled on decryption"));
        ssh_free_packet(st->pktin);
        crStop(NULL);
@@ -1270,36 +1382,9 @@ static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
     crFinish(st->pktin);
 }
 
-static void ssh1_pktout_size(struct Packet *pkt, int len)
-{
-    int pad, biglen;
-
-    len += 5;                         /* type and CRC */
-    pad = 8 - (len % 8);
-    biglen = len + pad;
-
-    pkt->length = len - 5;
-    if (pkt->maxlen < biglen) {
-       pkt->maxlen = biglen;
-       pkt->data = sresize(pkt->data, biglen + 4 + APIEXTRA, unsigned char);
-    }
-    pkt->body = pkt->data + 4 + pad + 1;
-}
-
-static struct Packet *s_wrpkt_start(int type, int len)
-{
-    struct Packet *pkt = ssh_new_packet();
-    ssh1_pktout_size(pkt, len);
-    pkt->type = type;
-    /* Initialise log omission state */
-    pkt->nblanks = 0;
-    pkt->blanks = NULL;
-    return pkt;
-}
-
-static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt)
+static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt, int *offset_p)
 {
-    int pad, biglen, i;
+    int pad, biglen, i, pktoffs;
     unsigned long crc;
 #ifdef __SC__
     /*
@@ -1312,12 +1397,10 @@ static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt)
 #endif
     int len;
 
-    pkt->body[-1] = pkt->type;
-
     if (ssh->logctx)
-       log_packet(ssh->logctx, PKT_OUTGOING, pkt->type,
-                  ssh1_pkt_type(pkt->type),
-                  pkt->body, pkt->length,
+       log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[12],
+                  ssh1_pkt_type(pkt->data[12]),
+                  pkt->body, pkt->length - (pkt->body - pkt->data),
                   pkt->nblanks, pkt->blanks);
     sfree(pkt->blanks); pkt->blanks = NULL;
     pkt->nblanks = 0;
@@ -1326,132 +1409,99 @@ static int s_wrpkt_prepare(Ssh ssh, struct Packet *pkt)
        unsigned char *compblk;
        int complen;
        zlib_compress_block(ssh->cs_comp_ctx,
-                           pkt->body - 1, pkt->length + 1,
+                           pkt->data + 12, pkt->length - 12,
                            &compblk, &complen);
-       ssh1_pktout_size(pkt, complen - 1);
-       memcpy(pkt->body - 1, compblk, complen);
+       memcpy(pkt->data + 12, compblk, complen);
        sfree(compblk);
+       pkt->length = complen + 12;
     }
 
-    len = pkt->length + 5;            /* type and CRC */
+    ssh_pkt_ensure(pkt, pkt->length + 4); /* space for CRC */
+    pkt->length += 4;
+    len = pkt->length - 4 - 8; /* len(type+data+CRC) */
     pad = 8 - (len % 8);
-    biglen = len + pad;
+    pktoffs = 8 - pad;
+    biglen = len + pad;                /* len(padding+type+data+CRC) */
 
-    for (i = 0; i < pad; i++)
-       pkt->data[i + 4] = random_byte();
-    crc = crc32_compute(pkt->data + 4, biglen - 4);
-    PUT_32BIT(pkt->data + biglen, crc);
-    PUT_32BIT(pkt->data, len);
+    for (i = pktoffs; i < 4+8; i++)
+       pkt->data[i] = random_byte();
+    crc = crc32_compute(pkt->data + pktoffs + 4, biglen - 4); /* all ex len */
+    PUT_32BIT(pkt->data + pktoffs + 4 + biglen - 4, crc);
+    PUT_32BIT(pkt->data + pktoffs, len);
 
     if (ssh->cipher)
-       ssh->cipher->encrypt(ssh->v1_cipher_ctx, pkt->data + 4, biglen);
+       ssh->cipher->encrypt(ssh->v1_cipher_ctx,
+                            pkt->data + pktoffs + 4, biglen);
 
-    return biglen + 4;
+    if (offset_p) *offset_p = pktoffs;
+    return biglen + 4;         /* len(length+padding+type+data+CRC) */
 }
 
 static void s_wrpkt(Ssh ssh, struct Packet *pkt)
 {
-    int len, backlog;
-    len = s_wrpkt_prepare(ssh, pkt);
-    backlog = sk_write(ssh->s, (char *)pkt->data, len);
+    int len, backlog, offset;
+    len = s_wrpkt_prepare(ssh, pkt, &offset);
+    backlog = sk_write(ssh->s, (char *)pkt->data + offset, len);
     if (backlog > SSH_MAX_BACKLOG)
        ssh_throttle_all(ssh, 1, backlog);
+    ssh_free_packet(pkt);
 }
 
 static void s_wrpkt_defer(Ssh ssh, struct Packet *pkt)
 {
-    int len;
-    len = s_wrpkt_prepare(ssh, pkt);
+    int len, offset;
+    len = s_wrpkt_prepare(ssh, pkt, &offset);
     if (ssh->deferred_len + len > ssh->deferred_size) {
        ssh->deferred_size = ssh->deferred_len + len + 128;
        ssh->deferred_send_data = sresize(ssh->deferred_send_data,
                                          ssh->deferred_size,
                                          unsigned char);
     }
-    memcpy(ssh->deferred_send_data + ssh->deferred_len, pkt->data, len);
+    memcpy(ssh->deferred_send_data + ssh->deferred_len,
+          pkt->data + offset, len);
     ssh->deferred_len += len;
+    ssh_free_packet(pkt);
 }
 
 /*
- * Construct a packet with the specified contents.
+ * Construct a SSH-1 packet with the specified contents.
+ * (This all-at-once interface used to be the only one, but now SSH-1
+ * packets can also be constructed incrementally.)
  */
-static struct Packet *construct_packet(Ssh ssh, int pkttype,
-                                      va_list ap1, va_list ap2)
+static struct Packet *construct_packet(Ssh ssh, int pkttype, va_list ap)
 {
-    unsigned char *p, *argp, argchar;
-    unsigned long argint;
-    int pktlen, argtype, arglen;
+    int argtype;
     Bignum bn;
     struct Packet *pkt;
 
-    pktlen = 0;
-    while ((argtype = va_arg(ap1, int)) != PKT_END) {
-       switch (argtype) {
-         case PKT_INT:
-           (void) va_arg(ap1, int);
-           pktlen += 4;
-           break;
-         case PKT_CHAR:
-           (void) va_arg(ap1, int);
-           pktlen++;
-           break;
-         case PKT_DATA:
-           (void) va_arg(ap1, unsigned char *);
-           arglen = va_arg(ap1, int);
-           pktlen += arglen;
-           break;
-         case PKT_STR:
-           argp = va_arg(ap1, unsigned char *);
-           arglen = strlen((char *)argp);
-           pktlen += 4 + arglen;
-           break;
-         case PKT_BIGNUM:
-           bn = va_arg(ap1, Bignum);
-           pktlen += ssh1_bignum_length(bn);
-           break;
-         case PKTT_PASSWORD:
-         case PKTT_DATA:
-         case PKTT_OTHER:
-           /* ignore this pass */
-           break;
-         default:
-           assert(0);
-       }
-    }
-
-    pkt = s_wrpkt_start(pkttype, pktlen);
-    p = pkt->body;
+    pkt = ssh1_pkt_init(pkttype);
 
-    while ((argtype = va_arg(ap2, int)) != PKT_END) {
-       int offset = p - pkt->body, len = 0;
+    while ((argtype = va_arg(ap, int)) != PKT_END) {
+       unsigned char *argp, argchar;
+       unsigned long argint;
+       int arglen;
        switch (argtype) {
          /* Actual fields in the packet */
          case PKT_INT:
-           argint = va_arg(ap2, int);
-           PUT_32BIT(p, argint);
-           len = 4;
+           argint = va_arg(ap, int);
+           ssh_pkt_adduint32(pkt, argint);
            break;
          case PKT_CHAR:
-           argchar = (unsigned char) va_arg(ap2, int);
-           *p = argchar;
-           len = 1;
+           argchar = (unsigned char) va_arg(ap, int);
+           ssh_pkt_addbyte(pkt, argchar);
            break;
          case PKT_DATA:
-           argp = va_arg(ap2, unsigned char *);
-           arglen = va_arg(ap2, int);
-           memcpy(p, argp, arglen);
-           len = arglen;
+           argp = va_arg(ap, unsigned char *);
+           arglen = va_arg(ap, int);
+           ssh_pkt_adddata(pkt, argp, arglen);
            break;
          case PKT_STR:
-           argp = va_arg(ap2, unsigned char *);
-           arglen = strlen((char *)argp);
-           PUT_32BIT(p, arglen);
-           memcpy(p + 4, argp, arglen);
-           len = arglen + 4;
+           argp = va_arg(ap, unsigned char *);
+           ssh_pkt_addstring(pkt, argp);
            break;
          case PKT_BIGNUM:
-           bn = va_arg(ap2, Bignum);
-           len = ssh1_write_bignum(p, bn);
+           bn = va_arg(ap, Bignum);
+           ssh1_pkt_addmp(pkt, bn);
            break;
          /* Tokens for modifications to packet logging */
          case PKTT_PASSWORD:
@@ -1464,16 +1514,6 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype,
            end_log_omission(ssh, pkt);
            break;
        }
-       p += len;
-       /* Deal with logfile omission, if required. */
-       if (len && (pkt->logmode != PKTLOG_EMIT)) {
-           pkt->nblanks++;
-           pkt->blanks = sresize(pkt->blanks, pkt->nblanks,
-                                 struct logblank_t);
-           pkt->blanks[pkt->nblanks-1].offset = offset;
-           pkt->blanks[pkt->nblanks-1].len    = len;
-           pkt->blanks[pkt->nblanks-1].type   = pkt->logmode;
-       }
     }
 
     return pkt;
@@ -1482,27 +1522,21 @@ static struct Packet *construct_packet(Ssh ssh, int pkttype,
 static void send_packet(Ssh ssh, int pkttype, ...)
 {
     struct Packet *pkt;
-    va_list ap1, ap2;
-    va_start(ap1, pkttype);
-    va_start(ap2, pkttype);
-    pkt = construct_packet(ssh, pkttype, ap1, ap2);
-    va_end(ap2);
-    va_end(ap1);
+    va_list ap;
+    va_start(ap, pkttype);
+    pkt = construct_packet(ssh, pkttype, ap);
+    va_end(ap);
     s_wrpkt(ssh, pkt);
-    ssh_free_packet(pkt);
 }
 
 static void defer_packet(Ssh ssh, int pkttype, ...)
 {
     struct Packet *pkt;
-    va_list ap1, ap2;
-    va_start(ap1, pkttype);
-    va_start(ap2, pkttype);
-    pkt = construct_packet(ssh, pkttype, ap1, ap2);
-    va_end(ap2);
-    va_end(ap1);
+    va_list ap;
+    va_start(ap, pkttype);
+    pkt = construct_packet(ssh, pkttype, ap);
+    va_end(ap);
     s_wrpkt_defer(ssh, pkt);
-    ssh_free_packet(pkt);
 }
 
 static int ssh_versioncmp(char *a, char *b)
@@ -1529,7 +1563,6 @@ static int ssh_versioncmp(char *a, char *b)
  * Utility routines for putting an SSH-protocol `string' and
  * `uint32' into a SHA state.
  */
-#include <stdio.h>
 static void sha_string(SHA_State * s, void *str, int len)
 {
     unsigned char lenblk[4];
@@ -1546,77 +1579,80 @@ static void sha_uint32(SHA_State * s, unsigned i)
 }
 
 /*
- * SSH-2 packet construction functions.
+ * Packet construction functions. Mostly shared between SSH-1 and SSH-2.
  */
-static void ssh2_pkt_ensure(struct Packet *pkt, int length)
+static void ssh_pkt_ensure(struct Packet *pkt, int length)
 {
     if (pkt->maxlen < length) {
+       unsigned char *body = pkt->body;
+       int offset = body ? pkt->data - body : 0;
        pkt->maxlen = length + 256;
        pkt->data = sresize(pkt->data, pkt->maxlen + APIEXTRA, unsigned char);
+       if (body) pkt->body = pkt->data + offset;
     }
 }
-static void ssh2_pkt_adddata(struct Packet *pkt, void *data, int len)
+static void ssh_pkt_adddata(struct Packet *pkt, void *data, int len)
 {
     if (pkt->logmode != PKTLOG_EMIT) {
        pkt->nblanks++;
        pkt->blanks = sresize(pkt->blanks, pkt->nblanks, struct logblank_t);
-       pkt->blanks[pkt->nblanks-1].offset = pkt->length - 6;
+       assert(pkt->body);
+       pkt->blanks[pkt->nblanks-1].offset = pkt->length -
+                                            (pkt->body - pkt->data);
        pkt->blanks[pkt->nblanks-1].len = len;
        pkt->blanks[pkt->nblanks-1].type = pkt->logmode;
     }
     pkt->length += len;
-    ssh2_pkt_ensure(pkt, pkt->length);
+    ssh_pkt_ensure(pkt, pkt->length);
     memcpy(pkt->data + pkt->length - len, data, len);
 }
-static void ssh2_pkt_addbyte(struct Packet *pkt, unsigned char byte)
-{
-    ssh2_pkt_adddata(pkt, &byte, 1);
-}
-static struct Packet *ssh2_pkt_init(int pkt_type)
+static void ssh_pkt_addbyte(struct Packet *pkt, unsigned char byte)
 {
-    struct Packet *pkt = ssh_new_packet();
-    pkt->length = 5;
-    pkt->forcepad = 0;
-    ssh2_pkt_addbyte(pkt, (unsigned char) pkt_type);
-    return pkt;
+    ssh_pkt_adddata(pkt, &byte, 1);
 }
 static void ssh2_pkt_addbool(struct Packet *pkt, unsigned char value)
 {
-    ssh2_pkt_adddata(pkt, &value, 1);
+    ssh_pkt_adddata(pkt, &value, 1);
 }
-static void ssh2_pkt_adduint32(struct Packet *pkt, unsigned long value)
+static void ssh_pkt_adduint32(struct Packet *pkt, unsigned long value)
 {
     unsigned char x[4];
     PUT_32BIT(x, value);
-    ssh2_pkt_adddata(pkt, x, 4);
+    ssh_pkt_adddata(pkt, x, 4);
 }
-static void ssh2_pkt_addstring_start(struct Packet *pkt)
+static void ssh_pkt_addstring_start(struct Packet *pkt)
 {
-    ssh2_pkt_adduint32(pkt, 0);
+    ssh_pkt_adduint32(pkt, 0);
     pkt->savedpos = pkt->length;
 }
-static void ssh2_pkt_addstring_str(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring_str(struct Packet *pkt, char *data)
 {
-    ssh2_pkt_adddata(pkt, data, strlen(data));
+    ssh_pkt_adddata(pkt, data, strlen(data));
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh2_pkt_addstring_data(struct Packet *pkt, char *data, int len)
+static void ssh_pkt_addstring_data(struct Packet *pkt, char *data, int len)
 {
-    ssh2_pkt_adddata(pkt, data, len);
+    ssh_pkt_adddata(pkt, data, len);
     PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 }
-static void ssh2_pkt_addstring(struct Packet *pkt, char *data)
+static void ssh_pkt_addstring(struct Packet *pkt, char *data)
 {
-    ssh2_pkt_addstring_start(pkt);
-    ssh2_pkt_addstring_str(pkt, data);
+    ssh_pkt_addstring_start(pkt);
+    ssh_pkt_addstring_str(pkt, data);
+}
+static void ssh1_pkt_addmp(struct Packet *pkt, Bignum b)
+{
+    int len = ssh1_bignum_length(b);
+    unsigned char *data = snewn(len, char);
+    (void) ssh1_write_bignum(data, b);
+    ssh_pkt_adddata(pkt, data, len);
+    sfree(data);
 }
 static unsigned char *ssh2_mpint_fmt(Bignum b, int *len)
 {
     unsigned char *p;
     int i, n = (bignum_bitcount(b) + 7) / 8;
     p = snewn(n + 1, unsigned char);
-    if (!p)
-       fatalbox("out of memory");
     p[0] = 0;
     for (i = 1; i <= n; i++)
        p[i] = bignum_byte(b, n - i);
@@ -1632,11 +1668,40 @@ static void ssh2_pkt_addmp(struct Packet *pkt, Bignum b)
     unsigned char *p;
     int len;
     p = ssh2_mpint_fmt(b, &len);
-    ssh2_pkt_addstring_start(pkt);
-    ssh2_pkt_addstring_data(pkt, (char *)p, len);
+    ssh_pkt_addstring_start(pkt);
+    ssh_pkt_addstring_data(pkt, (char *)p, len);
     sfree(p);
 }
 
+static struct Packet *ssh1_pkt_init(int pkt_type)
+{
+    struct Packet *pkt = ssh_new_packet();
+    pkt->length = 4 + 8;           /* space for length + max padding */
+    ssh_pkt_addbyte(pkt, pkt_type);
+    pkt->body = pkt->data + pkt->length;
+    return pkt;
+}
+
+/* For legacy code (SSH-1 and -2 packet construction used to be separate) */
+#define ssh2_pkt_ensure(pkt, length) ssh_pkt_ensure(pkt, length)
+#define ssh2_pkt_adddata(pkt, data, len) ssh_pkt_adddata(pkt, data, len)
+#define ssh2_pkt_addbyte(pkt, byte) ssh_pkt_addbyte(pkt, byte)
+#define ssh2_pkt_adduint32(pkt, value) ssh_pkt_adduint32(pkt, value)
+#define ssh2_pkt_addstring_start(pkt) ssh_pkt_addstring_start(pkt)
+#define ssh2_pkt_addstring_str(pkt, data) ssh_pkt_addstring_str(pkt, data)
+#define ssh2_pkt_addstring_data(pkt, data, len) ssh_pkt_addstring_data(pkt, data, len)
+#define ssh2_pkt_addstring(pkt, data) ssh_pkt_addstring(pkt, data)
+
+static struct Packet *ssh2_pkt_init(int pkt_type)
+{
+    struct Packet *pkt = ssh_new_packet();
+    pkt->length = 5;
+    pkt->forcepad = 0;
+    ssh_pkt_addbyte(pkt, (unsigned char) pkt_type);
+    pkt->body = pkt->data + pkt->length;
+    return pkt;
+}
+
 /*
  * Construct an SSH-2 final-form packet: compress it, encrypt it,
  * put the MAC on it. Final packet, ready to be sent, is stored in
@@ -1649,7 +1714,7 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
     if (ssh->logctx)
        log_packet(ssh->logctx, PKT_OUTGOING, pkt->data[5],
                   ssh2_pkt_type(ssh->pkt_ctx, pkt->data[5]),
-                  pkt->data + 6, pkt->length - 6,
+                  pkt->body, pkt->length - (pkt->body - pkt->data),
                   pkt->nblanks, pkt->blanks);
     sfree(pkt->blanks); pkt->blanks = NULL;
     pkt->nblanks = 0;
@@ -1733,8 +1798,19 @@ static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
  * ssh2_pkt_send() or ssh2_pkt_defer() either go straight to one of
  * these or get queued, and then when the queue is later emptied
  * the packets are all passed to defer_noqueue().
+ *
+ * When using a CBC-mode cipher, it's necessary to ensure that an
+ * attacker can't provide data to be encrypted using an IV that they
+ * know.  We ensure this by prefixing each packet that might contain
+ * user data with an SSH_MSG_IGNORE.  This is done using the deferral
+ * mechanism, so in this case send_noqueue() ends up redirecting to
+ * defer_noqueue().  If you don't like this inefficiency, don't use
+ * CBC.
  */
 
+static void ssh2_pkt_defer_noqueue(Ssh, struct Packet *, int);
+static void ssh_pkt_defersend(Ssh);
+
 /*
  * Send an SSH-2 packet immediately, without queuing or deferring.
  */
@@ -1742,6 +1818,12 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
 {
     int len;
     int backlog;
+    if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC)) {
+       /* We need to send two packets, so use the deferral mechanism. */
+       ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
+       ssh_pkt_defersend(ssh);
+       return;
+    }
     len = ssh2_pkt_construct(ssh, pkt);
     backlog = sk_write(ssh->s, (char *)pkt->data, len);
     if (backlog > SSH_MAX_BACKLOG)
@@ -1759,9 +1841,19 @@ static void ssh2_pkt_send_noqueue(Ssh ssh, struct Packet *pkt)
 /*
  * Defer an SSH-2 packet.
  */
-static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt)
+static void ssh2_pkt_defer_noqueue(Ssh ssh, struct Packet *pkt, int noignore)
 {
-    int len = ssh2_pkt_construct(ssh, pkt);
+    int len;
+    if (ssh->cscipher != NULL && (ssh->cscipher->flags & SSH_CIPHER_IS_CBC) &&
+       ssh->deferred_len == 0 && !noignore) {
+       /*
+        * Interpose an SSH_MSG_IGNORE to ensure that user data don't
+        * get encrypted with a known IV.
+        */
+       struct Packet *ipkt = ssh2_pkt_init(SSH2_MSG_IGNORE);
+       ssh2_pkt_defer_noqueue(ssh, ipkt, TRUE);
+    }
+    len = ssh2_pkt_construct(ssh, pkt);
     if (ssh->deferred_len + len > ssh->deferred_size) {
        ssh->deferred_size = ssh->deferred_len + len + 128;
        ssh->deferred_send_data = sresize(ssh->deferred_send_data,
@@ -1811,7 +1903,7 @@ static void ssh2_pkt_defer(Ssh ssh, struct Packet *pkt)
     if (ssh->queueing)
        ssh2_pkt_queue(ssh, pkt);
     else
-       ssh2_pkt_defer_noqueue(ssh, pkt);
+       ssh2_pkt_defer_noqueue(ssh, pkt, FALSE);
 }
 #endif
 
@@ -1859,7 +1951,7 @@ static void ssh2_pkt_queuesend(Ssh ssh)
     assert(!ssh->queueing);
 
     for (i = 0; i < ssh->queuelen; i++)
-       ssh2_pkt_defer_noqueue(ssh, ssh->queue[i]);
+       ssh2_pkt_defer_noqueue(ssh, ssh->queue[i], FALSE);
     ssh->queuelen = 0;
 
     ssh_pkt_defersend(ssh);
@@ -2319,6 +2411,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c)
                   strcspn(verstring, "\015\012"), verstring);
        sk_write(ssh->s, verstring, strlen(verstring));
         sfree(verstring);
+       if (ssh->version == 2)
+           do_ssh2_transport(ssh, NULL, -1, NULL);
     }
 
     logeventf(ssh, "Using SSH protocol version %d", ssh->version);
@@ -2502,20 +2596,20 @@ static int ssh_closing(Plug plug, const char *error_msg, int error_code,
     Ssh ssh = (Ssh) plug;
     int need_notify = ssh_do_close(ssh, FALSE);
 
-    if (!error_msg && !ssh->close_expected) {
-        error_msg = "Server unexpectedly closed network connection";
+    if (!error_msg) {
+       if (!ssh->close_expected)
+           error_msg = "Server unexpectedly closed network connection";
+       else
+           error_msg = "Server closed network connection";
     }
 
     if (need_notify)
         notify_remote_exit(ssh->frontend);
 
-    if (error_msg) {
-       /* A socket error has occurred. */
+    if (error_msg)
        logevent(error_msg);
+    if (!ssh->close_expected || !ssh->clean_exit)
        connection_fatal(ssh->frontend, "%s", error_msg);
-    } else {
-        logevent("Server closed network connection");
-    }
     return 0;
 }
 
@@ -2562,8 +2656,6 @@ static const char *connect_to_host(Ssh ssh, char *host, int port,
     const char *err;
 
     ssh->savedhost = snewn(1 + strlen(host), char);
-    if (!ssh->savedhost)
-       fatalbox("Out of memory");
     strcpy(ssh->savedhost, host);
 
     if (port < 0)
@@ -2779,6 +2871,39 @@ static void ssh_agentf_callback(void *cv, void *reply, int replylen)
 }
 
 /*
+ * Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
+ * non-NULL, otherwise just close the connection. `client_reason' == NULL
+ * => log `wire_reason'.
+ */
+static void ssh_disconnect(Ssh ssh, char *client_reason, char *wire_reason,
+                          int code, int clean_exit)
+{
+    char *error;
+    if (!client_reason)
+       client_reason = wire_reason;
+    if (client_reason)
+       error = dupprintf("Disconnected: %s", client_reason);
+    else
+       error = dupstr("Disconnected");
+    if (wire_reason) {
+       if (ssh->version == 1) {
+           send_packet(ssh, SSH1_MSG_DISCONNECT, PKT_STR, wire_reason,
+                       PKT_END);
+       } else if (ssh->version == 2) {
+           struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+           ssh2_pkt_adduint32(pktout, code);
+           ssh2_pkt_addstring(pktout, wire_reason);
+           ssh2_pkt_addstring(pktout, "en");   /* language tag */
+           ssh2_pkt_send_noqueue(ssh, pktout);
+       }
+    }
+    ssh->close_expected = TRUE;
+    ssh->clean_exit = clean_exit;
+    ssh_closing((Plug)ssh, error, 0, 0);
+    sfree(error);
+}
+
+/*
  * Handle the key exchange and user authentication phases.
  */
 static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
@@ -2883,8 +3008,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
     s->len = (hostkey.bytes > servkey.bytes ? hostkey.bytes : servkey.bytes);
 
     s->rsabuf = snewn(s->len, unsigned char);
-    if (!s->rsabuf)
-       fatalbox("Out of memory");
 
     /*
      * Verify the host key.
@@ -2896,8 +3019,6 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
        int len = rsastr_len(&hostkey);
        char fingerprint[100];
        char *keystr = snewn(len, char);
-       if (!keystr)
-           fatalbox("Out of memory");
        rsastr_fmt(keystr, &hostkey);
        rsa_fingerprint(fingerprint, sizeof(fingerprint), &hostkey);
 
@@ -2921,8 +3042,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
         ssh_set_frozen(ssh, 0);
 
         if (s->dlgret == 0) {
-            ssh->close_expected = TRUE;
-            ssh_closing((Plug)ssh, NULL, 0, 0);
+           ssh_disconnect(ssh, "User aborted at host key verification",
+                          NULL, 0, TRUE);
            crStop(0);
         }
     }
@@ -3002,8 +3123,8 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
            }
             ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
         }
@@ -3077,9 +3198,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                     * get_line failed to get a username.
                     * Terminate.
                     */
-                   logevent("No username provided. Abandoning session.");
-                   ssh->close_expected = TRUE;
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
+                   ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
                    crStop(1);
                }
            } else {
@@ -3416,13 +3535,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
                 * because one was supplied on the command line
                 * which has already failed to work). Terminate.
                 */
-               send_packet(ssh, SSH1_MSG_DISCONNECT,
-                           PKT_STR, "No more passwords available to try",
-                           PKT_END);
-               logevent("Unable to authenticate");
-               connection_fatal(ssh->frontend, "Unable to authenticate");
-               ssh->close_expected = TRUE;
-                ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, NULL, "Unable to authenticate", 0, FALSE);
                crStop(1);
            }
        } else {
@@ -4470,10 +4583,30 @@ static void ssh1_smsg_exit_status(Ssh ssh, struct Packet *pktin)
      * encrypted packet, we close the session once
      * we've sent EXIT_CONFIRMATION.
      */
-    ssh->close_expected = TRUE;
-    ssh_closing((Plug)ssh, NULL, 0, 0);
+    ssh_disconnect(ssh, NULL, NULL, 0, TRUE);
 }
 
+/* Helper function to deal with sending tty modes for REQUEST_PTY */
+static void ssh1_send_ttymode(void *data, char *mode, char *val)
+{
+    struct Packet *pktout = (struct Packet *)data;
+    int i = 0;
+    unsigned int arg = 0;
+    while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+    if (i == lenof(ssh_ttymodes)) return;
+    switch (ssh_ttymodes[i].type) {
+      case TTY_OP_CHAR:
+       arg = ssh_tty_parse_specchar(val);
+       break;
+      case TTY_OP_BOOL:
+       arg = ssh_tty_parse_boolean(val);
+       break;
+    }
+    ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+    ssh2_pkt_addbyte(pktout, arg);
+}
+
+
 static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
                               struct Packet *pktin)
 {
@@ -4518,14 +4651,23 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
        ssh->x11auth = x11_invent_auth(proto, sizeof(proto),
                                       data, sizeof(data), ssh->cfg.x11_auth);
         x11_get_real_auth(ssh->x11auth, ssh->cfg.x11_display);
+       /*
+        * Note that while we blank the X authentication data here, we don't
+        * take any special action to blank the start of an X11 channel,
+        * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+        * without having session blanking enabled is likely to leak your
+        * cookie into the log.
+        */
        if (ssh->v1_local_protoflags & SSH1_PROTOFLAG_SCREEN_NUMBER) {
            send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-                       PKT_STR, proto, PKT_STR, data,
+                       PKT_STR, proto,
+                       PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER,
                        PKT_INT, x11_get_screen_number(ssh->cfg.x11_display),
                        PKT_END);
        } else {
            send_packet(ssh, SSH1_CMSG_X11_REQUEST_FORWARDING,
-                       PKT_STR, proto, PKT_STR, data, PKT_END);
+                       PKT_STR, proto,
+                       PKTT_PASSWORD, PKT_STR, data, PKTT_OTHER, PKT_END);
        }
        do {
            crReturnV;
@@ -4547,19 +4689,26 @@ static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
     ssh->packet_dispatch[SSH1_MSG_PORT_OPEN] = ssh1_msg_port_open;
 
     if (!ssh->cfg.nopty) {
+       struct Packet *pkt;
        /* Unpick the terminal-speed string. */
        /* XXX perhaps we should allow no speeds to be sent. */
        ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
        sscanf(ssh->cfg.termspeed, "%d,%d", &ssh->ospeed, &ssh->ispeed);
        /* Send the pty request. */
-       send_packet(ssh, SSH1_CMSG_REQUEST_PTY,
-                   PKT_STR, ssh->cfg.termtype,
-                   PKT_INT, ssh->term_height,
-                   PKT_INT, ssh->term_width,
-                   PKT_INT, 0, PKT_INT, 0, /* width,height in pixels */
-                   PKT_CHAR, 192, PKT_INT, ssh->ispeed, /* TTY_OP_ISPEED */
-                   PKT_CHAR, 193, PKT_INT, ssh->ospeed, /* TTY_OP_OSPEED */
-                   PKT_CHAR, 0, PKT_END);
+       pkt = ssh1_pkt_init(SSH1_CMSG_REQUEST_PTY);
+       ssh_pkt_addstring(pkt, ssh->cfg.termtype);
+       ssh_pkt_adduint32(pkt, ssh->term_height);
+       ssh_pkt_adduint32(pkt, ssh->term_width);
+       ssh_pkt_adduint32(pkt, 0); /* width in pixels */
+       ssh_pkt_adduint32(pkt, 0); /* height in pixels */
+       parse_ttymodes(ssh, ssh->cfg.ttymodes,
+                      ssh1_send_ttymode, (void *)pkt);
+       ssh_pkt_addbyte(pkt, SSH1_TTY_OP_ISPEED);
+       ssh_pkt_adduint32(pkt, ssh->ispeed);
+       ssh_pkt_addbyte(pkt, SSH1_TTY_OP_OSPEED);
+       ssh_pkt_adduint32(pkt, ssh->ospeed);
+       ssh_pkt_addbyte(pkt, SSH_TTY_OP_END);
+       s_wrpkt(ssh, pkt);
        ssh->state = SSH_STATE_INTERMED;
        do {
            crReturnV;
@@ -5209,8 +5358,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at kex warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
@@ -5234,8 +5383,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
@@ -5259,8 +5408,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
            }
            ssh_set_frozen(ssh, 0);
            if (s->dlgret == 0) {
-               ssh->close_expected = TRUE;
-               ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
+                              0, TRUE);
                crStop(0);
            }
        }
@@ -5408,8 +5557,8 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
     }
     ssh_set_frozen(ssh, 0);
     if (s->dlgret == 0) {
-        ssh->close_expected = TRUE;
-        ssh_closing((Plug)ssh, NULL, 0, 0);
+       ssh_disconnect(ssh, "User aborted at host key verification", NULL,
+                      0, TRUE);
         crStop(0);
     }
     if (!s->got_session_id) {     /* don't bother logging this in rekeys */
@@ -5575,7 +5724,7 @@ static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
      * it would only confuse the layer above.
      */
     if (s->activated_authconn) {
-       crReturn(1);
+       crReturn(0);
     }
     s->activated_authconn = TRUE;
 
@@ -5890,8 +6039,6 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
      * not running in -N mode.)
      */
     if (!ssh->cfg.ssh_no_shell && count234(ssh->channels) == 0) {
-       logevent("All channels closed. Disconnecting");
-#if 0
        /*
         * We used to send SSH_MSG_DISCONNECT here,
         * because I'd believed that _every_ conforming
@@ -5903,14 +6050,7 @@ static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
         * this is more polite than sending a
         * DISCONNECT. So now we don't.
         */
-       s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-       ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION);
-       ssh2_pkt_addstring(s->pktout, "All open channels closed");
-       ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-       ssh2_pkt_send_noqueue(ssh, s->pktout);
-#endif
-       ssh->close_expected = TRUE;
-       ssh_closing((Plug)ssh, NULL, 0, 0);
+       ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
     }
 }
 
@@ -5997,18 +6137,10 @@ static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
      */
     c = find234(ssh->channels, &localid, ssh_channelfind);
     if (!c) {
-       char buf[80];
-       sprintf(buf, "Received channel request for nonexistent"
-               " channel %d", localid);
-       logevent(buf);
-       pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-       ssh2_pkt_adduint32(pktout, SSH2_DISCONNECT_BY_APPLICATION);
-       ssh2_pkt_addstring(pktout, buf);
-       ssh2_pkt_addstring(pktout, "en");       /* language tag */
-       ssh2_pkt_send_noqueue(ssh, pktout);
-       connection_fatal(ssh->frontend, "%s", buf);
-       ssh->close_expected = TRUE;
-       ssh_closing((Plug)ssh, NULL, 0, 0);
+       char *buf = dupprintf("Received channel request for nonexistent"
+                             " channel %d", localid);
+       ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+       sfree(buf);
        return;
     }
 
@@ -6218,7 +6350,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
            }
        }
     } else if (typelen == 22 &&
-              !memcmp(type, "auth-agent@openssh.com", 3)) {
+              !memcmp(type, "auth-agent@openssh.com", 22)) {
        if (!ssh->agentfwd_enabled)
            error = "Agent forwarding is not enabled";
        else {
@@ -6258,6 +6390,41 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin)
 }
 
 /*
+ * Buffer banner messages for later display at some convenient point.
+ */
+static void ssh2_msg_userauth_banner(Ssh ssh, struct Packet *pktin)
+{
+    /* Arbitrary limit to prevent unbounded inflation of buffer */
+    if (bufchain_size(&ssh->banner) <= 131072) {
+       char *banner = NULL;
+       int size = 0;
+       ssh_pkt_getstring(pktin, &banner, &size);
+       if (banner)
+           bufchain_add(&ssh->banner, banner, size);
+    }
+}
+
+/* Helper function to deal with sending tty modes for "pty-req" */
+static void ssh2_send_ttymode(void *data, char *mode, char *val)
+{
+    struct Packet *pktout = (struct Packet *)data;
+    int i = 0;
+    unsigned int arg = 0;
+    while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
+    if (i == lenof(ssh_ttymodes)) return;
+    switch (ssh_ttymodes[i].type) {
+      case TTY_OP_CHAR:
+       arg = ssh_tty_parse_specchar(val);
+       break;
+      case TTY_OP_BOOL:
+       arg = ssh_tty_parse_boolean(val);
+       break;
+    }
+    ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+    ssh2_pkt_adduint32(pktout, arg);
+}
+
+/*
  * Handle the SSH-2 userauth and connection layers.
  */
 static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
@@ -6278,6 +6445,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                AUTH_TYPE_KEYBOARD_INTERACTIVE,
                AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
        } type;
+       int done_service_req;
        int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
        int tried_pubkey_config, tried_agent;
        int kbd_inter_running, kbd_inter_refused;
@@ -6305,16 +6473,33 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 
     crBegin(ssh->do_ssh2_authconn_crstate);
 
-    /*
-     * Request userauth protocol, and await a response to it.
-     */
-    s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
-    ssh2_pkt_addstring(s->pktout, "ssh-userauth");
-    ssh2_pkt_send(ssh, s->pktout);
-    crWaitUntilV(pktin);
-    if (pktin->type != SSH2_MSG_SERVICE_ACCEPT) {
-       bombout(("Server refused user authentication protocol"));
-       crStopV;
+    s->done_service_req = FALSE;
+    s->we_are_in = FALSE;
+    if (!ssh->cfg.ssh_no_userauth) {
+       /*
+        * Request userauth protocol, and await a response to it.
+        */
+       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+       ssh2_pkt_addstring(s->pktout, "ssh-userauth");
+       ssh2_pkt_send(ssh, s->pktout);
+       crWaitUntilV(pktin);
+       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT)
+           s->done_service_req = TRUE;
+    }
+    if (!s->done_service_req) {
+       /*
+        * Request connection protocol directly, without authentication.
+        */
+       s->pktout = ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+       ssh2_pkt_addstring(s->pktout, "ssh-connection");
+       ssh2_pkt_send(ssh, s->pktout);
+       crWaitUntilV(pktin);
+       if (pktin->type == SSH2_MSG_SERVICE_ACCEPT) {
+           s->we_are_in = TRUE; /* no auth required */
+       } else {
+           bombout(("Server refused service request"));
+           crStopV;
+       }
     }
 
     /*
@@ -6343,7 +6528,10 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
      */
     s->username[0] = '\0';
     s->got_username = FALSE;
-    do {
+    bufchain_init(&ssh->banner);
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] =
+       ssh2_msg_userauth_banner;
+    while (!s->we_are_in) {
        /*
         * Get a username.
         */
@@ -6361,9 +6549,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                     * get_line failed to get a username.
                     * Terminate.
                     */
-                   logevent("No username provided. Abandoning session.");
-                   ssh->close_expected = TRUE;
-                    ssh_closing((Plug)ssh, NULL, 0, 0);
+                   ssh_disconnect(ssh, "No username provided", NULL, 0, TRUE);
                    crStopV;
                }
            } else {
@@ -6443,9 +6629,14 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
             */
            if (!s->gotit)
                crWaitUntilV(pktin);
-           while (pktin->type == SSH2_MSG_USERAUTH_BANNER) {
-               char *banner;
-               int size;
+           /*
+            * Now is a convenient point to spew any banner material
+            * that we've accumulated. (This should ensure that when
+            * we exit the auth loop, we haven't any left to deal
+            * with.)
+            */
+           {
+               int size = bufchain_size(&ssh->banner);
                /*
                 * Don't show the banner if we're operating in
                 * non-verbose non-interactive mode. (It's probably
@@ -6454,12 +6645,13 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                 * the banner will screw up processing on the
                 * output of (say) plink.)
                 */
-               if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
-                   ssh_pkt_getstring(pktin, &banner, &size);
-                   if (banner)
-                       c_write_untrusted(ssh, banner, size);
+               if (size && (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE))) {
+                   char *banner = snewn(size, char);
+                   bufchain_fetch(&ssh->banner, banner, size);
+                   c_write_untrusted(ssh, banner, size);
+                   sfree(banner);
                }
-               crWaitUntilV(pktin);
+               bufchain_clear(&ssh->banner);
            }
            if (pktin->type == SSH2_MSG_USERAUTH_SUCCESS) {
                logevent("Access granted");
@@ -6908,17 +7100,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                         * command line which has already failed to
                         * work). Terminate.
                         */
-                       s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-                       ssh2_pkt_adduint32(s->pktout,SSH2_DISCONNECT_BY_APPLICATION);
-                       ssh2_pkt_addstring(s->pktout, "No more passwords available"
-                                          " to try");
-                       ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-                       ssh2_pkt_send_noqueue(ssh, s->pktout);
-                       logevent("Unable to authenticate");
-                       connection_fatal(ssh->frontend,
-                                        "Unable to authenticate");
-                       ssh->close_expected = TRUE;
-                        ssh_closing((Plug)ssh, NULL, 0, 0);
+                       ssh_disconnect(ssh, NULL, "Unable to authenticate",
+                                      SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
+                                      FALSE);
                        crStopV;
                    }
                } else {
@@ -7069,27 +7253,18 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                }
                s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
            } else {
-               c_write_str(ssh, "No supported authentication methods"
-                           " left to try!\r\n");
-               logevent("No supported authentications offered."
-                        " Disconnecting");
-               s->pktout = ssh2_pkt_init(SSH2_MSG_DISCONNECT);
-               ssh2_pkt_adduint32(s->pktout, SSH2_DISCONNECT_BY_APPLICATION);
-               ssh2_pkt_addstring(s->pktout, "No supported authentication"
-                                  " methods available");
-               ssh2_pkt_addstring(s->pktout, "en");    /* language tag */
-               ssh2_pkt_send_noqueue(ssh, s->pktout);
-               ssh->close_expected = TRUE;
-                ssh_closing((Plug)ssh, NULL, 0, 0);
+               ssh_disconnect(ssh, NULL,
+                              "No supported authentication methods available",
+                              SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE,
+                              FALSE);
                crStopV;
            }
        }
-    } while (!s->we_are_in);
+    }
+    ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
 
     /*
-     * Now we're authenticated for the connection protocol. The
-     * connection protocol will automatically have started at this
-     * point; there's no need to send SERVICE_REQUEST.
+     * Now the connection protocol has started, one way or another.
      */
 
     ssh->channels = newtree234(ssh_channelcmp);
@@ -7173,7 +7348,16 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_addbool(s->pktout, 1);        /* want reply */
        ssh2_pkt_addbool(s->pktout, 0);        /* many connections */
        ssh2_pkt_addstring(s->pktout, proto);
+       /*
+        * Note that while we blank the X authentication data here, we don't
+        * take any special action to blank the start of an X11 channel,
+        * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+        * without having session blanking enabled is likely to leak your
+        * cookie into the log.
+        */
+       dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
        ssh2_pkt_addstring(s->pktout, data);
+       end_log_omission(ssh, s->pktout);
        ssh2_pkt_adduint32(s->pktout, x11_get_screen_number(ssh->cfg.x11_display));
        ssh2_pkt_send(ssh, s->pktout);
 
@@ -7242,9 +7426,11 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
        ssh2_pkt_adduint32(s->pktout, 0);              /* pixel width */
        ssh2_pkt_adduint32(s->pktout, 0);              /* pixel height */
        ssh2_pkt_addstring_start(s->pktout);
-       ssh2_pkt_addbyte(s->pktout, 128);              /* TTY_OP_ISPEED */
+       parse_ttymodes(ssh, ssh->cfg.ttymodes,
+                      ssh2_send_ttymode, (void *)s->pktout);
+       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED);
        ssh2_pkt_adduint32(s->pktout, ssh->ispeed);
-       ssh2_pkt_addbyte(s->pktout, 129);              /* TTY_OP_OSPEED */
+       ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED);
        ssh2_pkt_adduint32(s->pktout, ssh->ospeed);
        ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
        ssh2_pkt_send(ssh, s->pktout);
@@ -7644,6 +7830,7 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
     ssh->hostkey = NULL;
     ssh->exitcode = -1;
     ssh->close_expected = FALSE;
+    ssh->clean_exit = FALSE;
     ssh->state = SSH_STATE_PREPACKET;
     ssh->size_needed = FALSE;
     ssh->eof_needed = FALSE;
@@ -7821,7 +8008,8 @@ static void ssh_reconfig(void *handle, Config *cfg)
     unsigned long old_max_data_size;
 
     pinger_reconfig(ssh->pinger, &ssh->cfg, cfg);
-    ssh_setup_portfwd(ssh, cfg);
+    if (ssh->portfwds)
+       ssh_setup_portfwd(ssh, cfg);
 
     if (ssh->cfg.ssh_rekey_time != cfg->ssh_rekey_time &&
        cfg->ssh_rekey_time != 0) {
@@ -7868,7 +8056,7 @@ static void ssh_reconfig(void *handle, Config *cfg)
 }
 
 /*
- * Called to send data down the Telnet connection.
+ * Called to send data down the SSH connection.
  */
 static int ssh_send(void *handle, char *buf, int len)
 {
@@ -8025,7 +8213,7 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 }
 
 /*
- * Send Telnet special codes. TS_EOF is useful for `plink', so you
+ * Send special codes. TS_EOF is useful for `plink', so you
  * can send an EOF and collect resulting output (e.g. `plink
  * hostname sort').
  */