]> asedeno.scripts.mit.edu Git - PuTTY.git/commitdiff
Merge tag '0.66'
authorSimon Tatham <anakin@pobox.com>
Sat, 7 Nov 2015 09:54:05 +0000 (09:54 +0000)
committerSimon Tatham <anakin@pobox.com>
Sat, 7 Nov 2015 09:54:05 +0000 (09:54 +0000)
This brings in the rest of the 0.66 branch, including some changes new
on master.

Conflicts:
        doc/plink.but
        sshrsa.c

(The conflicts were both trivial: in one, the addition of an extra
parameter to rsa2_newkey on master happened on the line next to 0.66's
addition of a check for NULL return value, and in the other, I'd got
the version number in the plink -h transcript messed up on master.)

1  2 
doc/plink.but
logging.c
ssh.c
sshrsa.c
terminal.c

diff --combined doc/plink.but
index 553639e6e97cc8bf2d237f84db195491cb5801c1,589d19ef193a7269047225b3c2e3df871fff7ad1..889d3b6acf6973c62e35f05883657fd544ebaeaf
@@@ -41,7 -41,7 +41,7 @@@ use Plink
  
  \c Z:\sysosd>plink
  \c Plink: command-line connection utility
- \c Release 0.XX
+ \c Release 0.66
  \c Usage: plink [options] [user@]host [command]
  \c        ("host" can also be a PuTTY saved session name)
  \c Options:
@@@ -232,27 -232,6 +232,27 @@@ line
  
  (This option is only meaningful with the SSH-2 protocol.)
  
 +\S2{plink-option-shareexists} \I{-shareexists-plink}\c{-shareexists}:
 +test for connection-sharing upstream
 +
 +This option does not make a new connection; instead it allows testing
 +for the presence of an existing connection that can be shared.
 +(See \k{config-ssh-sharing} for more information about SSH connection
 +sharing.)
 +
 +A Plink invocation of the form:
 +
 +\c plink -shareexists <session>
 +\e                    iiiiiiiii
 +
 +will test whether there is currently a viable \q{upstream} for the
 +session in question, which can be specified using any syntax you'd
 +normally use with Plink to make an actual connection (a host/port
 +number, a bare saved session name, \c{-load}, etc). It returns a
 +zero exit status if a usable \q{upstream} exists, nonzero otherwise.
 +
 +(This option is only meaningful with the SSH-2 protocol.)
 +
  \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
diff --combined logging.c
index 563dcded510001f9bb223ccf7301648d02f8317a,a40d32a6e28fdb82d9387e6216014840624df98c..865fe9b82ed8dc547b48ddb2d06cdc010dd8973d
+++ b/logging.c
@@@ -164,6 -164,7 +164,7 @@@ void logfopen(void *handle
  {
      struct LogContext *ctx = (struct LogContext *)handle;
      struct tm tm;
+     FILE *fp;
      int mode;
  
      /* Prevent repeat calls */
                     conf_get_str(ctx->conf, CONF_host),
                     conf_get_int(ctx->conf, CONF_port), &tm);
  
-     ctx->lgfp = f_open(ctx->currlogfilename, "r", FALSE);  /* file already present? */
-     if (ctx->lgfp) {
+     fp = f_open(ctx->currlogfilename, "r", FALSE);  /* file already present? */
+     if (fp) {
        int logxfovr = conf_get_int(ctx->conf, CONF_logxfovr);
-       fclose(ctx->lgfp);
+       fclose(fp);
        if (logxfovr != LGXF_ASK) {
            mode = ((logxfovr == LGXF_OVR) ? 2 : 1);
        } else
@@@ -255,7 -256,7 +256,7 @@@ void log_eventlog(void *handle, const c
   * Set of blanking areas must be in increasing order.
   */
  void log_packet(void *handle, int direction, int type,
 -              char *texttype, const void *data, int len,
 +              const char *texttype, const void *data, int len,
                int n_blanks, const struct logblank_t *blanks,
                const unsigned long *seq,
                  unsigned downstream_id, const char *additional_log_text)
diff --combined ssh.c
index 5d6514b5ae7829509c8001f435517d1e44061c6f,1077209ae8e1fee800c0c0258db04da3b2742934..1f8a214b6a07e165872d4e83cfecd8af7517ac21
--- 1/ssh.c
--- 2/ssh.c
+++ b/ssh.c
@@@ -32,7 -32,6 +32,7 @@@ typedef enum 
      SSH2_PKTCTX_NOKEX,
      SSH2_PKTCTX_DHGROUP,
      SSH2_PKTCTX_DHGEX,
 +    SSH2_PKTCTX_ECDHKEX,
      SSH2_PKTCTX_RSAKEX
  } Pkt_KCtx;
  typedef enum {
@@@ -189,7 -188,7 +189,7 @@@ static unsigned int ssh_tty_parse_boole
  #define translate(x) if (type == x) return #x
  #define translatek(x,ctx) if (type == x && (pkt_kctx == ctx)) return #x
  #define translatea(x,ctx) if (type == x && (pkt_actx == ctx)) return #x
 -static char *ssh1_pkt_type(int type)
 +static const char *ssh1_pkt_type(int type)
  {
      translate(SSH1_MSG_DISCONNECT);
      translate(SSH1_SMSG_PUBLIC_KEY);
      translate(SSH1_CMSG_AUTH_CCARD_RESPONSE);
      return "unknown";
  }
 -static char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx, int type)
 +static const char *ssh2_pkt_type(Pkt_KCtx pkt_kctx, Pkt_ACtx pkt_actx,
 +                                 int type)
  {
      translatea(SSH2_MSG_USERAUTH_GSSAPI_RESPONSE,SSH2_PKTCTX_GSSAPI);
      translatea(SSH2_MSG_USERAUTH_GSSAPI_TOKEN,SSH2_PKTCTX_GSSAPI);
      translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
      translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
      translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
 +    translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX);
 +    translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX);
      translate(SSH2_MSG_USERAUTH_REQUEST);
      translate(SSH2_MSG_USERAUTH_FAILURE);
      translate(SSH2_MSG_USERAUTH_SUCCESS);
@@@ -358,12 -354,13 +358,13 @@@ static void ssh2_pkt_addmp(struct Packe
  static int ssh2_pkt_construct(Ssh, struct Packet *);
  static void ssh2_pkt_send(Ssh, struct Packet *);
  static void ssh2_pkt_send_noqueue(Ssh, struct Packet *);
 -static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 +static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
                         struct Packet *pktin);
 -static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 +static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                             struct Packet *pktin);
  static void ssh2_channel_check_close(struct ssh_channel *c);
  static void ssh_channel_destroy(struct ssh_channel *c);
+ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
  
  /*
   * Buffer management constants. There are several of these for
  #define OUR_V2_MAXPKT 0x4000UL
  #define OUR_V2_PACKETLIMIT 0x9000UL
  
 -const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
 +const static struct ssh_signkey *hostkey_algs[] = {
 +    &ssh_ecdsa_ed25519,
 +    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
 +    &ssh_rsa, &ssh_dss
 +};
  
  const static struct ssh_mac *macs[] = {
      &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
@@@ -687,11 -680,11 +688,11 @@@ struct Packet 
      const char *additional_log_text;
  };
  
 -static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh1_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin);
 -static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh2_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin);
 -static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen,
                                            struct Packet *pktin);
  static void ssh1_protocol_setup(Ssh ssh);
  static void ssh2_protocol_setup(Ssh ssh);
@@@ -699,8 -692,7 +700,8 @@@ static void ssh2_bare_connection_protoc
  static void ssh_size(void *handle, int width, int height);
  static void ssh_special(void *handle, Telnet_Special);
  static int ssh2_try_send(struct ssh_channel *c);
 -static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
 +static void ssh2_add_channel_data(struct ssh_channel *c,
 +                                  const char *buf, int len);
  static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
  static void ssh2_set_window(struct ssh_channel *c, int newwin);
  static int ssh_sendbuffer(void *handle);
@@@ -709,7 -701,7 +710,7 @@@ static unsigned long ssh_pkt_getuint32(
  static int ssh2_pkt_getbool(struct Packet *pkt);
  static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
  static void ssh2_timer(void *ctx, unsigned long now);
 -static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 +static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                              struct Packet *pktin);
  static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
  
@@@ -771,7 -763,6 +772,7 @@@ struct ssh_tag 
      const struct ssh2_cipher *cscipher, *sccipher;
      void *cs_cipher_ctx, *sc_cipher_ctx;
      const struct ssh_mac *csmac, *scmac;
 +    int csmac_etm, scmac_etm;
      void *cs_mac_ctx, *sc_mac_ctx;
      const struct ssh_compress *cscomp, *sccomp;
      void *cs_comp_ctx, *sc_comp_ctx;
      /* SSH-1 and SSH-2 use this for different things, but both use it */
      int protocol_initial_phase_done;
  
 -    void (*protocol) (Ssh ssh, void *vin, int inlen,
 +    void (*protocol) (Ssh ssh, const void *vin, int inlen,
                      struct Packet *pkt);
 -    struct Packet *(*s_rdpkt) (Ssh ssh, unsigned char **data, int *datalen);
 +    struct Packet *(*s_rdpkt) (Ssh ssh, const unsigned char **data,
 +                               int *datalen);
      int (*do_ssh_init)(Ssh ssh, unsigned char c);
  
      /*
      unsigned long max_data_size;
      int kex_in_progress;
      unsigned long next_rekey, last_rekey;
 -    char *deferred_rekey_reason;    /* points to STATIC string; don't free */
 +    const char *deferred_rekey_reason;
  
      /*
       * Fully qualified host name, which we need if doing GSSAPI.
@@@ -1297,8 -1287,7 +1298,8 @@@ static void ssh1_log_outgoing_packet(Ss
   * Update the *data and *datalen variables.
   * Return a Packet structure when a packet is completed.
   */
 -static struct Packet *ssh1_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
 +static struct Packet *ssh1_rdpkt(Ssh ssh, const unsigned char **data,
 +                                 int *datalen)
  {
      struct rdpkt1_state_tag *st = &ssh->rdpkt1_state;
  
@@@ -1553,8 -1542,7 +1554,8 @@@ static void ssh2_log_outgoing_packet(Ss
      pkt->length += (pkt->body - pkt->data);
  }
  
 -static struct Packet *ssh2_rdpkt(Ssh ssh, unsigned char **data, int *datalen)
 +static struct Packet *ssh2_rdpkt(Ssh ssh, const unsigned char **data,
 +                                 int *datalen)
  {
      struct rdpkt2_state_tag *st = &ssh->rdpkt2_state;
  
      st->maclen = ssh->scmac ? ssh->scmac->len : 0;
  
      if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_IS_CBC) &&
 -      ssh->scmac) {
 +      ssh->scmac && !ssh->scmac_etm) {
        /*
         * When dealing with a CBC-mode cipher, we want to avoid the
         * possibility of an attacker's tweaking the ciphertext stream
         * length, so we just read data and check the MAC repeatedly,
         * and when the MAC passes, see if the length we've got is
         * plausible.
 +         *
 +         * This defence is unnecessary in OpenSSH ETM mode, because
 +         * the whole point of ETM mode is that the attacker can't
 +         * tweak the ciphertext stream at all without the MAC
 +         * detecting it before we decrypt anything.
         */
  
        /* May as well allocate the whole lot now. */
        st->pktin->data = sresize(st->pktin->data,
                                  st->pktin->maxlen + APIEXTRA,
                                  unsigned char);
 +    } else if (ssh->scmac && ssh->scmac_etm) {
 +      st->pktin->data = snewn(4 + APIEXTRA, unsigned char);
 +
 +        /*
 +         * OpenSSH encrypt-then-MAC mode: the packet length is
 +         * unencrypted, unless the cipher supports length encryption.
 +         */
 +      for (st->i = st->len = 0; st->i < 4; st->i++) {
 +          while ((*datalen) == 0)
 +              crReturn(NULL);
 +          st->pktin->data[st->i] = *(*data)++;
 +          (*datalen)--;
 +      }
 +        /* Cipher supports length decryption, so do it */
 +        if (ssh->sccipher && (ssh->sccipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
 +            /* Keep the packet the same though, so the MAC passes */
 +            unsigned char len[4];
 +            memcpy(len, st->pktin->data, 4);
 +            ssh->sccipher->decrypt_length(ssh->sc_cipher_ctx, len, 4, st->incoming_sequence);
 +            st->len = toint(GET_32BIT(len));
 +        } else {
 +            st->len = toint(GET_32BIT(st->pktin->data));
 +        }
 +
 +      /*
 +       * _Completely_ silly lengths should be stomped on before they
 +       * do us any more damage.
 +       */
 +      if (st->len < 0 || st->len > OUR_V2_PACKETLIMIT ||
 +          st->len % st->cipherblk != 0) {
 +          bombout(("Incoming packet length field was garbled"));
 +          ssh_free_packet(st->pktin);
 +          crStop(NULL);
 +      }
 +
 +      /*
 +       * So now we can work out the total packet length.
 +       */
 +      st->packetlen = st->len + 4;
 +
 +      /*
 +       * Allocate memory for the rest of the packet.
 +       */
 +      st->pktin->maxlen = st->packetlen + st->maclen;
 +      st->pktin->data = sresize(st->pktin->data,
 +                                st->pktin->maxlen + APIEXTRA,
 +                                unsigned char);
 +
 +      /*
 +       * Read the remainder of the packet.
 +       */
 +      for (st->i = 4; st->i < st->packetlen + st->maclen; st->i++) {
 +          while ((*datalen) == 0)
 +              crReturn(NULL);
 +          st->pktin->data[st->i] = *(*data)++;
 +          (*datalen)--;
 +      }
 +
 +      /*
 +       * Check the MAC.
 +       */
 +      if (ssh->scmac
 +          && !ssh->scmac->verify(ssh->sc_mac_ctx, st->pktin->data,
 +                                 st->len + 4, st->incoming_sequence)) {
 +          bombout(("Incorrect MAC received on packet"));
 +          ssh_free_packet(st->pktin);
 +          crStop(NULL);
 +      }
 +
 +      /* Decrypt everything between the length field and the MAC. */
 +      if (ssh->sccipher)
 +          ssh->sccipher->decrypt(ssh->sc_cipher_ctx,
 +                                 st->pktin->data + 4,
 +                                 st->packetlen - 4);
      } else {
        st->pktin->data = snewn(st->cipherblk + APIEXTRA, unsigned char);
  
        }
      }
  
+     /*
+      * RFC 4253 doesn't explicitly say that completely empty packets
+      * with no type byte are forbidden, so treat them as deserving
+      * an SSH_MSG_UNIMPLEMENTED.
+      */
+     if (st->pktin->length <= 5) { /* == 5 we hope, but robustness */
+         ssh2_msg_something_unimplemented(ssh, st->pktin);
+         crStop(NULL);
+     }
      /*
       * pktin->body and pktin->length should identify the semantic
       * content of the packet, excluding the initial type byte.
      crFinish(st->pktin);
  }
  
 -static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh, unsigned char **data,
 +static struct Packet *ssh2_bare_connection_rdpkt(Ssh ssh,
 +                                                 const unsigned char **data,
                                                   int *datalen)
  {
      struct rdpkt2_bare_state_tag *st = &ssh->rdpkt2_bare_state;
@@@ -2065,7 -1982,7 +2075,7 @@@ static void defer_packet(Ssh ssh, int p
      s_wrpkt_defer(ssh, pkt);
  }
  
 -static int ssh_versioncmp(char *a, char *b)
 +static int ssh_versioncmp(const char *a, const char *b)
  {
      char *ae, *be;
      unsigned long av, bv;
@@@ -2142,16 -2059,17 +2152,16 @@@ static void ssh_pkt_addstring_start(str
      ssh_pkt_adduint32(pkt, 0);
      pkt->savedpos = pkt->length;
  }
 -static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
 -{
 -    ssh_pkt_adddata(pkt, data, strlen(data));
 -    PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
 -}
  static void ssh_pkt_addstring_data(struct Packet *pkt, const char *data,
                                     int len)
  {
      ssh_pkt_adddata(pkt, data, len);
      PUT_32BIT(pkt->data + pkt->savedpos - 4, pkt->length - pkt->savedpos);
  }
 +static void ssh_pkt_addstring_str(struct Packet *pkt, const char *data)
 +{
 +  ssh_pkt_addstring_data(pkt, data, strlen(data));
 +}
  static void ssh_pkt_addstring(struct Packet *pkt, const char *data)
  {
      ssh_pkt_addstring_start(pkt);
@@@ -2232,7 -2150,7 +2242,7 @@@ static struct Packet *ssh2_pkt_init(in
   */
  static int ssh2_pkt_construct(Ssh ssh, struct Packet *pkt)
  {
 -    int cipherblk, maclen, padding, i;
 +    int cipherblk, maclen, padding, unencrypted_prefix, i;
  
      if (ssh->logctx)
          ssh2_log_outgoing_packet(ssh, pkt);
      cipherblk = ssh->cscipher ? ssh->cscipher->blksize : 8;  /* block size */
      cipherblk = cipherblk < 8 ? 8 : cipherblk;        /* or 8 if blksize < 8 */
      padding = 4;
 +    unencrypted_prefix = (ssh->csmac && ssh->csmac_etm) ? 4 : 0;
      if (pkt->length + padding < pkt->forcepad)
        padding = pkt->forcepad - pkt->length;
      padding +=
 -      (cipherblk - (pkt->length + padding) % cipherblk) % cipherblk;
 +      (cipherblk - (pkt->length - unencrypted_prefix + padding) % cipherblk)
 +        % cipherblk;
      assert(padding <= 255);
      maclen = ssh->csmac ? ssh->csmac->len : 0;
      ssh2_pkt_ensure(pkt, pkt->length + padding + maclen);
      for (i = 0; i < padding; i++)
        pkt->data[pkt->length + i] = random_byte();
      PUT_32BIT(pkt->data, pkt->length + padding - 4);
 -    if (ssh->csmac)
 -      ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
 -                           pkt->length + padding,
 -                           ssh->v2_outgoing_sequence);
 -    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
  
 -    if (ssh->cscipher)
 -      ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
 -                             pkt->data, pkt->length + padding);
 +    /* Encrypt length if the scheme requires it */
 +    if (ssh->cscipher && (ssh->cscipher->flags & SSH_CIPHER_SEPARATE_LENGTH)) {
 +        ssh->cscipher->encrypt_length(ssh->cs_cipher_ctx, pkt->data, 4,
 +                                      ssh->v2_outgoing_sequence);
 +    }
  
 +    if (ssh->csmac && ssh->csmac_etm) {
 +        /*
 +         * OpenSSH-defined encrypt-then-MAC protocol.
 +         */
 +        if (ssh->cscipher)
 +            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
 +                                   pkt->data + 4, pkt->length + padding - 4);
 +        ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
 +                             pkt->length + padding,
 +                             ssh->v2_outgoing_sequence);
 +    } else {
 +        /*
 +         * SSH-2 standard protocol.
 +         */
 +        if (ssh->csmac)
 +            ssh->csmac->generate(ssh->cs_mac_ctx, pkt->data,
 +                                 pkt->length + padding,
 +                                 ssh->v2_outgoing_sequence);
 +        if (ssh->cscipher)
 +            ssh->cscipher->encrypt(ssh->cs_cipher_ctx,
 +                                   pkt->data, pkt->length + padding);
 +    }
 +
 +    ssh->v2_outgoing_sequence++;       /* whether or not we MACed */
      pkt->encrypted_len = pkt->length + padding;
  
      /* Ready-to-send packet starts at pkt->data. We return length. */
@@@ -2649,7 -2544,7 +2659,7 @@@ static void *ssh_pkt_getdata(struct Pac
      return pkt->body + (pkt->savedpos - length);
  }
  static int ssh1_pkt_getrsakey(struct Packet *pkt, struct RSAKey *key,
 -                            unsigned char **keystr)
 +                            const unsigned char **keystr)
  {
      int j;
  
@@@ -2930,8 -2825,7 +2940,8 @@@ static void ssh_detect_bugs(Ssh ssh, ch
         (wc_match("OpenSSH_2.[235]*", imp)))) {
        /*
         * These versions only support the original (pre-RFC4419)
 -       * SSH-2 GEX request.
 +       * SSH-2 GEX request, and disconnect with a protocol error if
 +       * we use the newer version.
         */
        ssh->remote_bugs |= BUG_SSH2_OLDGEX;
        logevent("We believe remote version has outdated SSH-2 GEX");
@@@ -3265,7 -3159,7 +3275,7 @@@ static int do_ssh_connection_init(Ssh s
  }
  
  static void ssh_process_incoming_data(Ssh ssh,
 -                                    unsigned char **data, int *datalen)
 +                                    const unsigned char **data, int *datalen)
  {
      struct Packet *pktin;
  
  }
  
  static void ssh_queue_incoming_data(Ssh ssh,
 -                                  unsigned char **data, int *datalen)
 +                                  const unsigned char **data, int *datalen)
  {
      bufchain_add(&ssh->queued_incoming_data, *data, *datalen);
      *data += *datalen;
  static void ssh_process_queued_incoming_data(Ssh ssh)
  {
      void *vdata;
 -    unsigned char *data;
 +    const unsigned char *data;
      int len, origlen;
  
      while (!ssh->frozen && bufchain_size(&ssh->queued_incoming_data)) {
@@@ -3310,7 -3204,7 +3320,7 @@@ static void ssh_set_frozen(Ssh ssh, in
      ssh->frozen = frozen;
  }
  
 -static void ssh_gotdata(Ssh ssh, unsigned char *data, int datalen)
 +static void ssh_gotdata(Ssh ssh, const unsigned char *data, int datalen)
  {
      /* Log raw data, if we're in that mode. */
      if (ssh->logctx)
@@@ -3549,20 -3443,35 +3559,20 @@@ static void ssh_sent(Plug plug, int buf
        ssh_throttle_all(ssh, 0, bufsize);
  }
  
 -/*
 - * Connect to specified host and port.
 - * Returns an error message, or NULL on success.
 - * Also places the canonical host name into `realhost'. It must be
 - * freed by the caller.
 - */
 -static const char *connect_to_host(Ssh ssh, char *host, int port,
 -                                 char **realhost, int nodelay, int keepalive)
 +static void ssh_hostport_setup(const char *host, int port, Conf *conf,
 +                               char **savedhost, int *savedport,
 +                               char **loghost_ret)
  {
 -    static const struct plug_function_table fn_table = {
 -      ssh_socket_log,
 -      ssh_closing,
 -      ssh_receive,
 -      ssh_sent,
 -      NULL
 -    };
 +    char *loghost = conf_get_str(conf, CONF_loghost);
 +    if (loghost_ret)
 +        *loghost_ret = loghost;
  
 -    SockAddr addr;
 -    const char *err;
 -    char *loghost;
 -    int addressfamily, sshprot;
 -    
 -    loghost = conf_get_str(ssh->conf, CONF_loghost);
      if (*loghost) {
        char *tmphost;
          char *colon;
  
          tmphost = dupstr(loghost);
 -      ssh->savedport = 22;           /* default ssh port */
 +      *savedport = 22;               /* default ssh port */
  
        /*
         * A colon suffix on the hostname string also lets us affect
        if (colon && colon == host_strchr(tmphost, ':')) {
            *colon++ = '\0';
            if (*colon)
 -              ssh->savedport = atoi(colon);
 +              *savedport = atoi(colon);
        }
  
 -        ssh->savedhost = host_strduptrim(tmphost);
 +        *savedhost = host_strduptrim(tmphost);
          sfree(tmphost);
      } else {
 -      ssh->savedhost = host_strduptrim(host);
 +      *savedhost = host_strduptrim(host);
        if (port < 0)
            port = 22;                 /* default ssh port */
 -      ssh->savedport = port;
 +      *savedport = port;
      }
 +}
 +
 +static int ssh_test_for_upstream(const char *host, int port, Conf *conf)
 +{
 +    char *savedhost;
 +    int savedport;
 +    int ret;
 +
 +    random_ref(); /* platform may need this to determine share socket name */
 +    ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
 +    ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
 +    sfree(savedhost);
 +    random_unref();
 +
 +    return ret;
 +}
 +
 +/*
 + * Connect to specified host and port.
 + * Returns an error message, or NULL on success.
 + * Also places the canonical host name into `realhost'. It must be
 + * freed by the caller.
 + */
 +static const char *connect_to_host(Ssh ssh, const char *host, int port,
 +                                 char **realhost, int nodelay, int keepalive)
 +{
 +    static const struct plug_function_table fn_table = {
 +      ssh_socket_log,
 +      ssh_closing,
 +      ssh_receive,
 +      ssh_sent,
 +      NULL
 +    };
 +
 +    SockAddr addr;
 +    const char *err;
 +    char *loghost;
 +    int addressfamily, sshprot;
 +
 +    ssh_hostport_setup(host, port, ssh->conf,
 +                       &ssh->savedhost, &ssh->savedport, &loghost);
  
      ssh->fn = &fn_table;               /* make 'ssh' usable as a Plug */
  
@@@ -3788,7 -3656,7 +3798,7 @@@ static void ssh_agentf_callback(void *c
  {
      struct ssh_channel *c = (struct ssh_channel *)cv;
      Ssh ssh = c->ssh;
 -    void *sentreply = reply;
 +    const void *sentreply = reply;
  
      c->u.a.outstanding_requests--;
      if (!sentreply) {
   * 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,
 +static void ssh_disconnect(Ssh ssh, const char *client_reason,
 +                           const char *wire_reason,
                           int code, int clean_exit)
  {
      char *error;
@@@ -3906,7 -3773,7 +3916,7 @@@ int verify_ssh_manual_host_key(Ssh ssh
  /*
   * Handle the key exchange and user authentication phases.
   */
 -static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen,
 +static int do_ssh1_login(Ssh ssh, const unsigned char *in, int inlen,
                         struct Packet *pktin)
  {
      int i, j, ret;
      struct do_ssh1_login_state {
        int crLine;
        int len;
 -      unsigned char *rsabuf, *keystr1, *keystr2;
 +      unsigned char *rsabuf;
 +        const unsigned char *keystr1, *keystr2;
        unsigned long supported_ciphers_mask, supported_auths_mask;
        int tried_publickey, tried_agent;
        int tis_auth_refused, ccard_auth_refused;
        void *publickey_blob;
        int publickey_bloblen;
        char *publickey_comment;
 -      int publickey_encrypted;
 +      int privatekey_available, privatekey_encrypted;
        prompts_t *cur_prompt;
        char c;
        int pwpkt_type;
  
      {
        int cipher_chosen = 0, warn = 0;
 -      char *cipher_string = NULL;
 +      const char *cipher_string = NULL;
        int i;
        for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
            int next_cipher = conf_get_int_int(ssh->conf,
      s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
      if (!filename_is_null(s->keyfile)) {
        int keytype;
 -      logeventf(ssh, "Reading private key file \"%.150s\"",
 +      logeventf(ssh, "Reading key file \"%.150s\"",
                  filename_to_str(s->keyfile));
        keytype = key_type(s->keyfile);
 -      if (keytype == SSH_KEYTYPE_SSH1) {
 +      if (keytype == SSH_KEYTYPE_SSH1 ||
 +            keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
            const char *error;
            if (rsakey_pubblob(s->keyfile,
                               &s->publickey_blob, &s->publickey_bloblen,
                               &s->publickey_comment, &error)) {
 -              s->publickey_encrypted = rsakey_encrypted(s->keyfile,
 -                                                        NULL);
 +                s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
 +                if (!s->privatekey_available)
 +                    logeventf(ssh, "Key file contains public key only");
 +              s->privatekey_encrypted = rsakey_encrypted(s->keyfile,
 +                                                           NULL);
            } else {
                char *msgbuf;
 -              logeventf(ssh, "Unable to load private key (%s)", error);
 -              msgbuf = dupprintf("Unable to load private key file "
 +              logeventf(ssh, "Unable to load key (%s)", error);
 +              msgbuf = dupprintf("Unable to load key file "
                                   "\"%.150s\" (%s)\r\n",
                                   filename_to_str(s->keyfile),
                                   error);
            if (s->authed)
                break;
        }
 -      if (s->publickey_blob && !s->tried_publickey) {
 +      if (s->publickey_blob && s->privatekey_available &&
 +            !s->tried_publickey) {
            /*
             * Try public key authentication with the specified
             * key file.
                 */
                char *passphrase = NULL;    /* only written after crReturn */
                const char *error;
 -              if (!s->publickey_encrypted) {
 +              if (!s->privatekey_encrypted) {
                    if (flags & FLAG_VERBOSE)
                        c_write_str(ssh, "No passphrase required.\r\n");
                    passphrase = NULL;
@@@ -5822,7 -5683,7 +5832,7 @@@ int ssh_agent_forwarding_permitted(Ssh 
      return conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists();
  }
  
 -static void do_ssh1_connection(Ssh ssh, unsigned char *in, int inlen,
 +static void do_ssh1_connection(Ssh ssh, const unsigned char *in, int inlen,
                               struct Packet *pktin)
  {
      crBegin(ssh->do_ssh1_connection_crstate);
        ssh_special(ssh, TS_EOF);
  
      if (ssh->ldisc)
 -      ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
 +      ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
      ssh->send_ok = 1;
      ssh->channels = newtree234(ssh_channelcmp);
      while (1) {
@@@ -6077,10 -5938,10 +6087,10 @@@ static void ssh1_protocol_setup(Ssh ssh
      ssh->packet_dispatch[SSH1_MSG_DEBUG] = ssh1_msg_debug;
  }
  
 -static void ssh1_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh1_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin)
  {
 -    unsigned char *in=(unsigned char*)vin;
 +    const unsigned char *in = (const unsigned char *)vin;
      if (ssh->state == SSH_STATE_CLOSED)
        return;
  
  }
  
  /*
 - * Utility routine for decoding comma-separated strings in KEXINIT.
 + * Utility routines for decoding comma-separated strings in KEXINIT.
   */
 -static int in_commasep_string(char *needle, char *haystack, int haylen)
 +static int first_in_commasep_string(char const *needle, char const *haystack,
 +                                  int haylen)
  {
      int needlen;
      if (!needle || !haystack)        /* protect against null pointers */
        return 0;
      needlen = strlen(needle);
 -    while (1) {
 -      /*
 -       * Is it at the start of the string?
 -       */
 -      if (haylen >= needlen &&       /* haystack is long enough */
 -          !memcmp(needle, haystack, needlen) &&       /* initial match */
 -          (haylen == needlen || haystack[needlen] == ',')
 -          /* either , or EOS follows */
 -          )
 -          return 1;
 -      /*
 -       * If not, search for the next comma and resume after that.
 -       * If no comma found, terminate.
 -       */
 -      while (haylen > 0 && *haystack != ',')
 -          haylen--, haystack++;
 -      if (haylen == 0)
 -          return 0;
 -      haylen--, haystack++;          /* skip over comma itself */
 -    }
 +
 +    if (haylen >= needlen &&       /* haystack is long enough */
 +      !memcmp(needle, haystack, needlen) &&   /* initial match */
 +      (haylen == needlen || haystack[needlen] == ',')
 +      /* either , or EOS follows */
 +      )
 +      return 1;
 +    return 0;
  }
  
 -/*
 - * Similar routine for checking whether we have the first string in a list.
 - */
 -static int first_in_commasep_string(char *needle, char *haystack, int haylen)
 +static int in_commasep_string(char const *needle, char const *haystack,
 +                            int haylen)
  {
 -    int needlen;
 +    char *p;
 +
      if (!needle || !haystack)        /* protect against null pointers */
        return 0;
 -    needlen = strlen(needle);
      /*
       * Is it at the start of the string?
       */
 -    if (haylen >= needlen &&       /* haystack is long enough */
 -      !memcmp(needle, haystack, needlen) &&   /* initial match */
 -      (haylen == needlen || haystack[needlen] == ',')
 -      /* either , or EOS follows */
 -      )
 +    if (first_in_commasep_string(needle, haystack, haylen))
        return 1;
 -    return 0;
 +    /*
 +     * If not, search for the next comma and resume after that.
 +     * If no comma found, terminate.
 +     */
 +    p = memchr(haystack, ',', haylen);
 +    if (!p) return 0;
 +    /* + 1 to skip over comma */
 +    return in_commasep_string(needle, p + 1, haylen - (p + 1 - haystack));
 +}
 +
 +/*
 + * Add a value to the comma-separated string at the end of the packet.
 + */
 +static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data)
 +{
 +    if (pkt->length - pkt->savedpos > 0)
 +      ssh_pkt_addstring_str(pkt, ",");
 +    ssh_pkt_addstring_str(pkt, data);
  }
  
  
  /*
 - * SSH-2 key creation method.
 - * (Currently assumes 2 lots of any hash are sufficient to generate
 - * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
 + * SSH-2 key derivation (RFC 4253 section 7.2).
   */
 -#define SSH2_MKKEY_ITERS (2)
 -static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
 -                     unsigned char *keyspace)
 +static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
 +                                 char chr, int keylen)
  {
      const struct ssh_hash *h = ssh->kex->hash;
 -    void *s;
 +    int keylen_padded;
 +    unsigned char *key;
 +    void *s, *s2;
 +
 +    if (keylen == 0)
 +        return NULL;
 +
 +    /* Round up to the next multiple of hash length. */
 +    keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen;
 +
 +    key = snewn(keylen_padded, unsigned char);
 +
      /* First hlen bytes. */
      s = h->init();
      if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
      h->bytes(s, H, h->hlen);
      h->bytes(s, &chr, 1);
      h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
 -    h->final(s, keyspace);
 -    /* Next hlen bytes. */
 -    s = h->init();
 -    if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
 -      hash_mpint(h, s, K);
 -    h->bytes(s, H, h->hlen);
 -    h->bytes(s, keyspace, h->hlen);
 -    h->final(s, keyspace + h->hlen);
 +    h->final(s, key);
 +
 +    /* Subsequent blocks of hlen bytes. */
 +    if (keylen_padded > h->hlen) {
 +        int offset;
 +
 +        s = h->init();
 +        if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
 +            hash_mpint(h, s, K);
 +        h->bytes(s, H, h->hlen);
 +
 +        for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) {
 +            h->bytes(s, key + offset - h->hlen, h->hlen);
 +            s2 = h->copy(s);
 +            h->final(s2, key + offset);
 +        }
 +
 +        h->free(s);
 +    }
 +
 +    /* Now clear any extra bytes of key material beyond the length
 +     * we're officially returning, because the caller won't know to
 +     * smemclr those. */
 +    if (keylen_padded > keylen)
 +        smemclr(key + keylen, keylen_padded - keylen);
 +
 +    return key;
 +}
 +
 +/*
 + * Structure for constructing KEXINIT algorithm lists.
 + */
 +#define MAXKEXLIST 16
 +struct kexinit_algorithm {
 +    const char *name;
 +    union {
 +      struct {
 +          const struct ssh_kex *kex;
 +          int warn;
 +      } kex;
 +      const struct ssh_signkey *hostkey;
 +      struct {
 +          const struct ssh2_cipher *cipher;
 +          int warn;
 +      } cipher;
 +      struct {
 +          const struct ssh_mac *mac;
 +          int etm;
 +      } mac;
 +      const struct ssh_compress *comp;
 +    } u;
 +};
 +
 +/*
 + * Find a slot in a KEXINIT algorithm list to use for a new algorithm.
 + * If the algorithm is already in the list, return a pointer to its
 + * entry, otherwise return an entry from the end of the list.
 + * This assumes that every time a particular name is passed in, it
 + * comes from the same string constant.  If this isn't true, this
 + * function may need to be rewritten to use strcmp() instead.
 + */
 +static struct kexinit_algorithm *ssh2_kexinit_addalg(struct kexinit_algorithm
 +                                                   *list, const char *name)
 +{
 +    int i;
 +
 +    for (i = 0; i < MAXKEXLIST; i++)
 +      if (list[i].name == NULL || list[i].name == name) {
 +          list[i].name = name;
 +          return &list[i];
 +      }
 +    assert(!"No space in KEXINIT list");
 +    return NULL;
  }
  
  /*
   * Handle the SSH-2 transport layer.
   */
 -static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
 +static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
                             struct Packet *pktin)
  {
 -    unsigned char *in = (unsigned char *)vin;
 +    const unsigned char *in = (const unsigned char *)vin;
 +    enum kexlist {
 +      KEXLIST_KEX, KEXLIST_HOSTKEY, KEXLIST_CSCIPHER, KEXLIST_SCCIPHER,
 +      KEXLIST_CSMAC, KEXLIST_SCMAC, KEXLIST_CSCOMP, KEXLIST_SCCOMP,
 +      NKEXLIST
 +    };
 +    const char * kexlist_descr[NKEXLIST] = {
 +      "key exchange algorithm", "host key algorithm",
 +      "client-to-server cipher", "server-to-client cipher",
 +      "client-to-server MAC", "server-to-client MAC",
 +      "client-to-server compression method",
 +      "server-to-client compression method" };
      struct do_ssh2_transport_state {
        int crLine;
        int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
        const struct ssh2_cipher *sccipher_tobe;
        const struct ssh_mac *csmac_tobe;
        const struct ssh_mac *scmac_tobe;
 +        int csmac_etm_tobe, scmac_etm_tobe;
        const struct ssh_compress *cscomp_tobe;
        const struct ssh_compress *sccomp_tobe;
        char *hostkeydata, *sigdata, *rsakeydata, *keystr, *fingerprint;
        int hostkeylen, siglen, rsakeylen;
        void *hkey;                    /* actual host key */
        void *rsakey;                  /* for RSA kex */
 +        void *eckey;                   /* for ECDH kex */
        unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
        int n_preferred_kex;
        const struct ssh_kexes *preferred_kex[KEX_MAX];
          int dlgret;
        int guessok;
        int ignorepkt;
 +      struct kexinit_algorithm kexlists[NKEXLIST][MAXKEXLIST];
      };
      crState(do_ssh2_transport_state);
  
    begin_key_exchange:
      ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
      {
 -      int i, j, k, commalist_started;
 +      int i, j, k, warn;
 +      struct kexinit_algorithm *alg;
  
        /*
         * Set up the preferred key exchange. (NULL => warn below here)
                s->preferred_kex[s->n_preferred_kex++] =
                    &ssh_rsa_kex;
                break;
 +              case KEX_ECDH:
 +                s->preferred_kex[s->n_preferred_kex++] =
 +                    &ssh_ecdh_kex;
 +                break;
              case KEX_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
              case CIPHER_ARCFOUR:
                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_arcfour;
                break;
 +              case CIPHER_CHACHA20:
 +                s->preferred_ciphers[s->n_preferred_ciphers++] = &ssh2_ccp;
 +                break;
              case CIPHER_WARN:
                /* Flag for later. Don't bother if it's the last in
                 * the list. */
         */
        ssh->kex_in_progress = TRUE;
  
 -      /*
 -       * Construct and send our key exchange packet.
 -       */
 -      s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
 -      for (i = 0; i < 16; i++)
 -          ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
 +      for (i = 0; i < NKEXLIST; i++)
 +          for (j = 0; j < MAXKEXLIST; j++)
 +              s->kexlists[i][j].name = NULL;
        /* List key exchange algorithms. */
 -      ssh2_pkt_addstring_start(s->pktout);
 -      commalist_started = 0;
 +      warn = FALSE;
        for (i = 0; i < s->n_preferred_kex; i++) {
            const struct ssh_kexes *k = s->preferred_kex[i];
 -          if (!k) continue;          /* warning flag */
 -          for (j = 0; j < k->nkexes; j++) {
 -              if (commalist_started)
 -                  ssh2_pkt_addstring_str(s->pktout, ",");
 -              ssh2_pkt_addstring_str(s->pktout, k->list[j]->name);
 -              commalist_started = 1;
 +          if (!k) warn = TRUE;
 +          else for (j = 0; j < k->nkexes; j++) {
 +              alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_KEX],
 +                                        k->list[j]->name);
 +              alg->u.kex.kex = k->list[j];
 +              alg->u.kex.warn = warn;
            }
        }
        /* List server host key algorithms. */
          if (!s->got_session_id) {
              /*
               * In the first key exchange, we list all the algorithms
 -             * we're prepared to cope with.
 +             * we're prepared to cope with, but prefer those algorithms
 +           * for which we have a host key for this host.
               */
 -            ssh2_pkt_addstring_start(s->pktout);
              for (i = 0; i < lenof(hostkey_algs); i++) {
 -                ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
 -                if (i < lenof(hostkey_algs) - 1)
 -                    ssh2_pkt_addstring_str(s->pktout, ",");
 -            }
 +              if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
 +                                    hostkey_algs[i]->keytype)) {
 +                  alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 +                                            hostkey_algs[i]->name);
 +                  alg->u.hostkey = hostkey_algs[i];
 +              }
 +          }
 +            for (i = 0; i < lenof(hostkey_algs); i++) {
 +              alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 +                                        hostkey_algs[i]->name);
 +              alg->u.hostkey = hostkey_algs[i];
 +          }
          } else {
              /*
               * In subsequent key exchanges, we list only the kex
               * reverification.
               */
              assert(ssh->kex);
 -            ssh2_pkt_addstring(s->pktout, ssh->hostkey->name);
 +          alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 +                                    ssh->hostkey->name);
 +          alg->u.hostkey = ssh->hostkey;
          }
        /* List encryption algorithms (client->server then server->client). */
 -      for (k = 0; k < 2; k++) {
 -          ssh2_pkt_addstring_start(s->pktout);
 -          commalist_started = 0;
 +      for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
 +          warn = FALSE;
            for (i = 0; i < s->n_preferred_ciphers; i++) {
                const struct ssh2_ciphers *c = s->preferred_ciphers[i];
 -              if (!c) continue;              /* warning flag */
 -              for (j = 0; j < c->nciphers; j++) {
 -                  if (commalist_started)
 -                      ssh2_pkt_addstring_str(s->pktout, ",");
 -                  ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
 -                  commalist_started = 1;
 +              if (!c) warn = TRUE;
 +              else for (j = 0; j < c->nciphers; j++) {
 +                  alg = ssh2_kexinit_addalg(s->kexlists[k],
 +                                            c->list[j]->name);
 +                  alg->u.cipher.cipher = c->list[j];
 +                  alg->u.cipher.warn = warn;
                }
            }
        }
        /* List MAC algorithms (client->server then server->client). */
 -      for (j = 0; j < 2; j++) {
 -          ssh2_pkt_addstring_start(s->pktout);
 +      for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
            for (i = 0; i < s->nmacs; i++) {
 -              ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
 -              if (i < s->nmacs - 1)
 -                  ssh2_pkt_addstring_str(s->pktout, ",");
 -          }
 +              alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
 +              alg->u.mac.mac = s->maclist[i];
 +              alg->u.mac.etm = FALSE;
 +            }
 +          for (i = 0; i < s->nmacs; i++)
 +                /* For each MAC, there may also be an ETM version,
 +                 * which we list second. */
 +                if (s->maclist[i]->etm_name) {
 +                  alg = ssh2_kexinit_addalg(s->kexlists[j],
 +                                            s->maclist[i]->etm_name);
 +                  alg->u.mac.mac = s->maclist[i];
 +                  alg->u.mac.etm = TRUE;
 +              }
        }
        /* List client->server compression algorithms,
         * then server->client compression algorithms. (We use the
         * same set twice.) */
 -      for (j = 0; j < 2; j++) {
 -          ssh2_pkt_addstring_start(s->pktout);
 +      for (j = KEXLIST_CSCOMP; j <= KEXLIST_SCCOMP; j++) {
            assert(lenof(compressions) > 1);
            /* Prefer non-delayed versions */
 -          ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
 +          alg = ssh2_kexinit_addalg(s->kexlists[j], s->preferred_comp->name);
 +          alg->u.comp = s->preferred_comp;
            /* We don't even list delayed versions of algorithms until
             * they're allowed to be used, to avoid a race. See the end of
             * this function. */
            if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
 -              ssh2_pkt_addstring_str(s->pktout, ",");
 -              ssh2_pkt_addstring_str(s->pktout,
 -                                     s->preferred_comp->delayed_name);
 +              alg = ssh2_kexinit_addalg(s->kexlists[j],
 +                                        s->preferred_comp->delayed_name);
 +              alg->u.comp = s->preferred_comp;
            }
            for (i = 0; i < lenof(compressions); i++) {
                const struct ssh_compress *c = compressions[i];
 -              if (c != s->preferred_comp) {
 -                  ssh2_pkt_addstring_str(s->pktout, ",");
 -                  ssh2_pkt_addstring_str(s->pktout, c->name);
 -                  if (s->userauth_succeeded && c->delayed_name) {
 -                      ssh2_pkt_addstring_str(s->pktout, ",");
 -                      ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
 -                  }
 +              alg = ssh2_kexinit_addalg(s->kexlists[j], c->name);
 +              alg->u.comp = c;
 +              if (s->userauth_succeeded && c->delayed_name) {
 +                  alg = ssh2_kexinit_addalg(s->kexlists[j], c->delayed_name);
 +                  alg->u.comp = c;
                }
            }
        }
 +      /*
 +       * Construct and send our key exchange packet.
 +       */
 +      s->pktout = ssh2_pkt_init(SSH2_MSG_KEXINIT);
 +      for (i = 0; i < 16; i++)
 +          ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
 +      for (i = 0; i < NKEXLIST; i++) {
 +          ssh2_pkt_addstring_start(s->pktout);
 +          for (j = 0; j < MAXKEXLIST; j++) {
 +              if (s->kexlists[i][j].name == NULL) break;
 +              ssh2_pkt_addstring_commasep(s->pktout, s->kexlists[i][j].name);
 +          }
 +      }
        /* List client->server languages. Empty list. */
        ssh2_pkt_addstring_start(s->pktout);
        /* List server->client languages. Empty list. */
       * to.
       */
      {
 -      char *str, *preferred;
 +      char *str;
        int i, j, len;
  
        if (pktin->type != SSH2_MSG_KEXINIT) {
        s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
  
        pktin->savedpos += 16;          /* skip garbage cookie */
 -      ssh_pkt_getstring(pktin, &str, &len);    /* key exchange algorithms */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
  
 -      preferred = NULL;
 -      for (i = 0; i < s->n_preferred_kex; i++) {
 -          const struct ssh_kexes *k = s->preferred_kex[i];
 -          if (!k) {
 -              s->warn_kex = TRUE;
 -          } else {
 -              for (j = 0; j < k->nkexes; j++) {
 -                  if (!preferred) preferred = k->list[j]->name;
 -                  if (in_commasep_string(k->list[j]->name, str, len)) {
 -                      ssh->kex = k->list[j];
 -                      break;
 -                  }
 -              }
 -          }
 -          if (ssh->kex)
 -              break;
 -      }
 -      if (!ssh->kex) {
 -            bombout(("Couldn't agree a key exchange algorithm"
 -                     " (available: %.*s)", len, str));
 -          crStopV;
 -      }
 -      /*
 -       * Note that the server's guess is considered wrong if it doesn't match
 -       * the first algorithm in our list, even if it's still the algorithm
 -       * we end up using.
 -       */
 -      s->guessok = first_in_commasep_string(preferred, str, len);
 -      ssh_pkt_getstring(pktin, &str, &len);    /* host key algorithms */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < lenof(hostkey_algs); i++) {
 -          if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
 -              ssh->hostkey = hostkey_algs[i];
 -              break;
 +      s->guessok = FALSE;
 +      for (i = 0; i < NKEXLIST; i++) {
 +          ssh_pkt_getstring(pktin, &str, &len);
 +          if (!str) {
 +              bombout(("KEXINIT packet was incomplete"));
 +              crStopV;
            }
 -      }
 -      if (!ssh->hostkey) {
 -            bombout(("Couldn't agree a host key algorithm"
 -                     " (available: %.*s)", len, str));
 -          crStopV;
 -      }
  
 -      s->guessok = s->guessok &&
 -          first_in_commasep_string(hostkey_algs[0]->name, str, len);
 -      ssh_pkt_getstring(pktin, &str, &len);    /* client->server cipher */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < s->n_preferred_ciphers; i++) {
 -          const struct ssh2_ciphers *c = s->preferred_ciphers[i];
 -          if (!c) {
 -              s->warn_cscipher = TRUE;
 -          } else {
 -              for (j = 0; j < c->nciphers; j++) {
 -                  if (in_commasep_string(c->list[j]->name, str, len)) {
 -                      s->cscipher_tobe = c->list[j];
 -                      break;
 -                  }
 -              }
 -          }
 -          if (s->cscipher_tobe)
 -              break;
 -      }
 -      if (!s->cscipher_tobe) {
 -            bombout(("Couldn't agree a client-to-server cipher"
 -                     " (available: %.*s)", len, str));
 -          crStopV;
 -      }
 +            /* If we've already selected a cipher which requires a
 +             * particular MAC, then just select that, and don't even
 +             * bother looking through the server's KEXINIT string for
 +             * MACs. */
 +            if (i == KEXLIST_CSMAC && s->cscipher_tobe &&
 +                s->cscipher_tobe->required_mac) {
 +                s->csmac_tobe = s->cscipher_tobe->required_mac;
 +                s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
 +                goto matched;
 +            }
 +            if (i == KEXLIST_SCMAC && s->sccipher_tobe &&
 +                s->sccipher_tobe->required_mac) {
 +                s->scmac_tobe = s->sccipher_tobe->required_mac;
 +                s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
 +                goto matched;
 +            }
  
 -      ssh_pkt_getstring(pktin, &str, &len);    /* server->client cipher */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < s->n_preferred_ciphers; i++) {
 -          const struct ssh2_ciphers *c = s->preferred_ciphers[i];
 -          if (!c) {
 -              s->warn_sccipher = TRUE;
 -          } else {
 -              for (j = 0; j < c->nciphers; j++) {
 -                  if (in_commasep_string(c->list[j]->name, str, len)) {
 -                      s->sccipher_tobe = c->list[j];
 -                      break;
 +          for (j = 0; j < MAXKEXLIST; j++) {
 +              struct kexinit_algorithm *alg = &s->kexlists[i][j];
 +              if (alg->name == NULL) break;
 +              if (in_commasep_string(alg->name, str, len)) {
 +                  /* We've found a matching algorithm. */
 +                  if (i == KEXLIST_KEX || i == KEXLIST_HOSTKEY) {
 +                      /* Check if we might need to ignore first kex pkt */
 +                      if (j != 0 ||
 +                          !first_in_commasep_string(alg->name, str, len))
 +                          s->guessok = FALSE;
                    }
 +                  if (i == KEXLIST_KEX) {
 +                      ssh->kex = alg->u.kex.kex;
 +                      s->warn_kex = alg->u.kex.warn;
 +                  } else if (i == KEXLIST_HOSTKEY) {
 +                      ssh->hostkey = alg->u.hostkey;
 +                  } else if (i == KEXLIST_CSCIPHER) {
 +                      s->cscipher_tobe = alg->u.cipher.cipher;
 +                      s->warn_cscipher = alg->u.cipher.warn;
 +                  } else if (i == KEXLIST_SCCIPHER) {
 +                      s->sccipher_tobe = alg->u.cipher.cipher;
 +                      s->warn_sccipher = alg->u.cipher.warn;
 +                  } else if (i == KEXLIST_CSMAC) {
 +                      s->csmac_tobe = alg->u.mac.mac;
 +                      s->csmac_etm_tobe = alg->u.mac.etm;
 +                  } else if (i == KEXLIST_SCMAC) {
 +                      s->scmac_tobe = alg->u.mac.mac;
 +                      s->scmac_etm_tobe = alg->u.mac.etm;
 +                  } else if (i == KEXLIST_CSCOMP) {
 +                      s->cscomp_tobe = alg->u.comp;
 +                  } else if (i == KEXLIST_SCCOMP) {
 +                      s->sccomp_tobe = alg->u.comp;
 +                  }
 +                  goto matched;
                }
 +              if ((i == KEXLIST_CSCOMP || i == KEXLIST_SCCOMP) &&
 +                  in_commasep_string(alg->u.comp->delayed_name, str, len))
 +                  s->pending_compression = TRUE;  /* try this later */
            }
 -          if (s->sccipher_tobe)
 -              break;
 -      }
 -      if (!s->sccipher_tobe) {
 -            bombout(("Couldn't agree a server-to-client cipher"
 -                     " (available: %.*s)", len, str));
 +          bombout(("Couldn't agree a %s ((available: %.*s)",
 +                   kexlist_descr[i], len, str));
            crStopV;
 +        matched:;
        }
  
 -      ssh_pkt_getstring(pktin, &str, &len);    /* client->server mac */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < s->nmacs; i++) {
 -          if (in_commasep_string(s->maclist[i]->name, str, len)) {
 -              s->csmac_tobe = s->maclist[i];
 -              break;
 -          }
 -      }
 -      ssh_pkt_getstring(pktin, &str, &len);    /* server->client mac */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < s->nmacs; i++) {
 -          if (in_commasep_string(s->maclist[i]->name, str, len)) {
 -              s->scmac_tobe = s->maclist[i];
 -              break;
 -          }
 -      }
 -      ssh_pkt_getstring(pktin, &str, &len);  /* client->server compression */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < lenof(compressions) + 1; i++) {
 -          const struct ssh_compress *c =
 -              i == 0 ? s->preferred_comp : compressions[i - 1];
 -          if (in_commasep_string(c->name, str, len)) {
 -              s->cscomp_tobe = c;
 -              break;
 -          } else if (in_commasep_string(c->delayed_name, str, len)) {
 -              if (s->userauth_succeeded) {
 -                  s->cscomp_tobe = c;
 -                  break;
 -              } else {
 -                  s->pending_compression = TRUE;  /* try this later */
 -              }
 -          }
 -      }
 -      ssh_pkt_getstring(pktin, &str, &len);  /* server->client compression */
 -        if (!str) {
 -            bombout(("KEXINIT packet was incomplete"));
 -            crStopV;
 -        }
 -      for (i = 0; i < lenof(compressions) + 1; i++) {
 -          const struct ssh_compress *c =
 -              i == 0 ? s->preferred_comp : compressions[i - 1];
 -          if (in_commasep_string(c->name, str, len)) {
 -              s->sccomp_tobe = c;
 -              break;
 -          } else if (in_commasep_string(c->delayed_name, str, len)) {
 -              if (s->userauth_succeeded) {
 -                  s->sccomp_tobe = c;
 -                  break;
 -              } else {
 -                  s->pending_compression = TRUE;  /* try this later */
 -              }
 -          }
 -      }
        if (s->pending_compression) {
            logevent("Server supports delayed compression; "
                     "will try this later");
          {
              int csbits, scbits;
  
 -            csbits = s->cscipher_tobe->keylen;
 -            scbits = s->sccipher_tobe->keylen;
 +            csbits = s->cscipher_tobe->real_keybits;
 +            scbits = s->sccipher_tobe->real_keybits;
              s->nbits = (csbits > scbits ? csbits : scbits);
          }
          /* The keys only have hlen-bit entropy, since they're based on
           * If we're doing Diffie-Hellman group exchange, start by
           * requesting a group.
           */
 -        if (!ssh->kex->pdata) {
 +        if (dh_is_gex(ssh->kex)) {
              logevent("Doing Diffie-Hellman group exchange");
              ssh->pkt_kctx = SSH2_PKTCTX_DHGEX;
              /*
              bombout(("unable to parse key exchange reply packet"));
              crStopV;
          }
 -        s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
 +        s->hkey = ssh->hostkey->newkey(ssh->hostkey,
 +                                       s->hostkeydata, s->hostkeylen);
          s->f = ssh2_pkt_getmp(pktin);
          if (!s->f) {
              bombout(("unable to parse key exchange reply packet"));
          set_busy_status(ssh->frontend, BUSY_NOT);
  
          hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
 -        if (!ssh->kex->pdata) {
 +        if (dh_is_gex(ssh->kex)) {
              if (!(ssh->remote_bugs & BUG_SSH2_OLDGEX))
                  hash_uint32(ssh->kex->hash, ssh->exhash, DH_MIN_SIZE);
              hash_uint32(ssh->kex->hash, ssh->exhash, s->pbits);
  
          dh_cleanup(ssh->kex_ctx);
          freebn(s->f);
 -        if (!ssh->kex->pdata) {
 +        if (dh_is_gex(ssh->kex)) {
              freebn(s->g);
              freebn(s->p);
          }
 +    } else if (ssh->kex->main_type == KEXTYPE_ECDH) {
 +
 +        logeventf(ssh, "Doing ECDH key exchange with curve %s and hash %s",
 +                  ssh_ecdhkex_curve_textname(ssh->kex),
 +                  ssh->kex->hash->text_name);
 +        ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX;
 +
 +        s->eckey = ssh_ecdhkex_newkey(ssh->kex);
 +        if (!s->eckey) {
 +            bombout(("Unable to generate key for ECDH"));
 +            crStopV;
 +        }
 +
 +        {
 +            char *publicPoint;
 +            int publicPointLength;
 +            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
 +            if (!publicPoint) {
 +                ssh_ecdhkex_freekey(s->eckey);
 +                bombout(("Unable to encode public key for ECDH"));
 +                crStopV;
 +            }
 +            s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT);
 +            ssh2_pkt_addstring_start(s->pktout);
 +            ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength);
 +            sfree(publicPoint);
 +        }
 +
 +        ssh2_pkt_send_noqueue(ssh, s->pktout);
 +
 +        crWaitUntilV(pktin);
 +        if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
 +            ssh_ecdhkex_freekey(s->eckey);
 +            bombout(("expected ECDH reply packet from server"));
 +            crStopV;
 +        }
 +
 +        ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
 +        if (!s->hostkeydata) {
 +            bombout(("unable to parse ECDH reply packet"));
 +            crStopV;
 +        }
 +        hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
 +        s->hkey = ssh->hostkey->newkey(ssh->hostkey,
 +                                       s->hostkeydata, s->hostkeylen);
 +
 +        {
 +            char *publicPoint;
 +            int publicPointLength;
 +            publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
 +            if (!publicPoint) {
 +                ssh_ecdhkex_freekey(s->eckey);
 +                bombout(("Unable to encode public key for ECDH hash"));
 +                crStopV;
 +            }
 +            hash_string(ssh->kex->hash, ssh->exhash,
 +                        publicPoint, publicPointLength);
 +            sfree(publicPoint);
 +        }
 +
 +        {
 +            char *keydata;
 +            int keylen;
 +            ssh_pkt_getstring(pktin, &keydata, &keylen);
 +            if (!keydata) {
 +                bombout(("unable to parse ECDH reply packet"));
 +                crStopV;
 +            }
 +            hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen);
 +            s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen);
 +            if (!s->K) {
 +                ssh_ecdhkex_freekey(s->eckey);
 +                bombout(("point received in ECDH was not valid"));
 +                crStopV;
 +            }
 +        }
 +
 +        ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
 +        if (!s->sigdata) {
 +            bombout(("unable to parse key exchange reply packet"));
 +            crStopV;
 +        }
 +
 +        ssh_ecdhkex_freekey(s->eckey);
      } else {
        logeventf(ssh, "Doing RSA key exchange with hash %s",
                  ssh->kex->hash->text_name);
          }
          hash_string(ssh->kex->hash, ssh->exhash,
                    s->hostkeydata, s->hostkeylen);
 -      s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
 +      s->hkey = ssh->hostkey->newkey(ssh->hostkey,
 +                                       s->hostkeydata, s->hostkeylen);
  
          {
              char *keydata;
           * Authenticate remote host: verify host key. (We've already
           * checked the signature of the exchange hash.)
           */
 -        s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
 +        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
          logevent("Host key fingerprint is:");
          logevent(s->fingerprint);
          /* First check against manually configured host keys. */
      if (ssh->cs_mac_ctx)
        ssh->csmac->free_context(ssh->cs_mac_ctx);
      ssh->csmac = s->csmac_tobe;
 -    ssh->cs_mac_ctx = ssh->csmac->make_context();
 +    ssh->csmac_etm = s->csmac_etm_tobe;
 +    ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
  
      if (ssh->cs_comp_ctx)
        ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
       * hash from the _first_ key exchange.
       */
      {
 -      unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
 -      assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
 -      assert((ssh->cscipher->keylen+7) / 8 <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
 -      assert(ssh->cscipher->blksize <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
 -      assert(ssh->csmac->len <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
 -      smemclr(keyspace, sizeof(keyspace));
 +      unsigned char *key;
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C',
 +                         ssh->cscipher->padded_keybytes);
 +      ssh->cscipher->setkey(ssh->cs_cipher_ctx, key);
 +        smemclr(key, ssh->cscipher->padded_keybytes);
 +        sfree(key);
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A',
 +                         ssh->cscipher->blksize);
 +      ssh->cscipher->setiv(ssh->cs_cipher_ctx, key);
 +        smemclr(key, ssh->cscipher->blksize);
 +        sfree(key);
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E',
 +                         ssh->csmac->keylen);
 +      ssh->csmac->setkey(ssh->cs_mac_ctx, key);
 +        smemclr(key, ssh->csmac->keylen);
 +        sfree(key);
      }
  
      logeventf(ssh, "Initialised %.200s client->server encryption",
              ssh->cscipher->text_name);
 -    logeventf(ssh, "Initialised %.200s client->server MAC algorithm",
 -            ssh->csmac->text_name);
 +    logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
 +            ssh->csmac->text_name,
 +              ssh->csmac_etm ? " (in ETM mode)" : "",
 +              ssh->cscipher->required_mac ? " (required by cipher)" : "");
      if (ssh->cscomp->text_name)
        logeventf(ssh, "Initialised %s compression",
                  ssh->cscomp->text_name);
      if (ssh->sc_mac_ctx)
        ssh->scmac->free_context(ssh->sc_mac_ctx);
      ssh->scmac = s->scmac_tobe;
 -    ssh->sc_mac_ctx = ssh->scmac->make_context();
 +    ssh->scmac_etm = s->scmac_etm_tobe;
 +    ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
  
      if (ssh->sc_comp_ctx)
        ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
       * hash from the _first_ key exchange.
       */
      {
 -      unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
 -      assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
 -      assert((ssh->sccipher->keylen+7) / 8 <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
 -      assert(ssh->sccipher->blksize <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
 -      ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
 -      assert(ssh->scmac->len <=
 -             ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
 -      ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
 -      smemclr(keyspace, sizeof(keyspace));
 +      unsigned char *key;
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D',
 +                         ssh->sccipher->padded_keybytes);
 +      ssh->sccipher->setkey(ssh->sc_cipher_ctx, key);
 +        smemclr(key, ssh->sccipher->padded_keybytes);
 +        sfree(key);
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B',
 +                         ssh->sccipher->blksize);
 +      ssh->sccipher->setiv(ssh->sc_cipher_ctx, key);
 +        smemclr(key, ssh->sccipher->blksize);
 +        sfree(key);
 +
 +      key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F',
 +                         ssh->scmac->keylen);
 +      ssh->scmac->setkey(ssh->sc_mac_ctx, key);
 +        smemclr(key, ssh->scmac->keylen);
 +        sfree(key);
      }
      logeventf(ssh, "Initialised %.200s server->client encryption",
              ssh->sccipher->text_name);
 -    logeventf(ssh, "Initialised %.200s server->client MAC algorithm",
 -            ssh->scmac->text_name);
 +    logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
 +            ssh->scmac->text_name,
 +              ssh->scmac_etm ? " (in ETM mode)" : "",
 +              ssh->sccipher->required_mac ? " (required by cipher)" : "");
      if (ssh->sccomp->text_name)
        logeventf(ssh, "Initialised %s decompression",
                  ssh->sccomp->text_name);
  /*
   * Add data to an SSH-2 channel output buffer.
   */
 -static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
 +static void ssh2_add_channel_data(struct ssh_channel *c, const char *buf,
                                  int len)
  {
      bufchain_add(&c->v.v2.outbuffer, buf, len);
@@@ -7539,8 -7277,7 +7549,8 @@@ static void ssh2_channel_init(struct ss
  /*
   * Construct the common parts of a CHANNEL_OPEN.
   */
 -static struct Packet *ssh2_chanopen_init(struct ssh_channel *c, char *type)
 +static struct Packet *ssh2_chanopen_init(struct ssh_channel *c,
 +                                         const char *type)
  {
      struct Packet *pktout;
  
@@@ -7587,8 -7324,7 +7597,8 @@@ static void ssh2_queue_chanreq_handler(
   * the server initiated channel closure before we saw the response)
   * and the handler should free any storage it's holding.
   */
 -static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
 +static struct Packet *ssh2_chanreq_init(struct ssh_channel *c,
 +                                        const char *type,
                                        cchandler_fn_t handler, void *ctx)
  {
      struct Packet *pktout;
@@@ -8303,7 -8039,7 +8313,7 @@@ static void ssh2_msg_channel_request(Ss
                   !memcmp(type, "exit-signal", 11)) {
  
            int is_plausible = TRUE, is_int = FALSE;
 -          char *fmt_sig = "", *fmt_msg = "";
 +            char *fmt_sig = NULL, *fmt_msg = NULL;
            char *msg;
            int msglen = 0, core = FALSE;
            /* ICK: older versions of OpenSSH (e.g. 3.4p1)
                /* ignore lang tag */
            } /* else don't attempt to parse */
            logeventf(ssh, "Server exited on signal%s%s%s",
 -                    fmt_sig, core ? " (core dumped)" : "",
 -                    fmt_msg);
 -          if (*fmt_sig) sfree(fmt_sig);
 -          if (*fmt_msg) sfree(fmt_msg);
 +                    fmt_sig ? fmt_sig : "",
 +                      core ? " (core dumped)" : "",
 +                    fmt_msg ? fmt_msg : "");
 +          sfree(fmt_sig);
 +            sfree(fmt_msg);
            reply = SSH2_MSG_CHANNEL_SUCCESS;
  
        }
@@@ -8502,7 -8237,7 +8512,7 @@@ static void ssh2_msg_channel_open(Ssh s
      char *peeraddr;
      int peeraddrlen;
      int peerport;
 -    char *error = NULL;
 +    const char *error = NULL;
      struct ssh_channel *c;
      unsigned remid, winsize, pktsize;
      unsigned our_winsize_override = 0;
@@@ -8915,7 -8650,7 +8925,7 @@@ static void ssh2_response_authconn(stru
          do_ssh2_authconn(c->ssh, NULL, 0, pktin);
  }
  
 -static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
 +static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
                             struct Packet *pktin)
  {
      struct do_ssh2_authconn_state {
        int got_username;
        void *publickey_blob;
        int publickey_bloblen;
 -      int publickey_encrypted;
 +      int privatekey_available, privatekey_encrypted;
        char *publickey_algorithm;
        char *publickey_comment;
        unsigned char agent_request[5], *agent_response, *agentp;
        s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
        if (!filename_is_null(s->keyfile)) {
            int keytype;
 -          logeventf(ssh, "Reading private key file \"%.150s\"",
 +          logeventf(ssh, "Reading key file \"%.150s\"",
                      filename_to_str(s->keyfile));
            keytype = key_type(s->keyfile);
 -          if (keytype == SSH_KEYTYPE_SSH2) {
 +          if (keytype == SSH_KEYTYPE_SSH2 ||
 +                keytype == SSH_KEYTYPE_SSH2_PUBLIC_RFC4716 ||
 +                keytype == SSH_KEYTYPE_SSH2_PUBLIC_OPENSSH) {
                const char *error;
                s->publickey_blob =
                    ssh2_userkey_loadpub(s->keyfile,
                                         &s->publickey_bloblen, 
                                         &s->publickey_comment, &error);
                if (s->publickey_blob) {
 -                  s->publickey_encrypted =
 +                  s->privatekey_available = (keytype == SSH_KEYTYPE_SSH2);
 +                    if (!s->privatekey_available)
 +                        logeventf(ssh, "Key file contains public key only");
 +                  s->privatekey_encrypted =
                        ssh2_userkey_encrypted(s->keyfile, NULL);
                } else {
                    char *msgbuf;
 -                  logeventf(ssh, "Unable to load private key (%s)", 
 +                  logeventf(ssh, "Unable to load key (%s)", 
                              error);
 -                  msgbuf = dupprintf("Unable to load private key file "
 +                  msgbuf = dupprintf("Unable to load key file "
                                       "\"%.150s\" (%s)\r\n",
                                       filename_to_str(s->keyfile),
                                       error);
                }
  
            } else if (s->can_pubkey && s->publickey_blob &&
 -                     !s->tried_pubkey_config) {
 +                     s->privatekey_available && !s->tried_pubkey_config) {
  
                struct ssh2_userkey *key;   /* not live over crReturn */
                char *passphrase;           /* not live over crReturn */
                key = NULL;
                while (!key) {
                    const char *error;  /* not live over crReturn */
 -                  if (s->publickey_encrypted) {
 +                  if (s->privatekey_encrypted) {
                        /*
                         * Get a passphrase from the user.
                         */
                    int prompt_len; /* not live over crReturn */
                    
                    {
 -                      char *msg;
 +                      const char *msg;
                        if (changereq_first_time)
                            msg = "Server requested password change";
                        else
  
      /* Clear up various bits and pieces from authentication. */
      if (s->publickey_blob) {
 +      sfree(s->publickey_algorithm);
        sfree(s->publickey_blob);
        sfree(s->publickey_comment);
      }
       * Transfer data!
       */
      if (ssh->ldisc)
 -      ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
 +      ldisc_echoedit_update(ssh->ldisc);  /* cause ldisc to notice changes */
      if (ssh->mainchan)
        ssh->send_ok = 1;
      while (1) {
@@@ -10836,10 -10565,10 +10846,10 @@@ static void ssh2_timer(void *ctx, unsig
      }
  }
  
 -static void ssh2_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh2_protocol(Ssh ssh, const void *vin, int inlen,
                          struct Packet *pktin)
  {
 -    unsigned char *in = (unsigned char *)vin;
 +    const unsigned char *in = (const unsigned char *)vin;
      if (ssh->state == SSH_STATE_CLOSED)
        return;
  
        do_ssh2_authconn(ssh, in, inlen, pktin);
  }
  
 -static void ssh2_bare_connection_protocol(Ssh ssh, void *vin, int inlen,
 +static void ssh2_bare_connection_protocol(Ssh ssh, const void *vin, int inlen,
                                            struct Packet *pktin)
  {
 -    unsigned char *in = (unsigned char *)vin;
 +    const unsigned char *in = (const unsigned char *)vin;
      if (ssh->state == SSH_STATE_CLOSED)
        return;
  
@@@ -10883,8 -10612,7 +10893,8 @@@ static void ssh_cache_conf_values(Ssh s
   * Returns an error message, or NULL on success.
   */
  static const char *ssh_init(void *frontend_handle, void **backend_handle,
 -                          Conf *conf, char *host, int port, char **realhost,
 +                          Conf *conf,
 +                            const char *host, int port, char **realhost,
                            int nodelay, int keepalive)
  {
      const char *p;
@@@ -11136,8 -10864,7 +11146,8 @@@ static void ssh_free(void *handle
  static void ssh_reconfig(void *handle, Conf *conf)
  {
      Ssh ssh = (Ssh) handle;
 -    char *rekeying = NULL, rekey_mandatory = FALSE;
 +    const char *rekeying = NULL;
 +    int rekey_mandatory = FALSE;
      unsigned long old_max_data_size;
      int i, rekey_time;
  
  /*
   * Called to send data down the SSH connection.
   */
 -static int ssh_send(void *handle, char *buf, int len)
 +static int ssh_send(void *handle, const char *buf, int len)
  {
      Ssh ssh = (Ssh) handle;
  
      if (ssh == NULL || ssh->s == NULL || ssh->protocol == NULL)
        return 0;
  
 -    ssh->protocol(ssh, (unsigned char *)buf, len, 0);
 +    ssh->protocol(ssh, (const unsigned char *)buf, len, 0);
  
      return ssh_sendbuffer(ssh);
  }
@@@ -11417,7 -11144,7 +11427,7 @@@ static void ssh_special(void *handle, T
        }
      } else {
        /* Is is a POSIX signal? */
 -      char *signame = NULL;
 +      const char *signame = NULL;
        if (code == TS_SIGABRT) signame = "ABRT";
        if (code == TS_SIGALRM) signame = "ALRM";
        if (code == TS_SIGFPE)  signame = "FPE";
@@@ -11534,8 -11261,7 +11544,8 @@@ static void ssh_unthrottle(void *handle
      ssh_process_queued_incoming_data(ssh);
  }
  
 -void ssh_send_port_open(void *channel, char *hostname, int port, char *org)
 +void ssh_send_port_open(void *channel, const char *hostname, int port,
 +                        const char *org)
  {
      struct ssh_channel *c = (struct ssh_channel *)channel;
      Ssh ssh = c->ssh;
@@@ -11660,7 -11386,6 +11670,7 @@@ Backend ssh_backend = 
      ssh_provide_logctx,
      ssh_unthrottle,
      ssh_cfg_info,
 +    ssh_test_for_upstream,
      "ssh",
      PROT_SSH,
      22
diff --combined sshrsa.c
index 850204c7ad1fb47f221abbd22c4b9da0a61e7ac9,5c1991effe9d265558ae2117cfada116b0142baa..e565a64ac791ff7be104a17f27814f4962f32fc7
+++ b/sshrsa.c
  #include "ssh.h"
  #include "misc.h"
  
 -int makekey(unsigned char *data, int len, struct RSAKey *result,
 -          unsigned char **keystr, int order)
 +int makekey(const unsigned char *data, int len, struct RSAKey *result,
 +          const unsigned char **keystr, int order)
  {
 -    unsigned char *p = data;
 +    const unsigned char *p = data;
      int i, n;
  
      if (len < 4)
@@@ -59,7 -59,7 +59,7 @@@
      return p - data;
  }
  
 -int makeprivate(unsigned char *data, int len, struct RSAKey *result)
 +int makeprivate(const unsigned char *data, int len, struct RSAKey *result)
  {
      return ssh1_read_bignum(data, len, &result->private_exponent);
  }
@@@ -533,8 -533,7 +533,8 @@@ void freersakey(struct RSAKey *key
   * Implementation of the ssh-rsa signing key type. 
   */
  
 -static void getstring(char **data, int *datalen, char **p, int *length)
 +static void getstring(const char **data, int *datalen,
 +                      const char **p, int *length)
  {
      *p = NULL;
      if (*datalen < 4)
      *data += *length;
      *datalen -= *length;
  }
 -static Bignum getmp(char **data, int *datalen)
 +static Bignum getmp(const char **data, int *datalen)
  {
 -    char *p;
 +    const char *p;
      int length;
      Bignum b;
  
  
  static void rsa2_freekey(void *key);   /* forward reference */
  
 -static void *rsa2_newkey(char *data, int len)
 +static void *rsa2_newkey(const struct ssh_signkey *self,
 +                         const char *data, int len)
  {
 -    char *p;
 +    const char *p;
      int slen;
      struct RSAKey *rsa;
  
@@@ -686,14 -684,13 +686,14 @@@ static unsigned char *rsa2_private_blob
      return blob;
  }
  
 -static void *rsa2_createkey(unsigned char *pub_blob, int pub_len,
 -                          unsigned char *priv_blob, int priv_len)
 +static void *rsa2_createkey(const struct ssh_signkey *self,
 +                            const unsigned char *pub_blob, int pub_len,
 +                          const unsigned char *priv_blob, int priv_len)
  {
      struct RSAKey *rsa;
 -    char *pb = (char *) priv_blob;
 +    const char *pb = (const char *) priv_blob;
  
 -    rsa = rsa2_newkey((char *) pub_blob, pub_len);
 +    rsa = rsa2_newkey(self, (char *) pub_blob, pub_len);
      rsa->private_exponent = getmp(&pb, &priv_len);
      rsa->p = getmp(&pb, &priv_len);
      rsa->q = getmp(&pb, &priv_len);
      return rsa;
  }
  
 -static void *rsa2_openssh_createkey(unsigned char **blob, int *len)
 +static void *rsa2_openssh_createkey(const struct ssh_signkey *self,
 +                                    const unsigned char **blob, int *len)
  {
 -    char **b = (char **) blob;
 +    const char **b = (const char **) blob;
      struct RSAKey *rsa;
  
      rsa = snew(struct RSAKey);
@@@ -766,19 -762,55 +766,21 @@@ static int rsa2_openssh_fmtkey(void *ke
      return bloblen;
  }
  
 -static int rsa2_pubkey_bits(void *blob, int len)
 +static int rsa2_pubkey_bits(const struct ssh_signkey *self,
 +                            const void *blob, int len)
  {
      struct RSAKey *rsa;
      int ret;
  
 -    rsa = rsa2_newkey((char *) blob, len);
 +    rsa = rsa2_newkey(self, (const char *) blob, len);
+     if (!rsa)
+       return -1;
      ret = bignum_bitcount(rsa->modulus);
      rsa2_freekey(rsa);
  
      return ret;
  }
  
 -static char *rsa2_fingerprint(void *key)
 -{
 -    struct RSAKey *rsa = (struct RSAKey *) key;
 -    struct MD5Context md5c;
 -    unsigned char digest[16], lenbuf[4];
 -    char buffer[16 * 3 + 40];
 -    char *ret;
 -    int numlen, i;
 -
 -    MD5Init(&md5c);
 -    MD5Update(&md5c, (unsigned char *)"\0\0\0\7ssh-rsa", 11);
 -
 -#define ADD_BIGNUM(bignum) \
 -    numlen = (bignum_bitcount(bignum)+8)/8; \
 -    PUT_32BIT(lenbuf, numlen); MD5Update(&md5c, lenbuf, 4); \
 -    for (i = numlen; i-- ;) { \
 -        unsigned char c = bignum_byte(bignum, i); \
 -        MD5Update(&md5c, &c, 1); \
 -    }
 -    ADD_BIGNUM(rsa->exponent);
 -    ADD_BIGNUM(rsa->modulus);
 -#undef ADD_BIGNUM
 -
 -    MD5Final(digest, &md5c);
 -
 -    sprintf(buffer, "ssh-rsa %d ", bignum_bitcount(rsa->modulus));
 -    for (i = 0; i < 16; i++)
 -      sprintf(buffer + strlen(buffer), "%s%02x", i ? ":" : "",
 -              digest[i]);
 -    ret = snewn(strlen(buffer) + 1, char);
 -    if (ret)
 -      strcpy(ret, buffer);
 -    return ret;
 -}
 -
  /*
   * This is the magic ASN.1/DER prefix that goes in the decoded
   * signature, between the string of FFs and the actual SHA hash
@@@ -810,12 -842,12 +812,12 @@@ static const unsigned char asn1_weird_s
  
  #define ASN1_LEN ( (int) sizeof(asn1_weird_stuff) )
  
 -static int rsa2_verifysig(void *key, char *sig, int siglen,
 -                        char *data, int datalen)
 +static int rsa2_verifysig(void *key, const char *sig, int siglen,
 +                        const char *data, int datalen)
  {
      struct RSAKey *rsa = (struct RSAKey *) key;
      Bignum in, out;
 -    char *p;
 +    const char *p;
      int slen;
      int bytes, i, j, ret;
      unsigned char hash[20];
      return ret;
  }
  
 -static unsigned char *rsa2_sign(void *key, char *data, int datalen,
 +static unsigned char *rsa2_sign(void *key, const char *data, int datalen,
                                int *siglen)
  {
      struct RSAKey *rsa = (struct RSAKey *) key;
@@@ -912,18 -944,17 +914,18 @@@ const struct ssh_signkey ssh_rsa = 
      rsa2_createkey,
      rsa2_openssh_createkey,
      rsa2_openssh_fmtkey,
 +    6 /* n,e,d,iqmp,q,p */,
      rsa2_pubkey_bits,
 -    rsa2_fingerprint,
      rsa2_verifysig,
      rsa2_sign,
      "ssh-rsa",
 -    "rsa2"
 +    "rsa2",
 +    NULL,
  };
  
  void *ssh_rsakex_newkey(char *data, int len)
  {
 -    return rsa2_newkey(data, len);
 +    return rsa2_newkey(&ssh_rsa, data, len);
  }
  
  void ssh_rsakex_freekey(void *key)
@@@ -1058,11 -1089,11 +1060,11 @@@ void ssh_rsakex_encrypt(const struct ss
  }
  
  static const struct ssh_kex ssh_rsa_kex_sha1 = {
 -    "rsa1024-sha1", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha1
 +    "rsa1024-sha1", NULL, KEXTYPE_RSA, &ssh_sha1, NULL,
  };
  
  static const struct ssh_kex ssh_rsa_kex_sha256 = {
 -    "rsa2048-sha256", NULL, KEXTYPE_RSA, NULL, NULL, 0, 0, &ssh_sha256
 +    "rsa2048-sha256", NULL, KEXTYPE_RSA, &ssh_sha256, NULL,
  };
  
  static const struct ssh_kex *const rsa_kex_list[] = {
diff --combined terminal.c
index ca47c8335f79eabaf581bd71b8b55f28b8718ee6,ae85eb58a6a892d2a26c16788707fd6027af70ec..c5fdeeb0245643a3c9584211f7664505e9b23761
@@@ -5,6 -5,7 +5,7 @@@
  #include <stdio.h>
  #include <stdlib.h>
  #include <ctype.h>
+ #include <limits.h>
  
  #include <time.h>
  #include <assert.h>
@@@ -65,7 -66,7 +66,7 @@@
  
  #define has_compat(x) ( ((CL_##x)&term->compatibility_level) != 0 )
  
 -char *EMPTY_WINDOW_TITLE = "";
 +const char *EMPTY_WINDOW_TITLE = "";
  
  const char sco2ansicolour[] = { 0, 4, 2, 6, 1, 5, 3, 7 };
  
@@@ -1350,7 -1351,7 +1351,7 @@@ void term_pwron(Terminal *term, int cle
  {
      power_on(term, clear);
      if (term->ldisc)                 /* cause ldisc to notice changes */
 -      ldisc_send(term->ldisc, NULL, 0, 0);
 +      ldisc_echoedit_update(term->ldisc);
      term->disptop = 0;
      deselect(term);
      term_update(term);
@@@ -2343,7 -2344,7 +2344,7 @@@ static void check_boundary(Terminal *te
      termline *ldata;
  
      /* Validate input coordinates, just in case. */
-     if (x == 0 || x > term->cols)
+     if (x <= 0 || x > term->cols)
        return;
  
      ldata = scrlineptr(y);
@@@ -2574,7 -2575,7 +2575,7 @@@ static void toggle_mode(Terminal *term
          case 10:                     /* DECEDM: set local edit mode */
            term->term_editing = state;
            if (term->ldisc)           /* cause ldisc to notice changes */
 -              ldisc_send(term->ldisc, NULL, 0, 0);
 +              ldisc_echoedit_update(term->ldisc);
            break;
          case 25:                     /* DECTCEM: enable/disable cursor */
            compatibility2(OTHER, VT220);
          case 12:                     /* SRM: set echo mode */
            term->term_echoing = !state;
            if (term->ldisc)           /* cause ldisc to notice changes */
 -              ldisc_send(term->ldisc, NULL, 0, 0);
 +              ldisc_echoedit_update(term->ldisc);
            break;
          case 20:                     /* LNM: Return sends ... */
            term->cr_lf_return = state;
@@@ -3361,7 -3362,7 +3362,7 @@@ static void term_out(Terminal *term
                    compatibility(VT100);
                    power_on(term, TRUE);
                    if (term->ldisc)   /* cause ldisc to notice changes */
 -                      ldisc_send(term->ldisc, NULL, 0, 0);
 +                      ldisc_echoedit_update(term->ldisc);
                    if (term->reset_132) {
                        if (!term->no_remote_resize)
                            request_resize(term->frontend, 80, term->rows);
                    if (term->esc_nargs <= ARGS_MAX) {
                        if (term->esc_args[term->esc_nargs - 1] == ARG_DEFAULT)
                            term->esc_args[term->esc_nargs - 1] = 0;
-                       term->esc_args[term->esc_nargs - 1] =
-                           10 * term->esc_args[term->esc_nargs - 1] + c - '0';
+                       if (term->esc_args[term->esc_nargs - 1] <=
+                           UINT_MAX / 10 &&
+                           term->esc_args[term->esc_nargs - 1] * 10 <=
+                           UINT_MAX - c - '0')
+                           term->esc_args[term->esc_nargs - 1] =
+                               10 * term->esc_args[term->esc_nargs - 1] +
+                               c - '0';
+                       else
+                           term->esc_args[term->esc_nargs - 1] = UINT_MAX;
                    }
                    term->termstate = SEEN_CSI;
                } else if (c == ';') {
                        term->esc_query = c;
                    term->termstate = SEEN_CSI;
                } else
+ #define CLAMP(arg, lim) ((arg) = ((arg) > (lim)) ? (lim) : (arg))
                    switch (ANSI(c, term->esc_query)) {
                      case 'A':       /* CUU: move up N lines */
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             term->curs.y - def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
                        compatibility(ANSI);
                        /* FALLTHROUGH */
                      case 'B':         /* CUD: Cursor down */
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             term->curs.y + def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
                        compatibility(ANSI);
                        /* FALLTHROUGH */
                      case 'C':         /* CUF: Cursor right */ 
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, term->curs.x + def(term->esc_args[0], 1),
                             term->curs.y, 1);
                        seen_disp_event(term);
                        break;
                      case 'D':       /* CUB: move left N cols */
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, term->curs.x - def(term->esc_args[0], 1),
                             term->curs.y, 1);
                        seen_disp_event(term);
                        break;
                      case 'E':       /* CNL: move down N lines and CR */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, 0,
                             term->curs.y + def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
                        break;
                      case 'F':       /* CPL: move up N lines and CR */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, 0,
                             term->curs.y - def(term->esc_args[0], 1), 1);
                        seen_disp_event(term);
                      case 'G':       /* CHA */
                      case '`':       /* HPA: set horizontal posn */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->cols);
                        move(term, def(term->esc_args[0], 1) - 1,
                             term->curs.y, 0);
                        seen_disp_event(term);
                        break;
                      case 'd':       /* VPA: set vertical posn */
                        compatibility(ANSI);
+                       CLAMP(term->esc_args[0], term->rows);
                        move(term, term->curs.x,
                             ((term->dec_om ? term->marg_t : 0) +
                              def(term->esc_args[0], 1) - 1),
                      case 'f':      /* HVP: set horz and vert posns at once */
                        if (term->esc_nargs < 2)
                            term->esc_args[1] = ARG_DEFAULT;
+                       CLAMP(term->esc_args[0], term->rows);
+                       CLAMP(term->esc_args[1], term->cols);
                        move(term, def(term->esc_args[1], 1) - 1,
                             ((term->dec_om ? term->marg_t : 0) +
                              def(term->esc_args[0], 1) - 1),
                        break;
                      case 'L':       /* IL: insert lines */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->rows);
                        if (term->curs.y <= term->marg_b)
                            scroll(term, term->curs.y, term->marg_b,
                                   -def(term->esc_args[0], 1), FALSE);
                        break;
                      case 'M':       /* DL: delete lines */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->rows);
                        if (term->curs.y <= term->marg_b)
                            scroll(term, term->curs.y, term->marg_b,
                                   def(term->esc_args[0], 1),
                      case '@':       /* ICH: insert chars */
                        /* XXX VTTEST says this is vt220, vt510 manual says vt102 */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->cols);
                        insch(term, def(term->esc_args[0], 1));
                        seen_disp_event(term);
                        break;
                      case 'P':       /* DCH: delete chars */
                        compatibility(VT102);
+                       CLAMP(term->esc_args[0], term->cols);
                        insch(term, -def(term->esc_args[0], 1));
                        seen_disp_event(term);
                        break;
                        compatibility(VT100);
                        if (term->esc_nargs <= 2) {
                            int top, bot;
+                           CLAMP(term->esc_args[0], term->rows);
+                           CLAMP(term->esc_args[1], term->rows);
                            top = def(term->esc_args[0], 1) - 1;
                            bot = (term->esc_nargs <= 1
                                   || term->esc_args[1] == 0 ?
  
                            switch (term->esc_args[0]) {
                                int x, y, len;
 -                              char buf[80], *p;
 +                              char buf[80];
 +                                const char *p;
                              case 1:
                                set_iconic(term->frontend, FALSE);
                                break;
                        }
                        break;
                      case 'S':         /* SU: Scroll up */
+                       CLAMP(term->esc_args[0], term->rows);
                        compatibility(SCOANSI);
                        scroll(term, term->marg_t, term->marg_b,
                               def(term->esc_args[0], 1), TRUE);
                        seen_disp_event(term);
                        break;
                      case 'T':         /* SD: Scroll down */
+                       CLAMP(term->esc_args[0], term->rows);
                        compatibility(SCOANSI);
                        scroll(term, term->marg_t, term->marg_b,
                               -def(term->esc_args[0], 1), TRUE);
                        /* XXX VTTEST says this is vt220, vt510 manual
                         * says vt100 */
                        compatibility(ANSIMIN);
+                       CLAMP(term->esc_args[0], term->cols);
                        {
                            int n = def(term->esc_args[0], 1);
                            pos cursplus;
                        break;
                      case 'Z':         /* CBT */
                        compatibility(OTHER);
+                       CLAMP(term->esc_args[0], term->cols);
                        {
                            int i = def(term->esc_args[0], 1);
                            pos old_curs = term->curs;
                        break;
                      case ANSI('F', '='):      /* set normal foreground */
                        compatibility(SCOANSI);
-                       if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+                       if (term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
                                 (term->esc_args[0] & 0x8)) <<
                        break;
                      case ANSI('G', '='):      /* set normal background */
                        compatibility(SCOANSI);
-                       if (term->esc_args[0] >= 0 && term->esc_args[0] < 16) {
+                       if (term->esc_args[0] < 16) {
                            long colour =
                                (sco2ansicolour[term->esc_args[0] & 0x7] |
                                 (term->esc_args[0] & 0x8)) <<
                  case '7':
                  case '8':
                  case '9':
-                   term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   if (term->esc_args[0] <= UINT_MAX / 10 &&
+                       term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+                       term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   else
+                       term->esc_args[0] = UINT_MAX;
                    break;
                  case 'L':
                    /*
                  case '7':
                  case '8':
                  case '9':
-                   term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   if (term->esc_args[0] <= UINT_MAX / 10 &&
+                       term->esc_args[0] * 10 <= UINT_MAX - c - '0')
+                       term->esc_args[0] = 10 * term->esc_args[0] + c - '0';
+                   else
+                       term->esc_args[0] = UINT_MAX;
                    break;
                  default:
                    term->termstate = OSC_STRING;
@@@ -6043,8 -6079,7 +6080,8 @@@ void term_mouse(Terminal *term, Mouse_B
            } else if (c <= 223 && r <= 223) {
                len = sprintf(abuf, "\033[M%c%c%c", encstate + 32, c + 32, r + 32);
            }
 -          ldisc_send(term->ldisc, abuf, len, 0);
 +            if (len > 0)
 +                ldisc_send(term->ldisc, abuf, len, 0);
        }
        return;
      }
        sel_spread(term);
      } else if ((bcooked == MBT_SELECT && a == MA_DRAG) ||
               (bcooked == MBT_EXTEND && a != MA_RELEASE)) {
 +        if (a == MA_DRAG &&
 +            (term->selstate == NO_SELECTION || term->selstate == SELECTED)) {
 +            /*
 +             * This can happen if a front end has passed us a MA_DRAG
 +             * without a prior MA_CLICK. OS X GTK does so, for
 +             * example, if the initial button press was eaten by the
 +             * WM when it activated the window in the first place. The
 +             * nicest thing to do in this situation is to ignore
 +             * further drags, and wait for the user to click in the
 +             * window again properly if they want to select.
 +             */
 +            return;
 +        }
        if (term->selstate == ABOUT_TO && poseq(term->selanchor, selpoint))
            return;
        if (bcooked == MBT_EXTEND && a != MA_DRAG &&
@@@ -6334,7 -6356,7 +6371,7 @@@ void term_set_focus(Terminal *term, in
   */
  char *term_get_ttymode(Terminal *term, const char *mode)
  {
 -    char *val = NULL;
 +    const char *val = NULL;
      if (strcmp(mode, "ERASE") == 0) {
        val = term->bksp_is_delete ? "^?" : "^H";
      }
@@@ -6354,7 -6376,7 +6391,7 @@@ struct term_userpass_state 
   * input.
   */
  int term_get_userpass_input(Terminal *term, prompts_t *p,
 -                          unsigned char *in, int inlen)
 +                          const unsigned char *in, int inlen)
  {
      struct term_userpass_state *s = (struct term_userpass_state *)p->data;
      if (!s) {