|  | @@ -11,6 +11,7 @@
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #include "putty.h"
 | 
	
		
			
				|  |  |  #include "tree234.h"
 | 
	
		
			
				|  |  | +#include "storage.h"
 | 
	
		
			
				|  |  |  #include "ssh.h"
 | 
	
		
			
				|  |  |  #ifndef NO_GSSAPI
 | 
	
		
			
				|  |  |  #include "sshgssc.h"
 | 
	
	
		
			
				|  | @@ -407,16 +408,23 @@ static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
 | 
	
		
			
				|  |  |  #define OUR_V2_MAXPKT 0x4000UL
 | 
	
		
			
				|  |  |  #define OUR_V2_PACKETLIMIT 0x9000UL
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const static struct ssh_signkey *hostkey_algs[] = {
 | 
	
		
			
				|  |  | -    &ssh_ecdsa_ed25519,
 | 
	
		
			
				|  |  | -    &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
 | 
	
		
			
				|  |  | -    &ssh_rsa, &ssh_dss
 | 
	
		
			
				|  |  | +struct ssh_signkey_with_user_pref_id {
 | 
	
		
			
				|  |  | +    const struct ssh_signkey *alg;
 | 
	
		
			
				|  |  | +    int id;
 | 
	
		
			
				|  |  | +};
 | 
	
		
			
				|  |  | +const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
 | 
	
		
			
				|  |  | +    { &ssh_ecdsa_ed25519, HK_ED25519 },
 | 
	
		
			
				|  |  | +    { &ssh_ecdsa_nistp256, HK_ECDSA },
 | 
	
		
			
				|  |  | +    { &ssh_ecdsa_nistp384, HK_ECDSA },
 | 
	
		
			
				|  |  | +    { &ssh_ecdsa_nistp521, HK_ECDSA },
 | 
	
		
			
				|  |  | +    { &ssh_dss, HK_DSA },
 | 
	
		
			
				|  |  | +    { &ssh_rsa, HK_RSA },
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -const static struct ssh_mac *macs[] = {
 | 
	
		
			
				|  |  | +const static struct ssh_mac *const macs[] = {
 | 
	
		
			
				|  |  |      &ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  | -const static struct ssh_mac *buggymacs[] = {
 | 
	
		
			
				|  |  | +const static struct ssh_mac *const buggymacs[] = {
 | 
	
		
			
				|  |  |      &ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -443,7 +451,7 @@ const static struct ssh_compress ssh_comp_none = {
 | 
	
		
			
				|  |  |      ssh_comp_none_disable, NULL
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  extern const struct ssh_compress ssh_zlib;
 | 
	
		
			
				|  |  | -const static struct ssh_compress *compressions[] = {
 | 
	
		
			
				|  |  | +const static struct ssh_compress *const compressions[] = {
 | 
	
		
			
				|  |  |      &ssh_zlib, &ssh_comp_none
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -954,6 +962,24 @@ struct ssh_tag {
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      struct ssh_gss_liblist *gsslibs;
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * The last list returned from get_specials.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    struct telnet_special *specials;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * List of host key algorithms for which we _don't_ have a stored
 | 
	
		
			
				|  |  | +     * host key. These are indices into the main hostkey_algs[] array
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    int uncert_hostkeys[lenof(hostkey_algs)];
 | 
	
		
			
				|  |  | +    int n_uncert_hostkeys;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * Flag indicating that the current rekey is intended to finish
 | 
	
		
			
				|  |  | +     * with a newly cross-certified host key.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    int cross_certifying;
 | 
	
		
			
				|  |  |  };
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  #define logevent(s) logevent(ssh->frontend, s)
 | 
	
	
		
			
				|  | @@ -6240,7 +6266,10 @@ struct kexinit_algorithm {
 | 
	
		
			
				|  |  |  	    const struct ssh_kex *kex;
 | 
	
		
			
				|  |  |  	    int warn;
 | 
	
		
			
				|  |  |  	} kex;
 | 
	
		
			
				|  |  | -	const struct ssh_signkey *hostkey;
 | 
	
		
			
				|  |  | +	struct {
 | 
	
		
			
				|  |  | +            const struct ssh_signkey *hostkey;
 | 
	
		
			
				|  |  | +            int warn;
 | 
	
		
			
				|  |  | +        } hk;
 | 
	
		
			
				|  |  |  	struct {
 | 
	
		
			
				|  |  |  	    const struct ssh2_cipher *cipher;
 | 
	
		
			
				|  |  |  	    int warn;
 | 
	
	
		
			
				|  | @@ -6295,12 +6324,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  	"server-to-client compression method" };
 | 
	
		
			
				|  |  |      struct do_ssh2_transport_state {
 | 
	
		
			
				|  |  |  	int crLine;
 | 
	
		
			
				|  |  | -	int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
 | 
	
		
			
				|  |  | +	int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
 | 
	
		
			
				|  |  |  	Bignum p, g, e, f, K;
 | 
	
		
			
				|  |  |  	void *our_kexinit;
 | 
	
		
			
				|  |  |  	int our_kexinitlen;
 | 
	
		
			
				|  |  |  	int kex_init_value, kex_reply_value;
 | 
	
		
			
				|  |  | -	const struct ssh_mac **maclist;
 | 
	
		
			
				|  |  | +	const struct ssh_mac *const *maclist;
 | 
	
		
			
				|  |  |  	int nmacs;
 | 
	
		
			
				|  |  |  	const struct ssh2_cipher *cscipher_tobe;
 | 
	
		
			
				|  |  |  	const struct ssh2_cipher *sccipher_tobe;
 | 
	
	
		
			
				|  | @@ -6317,6 +6346,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  	unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
 | 
	
		
			
				|  |  |  	int n_preferred_kex;
 | 
	
		
			
				|  |  |  	const struct ssh_kexes *preferred_kex[KEX_MAX];
 | 
	
		
			
				|  |  | +	int n_preferred_hk;
 | 
	
		
			
				|  |  | +	int preferred_hk[HK_MAX];
 | 
	
		
			
				|  |  |  	int n_preferred_ciphers;
 | 
	
		
			
				|  |  |  	const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
 | 
	
		
			
				|  |  |  	const struct ssh_compress *preferred_comp;
 | 
	
	
		
			
				|  | @@ -6393,6 +6424,20 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  	    }
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	/*
 | 
	
		
			
				|  |  | +	 * Set up the preferred host key types. These are just the ids
 | 
	
		
			
				|  |  | +	 * in the enum in putty.h, so 'warn below here' is indicated
 | 
	
		
			
				|  |  | +	 * by HK_WARN.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	s->n_preferred_hk = 0;
 | 
	
		
			
				|  |  | +	for (i = 0; i < HK_MAX; i++) {
 | 
	
		
			
				|  |  | +            int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
 | 
	
		
			
				|  |  | +            /* As above, don't bother with HK_WARN if it's last in the
 | 
	
		
			
				|  |  | +             * list */
 | 
	
		
			
				|  |  | +	    if (id != HK_WARN || i < HK_MAX - 1)
 | 
	
		
			
				|  |  | +                s->preferred_hk[s->n_preferred_hk++] = id;
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	/*
 | 
	
		
			
				|  |  |  	 * Set up the preferred ciphers. (NULL => warn below here)
 | 
	
		
			
				|  |  |  	 */
 | 
	
	
		
			
				|  | @@ -6469,24 +6514,47 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |               * In the first key exchange, we list all the algorithms
 | 
	
		
			
				|  |  |               * we're prepared to cope with, but prefer those algorithms
 | 
	
		
			
				|  |  |  	     * for which we have a host key for this host.
 | 
	
		
			
				|  |  | +             *
 | 
	
		
			
				|  |  | +             * If the host key algorithm is below the warning
 | 
	
		
			
				|  |  | +             * threshold, we warn even if we did already have a key
 | 
	
		
			
				|  |  | +             * for it, on the basis that if the user has just
 | 
	
		
			
				|  |  | +             * reconfigured that host key type to be warned about,
 | 
	
		
			
				|  |  | +             * they surely _do_ want to be alerted that a server
 | 
	
		
			
				|  |  | +             * they're actually connecting to is using it.
 | 
	
		
			
				|  |  |               */
 | 
	
		
			
				|  |  | -            for (i = 0; i < lenof(hostkey_algs); i++) {
 | 
	
		
			
				|  |  | -		if (have_ssh_host_key(
 | 
	
		
			
				|  |  | +            warn = FALSE;
 | 
	
		
			
				|  |  | +            for (i = 0; i < s->n_preferred_hk; i++) {
 | 
	
		
			
				|  |  | +                if (s->preferred_hk[i] == HK_WARN)
 | 
	
		
			
				|  |  | +                    warn = TRUE;
 | 
	
		
			
				|  |  | +                for (j = 0; j < lenof(hostkey_algs); j++) {
 | 
	
		
			
				|  |  | +                    if (hostkey_algs[j].id != s->preferred_hk[i])
 | 
	
		
			
				|  |  | +                        continue;
 | 
	
		
			
				|  |  | +                    if (have_ssh_host_key(
 | 
	
		
			
				|  |  |  #ifdef MPEXT
 | 
	
		
			
				|  |  | -				      ssh->frontend,
 | 
	
		
			
				|  |  | +                                          ssh->frontend,
 | 
	
		
			
				|  |  |  #endif
 | 
	
		
			
				|  |  | -				      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];
 | 
	
		
			
				|  |  | +                                          ssh->savedhost, ssh->savedport,
 | 
	
		
			
				|  |  | +                                          hostkey_algs[j].alg->keytype)) {
 | 
	
		
			
				|  |  | +                        alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 | 
	
		
			
				|  |  | +                                                  hostkey_algs[j].alg->name);
 | 
	
		
			
				|  |  | +                        alg->u.hk.hostkey = hostkey_algs[j].alg;
 | 
	
		
			
				|  |  | +                        alg->u.hk.warn = warn;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  |  	    }
 | 
	
		
			
				|  |  | +            warn = FALSE;
 | 
	
		
			
				|  |  | +            for (i = 0; i < s->n_preferred_hk; i++) {
 | 
	
		
			
				|  |  | +                if (s->preferred_hk[i] == HK_WARN)
 | 
	
		
			
				|  |  | +                    warn = TRUE;
 | 
	
		
			
				|  |  | +                for (j = 0; j < lenof(hostkey_algs); j++) {
 | 
	
		
			
				|  |  | +                    if (hostkey_algs[j].id != s->preferred_hk[i])
 | 
	
		
			
				|  |  | +                        continue;
 | 
	
		
			
				|  |  | +                    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 | 
	
		
			
				|  |  | +                                              hostkey_algs[j].alg->name);
 | 
	
		
			
				|  |  | +                    alg->u.hk.hostkey = hostkey_algs[j].alg;
 | 
	
		
			
				|  |  | +                    alg->u.hk.warn = warn;
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |          } else {
 | 
	
		
			
				|  |  |              /*
 | 
	
		
			
				|  |  |               * In subsequent key exchanges, we list only the kex
 | 
	
	
		
			
				|  | @@ -6498,7 +6566,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |              assert(ssh->kex);
 | 
	
		
			
				|  |  |  	    alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
 | 
	
		
			
				|  |  |  				      ssh->hostkey->name);
 | 
	
		
			
				|  |  | -	    alg->u.hostkey = ssh->hostkey;
 | 
	
		
			
				|  |  | +	    alg->u.hk.hostkey = ssh->hostkey;
 | 
	
		
			
				|  |  | +            alg->u.hk.warn = FALSE;
 | 
	
		
			
				|  |  |          }
 | 
	
		
			
				|  |  |  	/* List encryption algorithms (client->server then server->client). */
 | 
	
		
			
				|  |  |  	for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
 | 
	
	
		
			
				|  | @@ -6619,7 +6688,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  	s->scmac_tobe = NULL;
 | 
	
		
			
				|  |  |  	s->cscomp_tobe = NULL;
 | 
	
		
			
				|  |  |  	s->sccomp_tobe = NULL;
 | 
	
		
			
				|  |  | -	s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
 | 
	
		
			
				|  |  | +	s->warn_kex = s->warn_hk = FALSE;
 | 
	
		
			
				|  |  | +        s->warn_cscipher = s->warn_sccipher = FALSE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	pktin->savedpos += 16;	        /* skip garbage cookie */
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -6663,7 +6733,8 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  			ssh->kex = alg->u.kex.kex;
 | 
	
		
			
				|  |  |  			s->warn_kex = alg->u.kex.warn;
 | 
	
		
			
				|  |  |  		    } else if (i == KEXLIST_HOSTKEY) {
 | 
	
		
			
				|  |  | -			ssh->hostkey = alg->u.hostkey;
 | 
	
		
			
				|  |  | +			ssh->hostkey = alg->u.hk.hostkey;
 | 
	
		
			
				|  |  | +                        s->warn_hk = alg->u.hk.warn;
 | 
	
		
			
				|  |  |  		    } else if (i == KEXLIST_CSCIPHER) {
 | 
	
		
			
				|  |  |  			s->cscipher_tobe = alg->u.cipher.cipher;
 | 
	
		
			
				|  |  |  			s->warn_cscipher = alg->u.cipher.warn;
 | 
	
	
		
			
				|  | @@ -6687,10 +6758,41 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  		    in_commasep_string(alg->u.comp->delayed_name, str, len))
 | 
	
		
			
				|  |  |  		    s->pending_compression = TRUE;  /* try this later */
 | 
	
		
			
				|  |  |  	    }
 | 
	
		
			
				|  |  | -	    bombout(("Couldn't agree a %s ((available: %.*s)",
 | 
	
		
			
				|  |  | +	    bombout(("Couldn't agree a %s (available: %.*s)",
 | 
	
		
			
				|  |  |  		     kexlist_descr[i], len, str));
 | 
	
		
			
				|  |  |  	    crStopV;
 | 
	
		
			
				|  |  |  	  matched:;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            if (i == KEXLIST_HOSTKEY) {
 | 
	
		
			
				|  |  | +                int j;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                /*
 | 
	
		
			
				|  |  | +                 * In addition to deciding which host key we're
 | 
	
		
			
				|  |  | +                 * actually going to use, we should make a list of the
 | 
	
		
			
				|  |  | +                 * host keys offered by the server which we _don't_
 | 
	
		
			
				|  |  | +                 * have cached. These will be offered as cross-
 | 
	
		
			
				|  |  | +                 * certification options by ssh_get_specials.
 | 
	
		
			
				|  |  | +                 *
 | 
	
		
			
				|  |  | +                 * We also count the key we're currently using for KEX
 | 
	
		
			
				|  |  | +                 * as one we've already got, because by the time this
 | 
	
		
			
				|  |  | +                 * menu becomes visible, it will be.
 | 
	
		
			
				|  |  | +                 */
 | 
	
		
			
				|  |  | +                ssh->n_uncert_hostkeys = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                for (j = 0; j < lenof(hostkey_algs); j++) {
 | 
	
		
			
				|  |  | +                    if (hostkey_algs[j].alg != ssh->hostkey &&
 | 
	
		
			
				|  |  | +                        in_commasep_string(hostkey_algs[j].alg->name,
 | 
	
		
			
				|  |  | +                                           str, len) &&
 | 
	
		
			
				|  |  | +                        !have_ssh_host_key(
 | 
	
		
			
				|  |  | +#ifdef MPEXT
 | 
	
		
			
				|  |  | +                                           ssh->frontend,
 | 
	
		
			
				|  |  | +#endif
 | 
	
		
			
				|  |  | +                                           ssh->savedhost, ssh->savedport,
 | 
	
		
			
				|  |  | +                                           hostkey_algs[j].alg->keytype)) {
 | 
	
		
			
				|  |  | +                        ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  	if (s->pending_compression) {
 | 
	
	
		
			
				|  | @@ -6735,6 +6837,73 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  	    }
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +	if (s->warn_hk) {
 | 
	
		
			
				|  |  | +            int j, k;
 | 
	
		
			
				|  |  | +            char *betteralgs;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +	    ssh_set_frozen(ssh, 1);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            /*
 | 
	
		
			
				|  |  | +             * Change warning box wording depending on why we chose a
 | 
	
		
			
				|  |  | +             * warning-level host key algorithm. If it's because
 | 
	
		
			
				|  |  | +             * that's all we have *cached*, use the askhk mechanism,
 | 
	
		
			
				|  |  | +             * and list the host keys we could usefully cross-certify.
 | 
	
		
			
				|  |  | +             * Otherwise, use askalg for the standard wording.
 | 
	
		
			
				|  |  | +             */
 | 
	
		
			
				|  |  | +            betteralgs = NULL;
 | 
	
		
			
				|  |  | +            for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
 | 
	
		
			
				|  |  | +                const struct ssh_signkey_with_user_pref_id *hktype =
 | 
	
		
			
				|  |  | +                    &hostkey_algs[ssh->uncert_hostkeys[j]];
 | 
	
		
			
				|  |  | +                int better = FALSE;
 | 
	
		
			
				|  |  | +                for (k = 0; k < HK_MAX; k++) {
 | 
	
		
			
				|  |  | +                    int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
 | 
	
		
			
				|  |  | +                    if (id == HK_WARN) {
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +                    } else if (id == hktype->id) {
 | 
	
		
			
				|  |  | +                        better = TRUE;
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +                if (better) {
 | 
	
		
			
				|  |  | +                    if (betteralgs) {
 | 
	
		
			
				|  |  | +                        char *old_ba = betteralgs;
 | 
	
		
			
				|  |  | +                        betteralgs = dupcat(betteralgs, ",",
 | 
	
		
			
				|  |  | +                                            hktype->alg->name,
 | 
	
		
			
				|  |  | +                                            (const char *)NULL);
 | 
	
		
			
				|  |  | +                        sfree(old_ba);
 | 
	
		
			
				|  |  | +                    } else {
 | 
	
		
			
				|  |  | +                        betteralgs = dupstr(hktype->alg->name);
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            if (betteralgs) {
 | 
	
		
			
				|  |  | +                s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
 | 
	
		
			
				|  |  | +                                  betteralgs, ssh_dialog_callback, ssh);
 | 
	
		
			
				|  |  | +                sfree(betteralgs);
 | 
	
		
			
				|  |  | +            } else {
 | 
	
		
			
				|  |  | +                s->dlgret = askalg(ssh->frontend, "host key type",
 | 
	
		
			
				|  |  | +                                   ssh->hostkey->name,
 | 
	
		
			
				|  |  | +                                   ssh_dialog_callback, ssh);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +	    if (s->dlgret < 0) {
 | 
	
		
			
				|  |  | +		do {
 | 
	
		
			
				|  |  | +		    crReturnV;
 | 
	
		
			
				|  |  | +		    if (pktin) {
 | 
	
		
			
				|  |  | +			bombout(("Unexpected data from server while"
 | 
	
		
			
				|  |  | +				 " waiting for user response"));
 | 
	
		
			
				|  |  | +			crStopV;
 | 
	
		
			
				|  |  | +		    }
 | 
	
		
			
				|  |  | +		} while (pktin || inlen > 0);
 | 
	
		
			
				|  |  | +		s->dlgret = ssh->user_response;
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	    ssh_set_frozen(ssh, 0);
 | 
	
		
			
				|  |  | +	    if (s->dlgret == 0) {
 | 
	
		
			
				|  |  | +		ssh_disconnect(ssh, "User aborted at host key warning", NULL,
 | 
	
		
			
				|  |  | +			       0, TRUE);
 | 
	
		
			
				|  |  | +		crStopV;
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  	if (s->warn_cscipher) {
 | 
	
		
			
				|  |  |  	    ssh_set_frozen(ssh, 1);
 | 
	
		
			
				|  |  |  	    s->dlgret = askalg(ssh->frontend,
 | 
	
	
		
			
				|  | @@ -7151,6 +7320,40 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      s->keystr = ssh->hostkey->fmtkey(s->hkey);
 | 
	
		
			
				|  |  |      if (!s->got_session_id) {
 | 
	
		
			
				|  |  | +	/*
 | 
	
		
			
				|  |  | +	 * Make a note of any other host key formats that are available.
 | 
	
		
			
				|  |  | +	 */
 | 
	
		
			
				|  |  | +	{
 | 
	
		
			
				|  |  | +	    int i, j;
 | 
	
		
			
				|  |  | +	    char *list = NULL;
 | 
	
		
			
				|  |  | +	    for (i = 0; i < lenof(hostkey_algs); i++) {
 | 
	
		
			
				|  |  | +		if (hostkey_algs[i].alg == ssh->hostkey)
 | 
	
		
			
				|  |  | +		    continue;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                for (j = 0; j < ssh->n_uncert_hostkeys; j++)
 | 
	
		
			
				|  |  | +                    if (ssh->uncert_hostkeys[j] == i)
 | 
	
		
			
				|  |  | +                        break;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +                if (j < ssh->n_uncert_hostkeys) {
 | 
	
		
			
				|  |  | +		    char *newlist;
 | 
	
		
			
				|  |  | +		    if (list)
 | 
	
		
			
				|  |  | +			newlist = dupprintf("%s/%s", list,
 | 
	
		
			
				|  |  | +					    hostkey_algs[i].alg->name);
 | 
	
		
			
				|  |  | +		    else
 | 
	
		
			
				|  |  | +			newlist = dupprintf("%s", hostkey_algs[i].alg->name);
 | 
	
		
			
				|  |  | +		    sfree(list);
 | 
	
		
			
				|  |  | +		    list = newlist;
 | 
	
		
			
				|  |  | +		}
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	    if (list) {
 | 
	
		
			
				|  |  | +		logeventf(ssh,
 | 
	
		
			
				|  |  | +			  "Server also has %s host key%s, but we "
 | 
	
		
			
				|  |  | +			  "don't know %s", list,
 | 
	
		
			
				|  |  | +			  j > 1 ? "s" : "", j > 1 ? "any of them" : "it");
 | 
	
		
			
				|  |  | +		sfree(list);
 | 
	
		
			
				|  |  | +	    }
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |          /*
 | 
	
		
			
				|  |  |           * Authenticate remote host: verify host key. (We've already
 | 
	
		
			
				|  |  |           * checked the signature of the exchange hash.)
 | 
	
	
		
			
				|  | @@ -7198,6 +7401,18 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |           * subsequent rekeys.
 | 
	
		
			
				|  |  |           */
 | 
	
		
			
				|  |  |          ssh->hostkey_str = s->keystr;
 | 
	
		
			
				|  |  | +    } else if (ssh->cross_certifying) {
 | 
	
		
			
				|  |  | +        s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
 | 
	
		
			
				|  |  | +        logevent("Storing additional host key for this host:");
 | 
	
		
			
				|  |  | +        logevent(s->fingerprint);
 | 
	
		
			
				|  |  | +        store_host_key(ssh->savedhost, ssh->savedport,
 | 
	
		
			
				|  |  | +                       ssh->hostkey->keytype, s->keystr);
 | 
	
		
			
				|  |  | +        ssh->cross_certifying = FALSE;
 | 
	
		
			
				|  |  | +        /*
 | 
	
		
			
				|  |  | +         * Don't forget to store the new key as the one we'll be
 | 
	
		
			
				|  |  | +         * re-checking in future normal rekeys.
 | 
	
		
			
				|  |  | +         */
 | 
	
		
			
				|  |  | +        ssh->hostkey_str = s->keystr;
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |          /*
 | 
	
		
			
				|  |  |           * In a rekey, we never present an interactive host key
 | 
	
	
		
			
				|  | @@ -7384,6 +7599,12 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen,
 | 
	
		
			
				|  |  |       */
 | 
	
		
			
				|  |  |      freebn(s->K);
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    /*
 | 
	
		
			
				|  |  | +     * Update the specials menu to list the remaining uncertified host
 | 
	
		
			
				|  |  | +     * keys.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    update_specials_menu(ssh->frontend);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      /*
 | 
	
		
			
				|  |  |       * Key exchange is over. Loop straight back round if we have a
 | 
	
		
			
				|  |  |       * deferred rekey reason.
 | 
	
	
		
			
				|  | @@ -11053,6 +11274,9 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle,
 | 
	
		
			
				|  |  |      ssh->connshare = NULL;
 | 
	
		
			
				|  |  |      ssh->attempting_connshare = FALSE;
 | 
	
		
			
				|  |  |      ssh->session_started = FALSE;
 | 
	
		
			
				|  |  | +    ssh->specials = NULL;
 | 
	
		
			
				|  |  | +    ssh->n_uncert_hostkeys = 0;
 | 
	
		
			
				|  |  | +    ssh->cross_certifying = FALSE;
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      *backend_handle = ssh;
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -11207,6 +11431,7 @@ static void ssh_free(void *handle)
 | 
	
		
			
				|  |  |      sfree(ssh->v_s);
 | 
	
		
			
				|  |  |      sfree(ssh->fullhostname);
 | 
	
		
			
				|  |  |      sfree(ssh->hostkey_str);
 | 
	
		
			
				|  |  | +    sfree(ssh->specials);
 | 
	
		
			
				|  |  |      if (ssh->crcda_ctx) {
 | 
	
		
			
				|  |  |  	crcda_free_context(ssh->crcda_ctx);
 | 
	
		
			
				|  |  |  	ssh->crcda_ctx = NULL;
 | 
	
	
		
			
				|  | @@ -11420,19 +11645,24 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 | 
	
		
			
				|  |  |      static const struct telnet_special specials_end[] = {
 | 
	
		
			
				|  |  |  	{NULL, TS_EXITMENU}
 | 
	
		
			
				|  |  |      };
 | 
	
		
			
				|  |  | -    /* XXX review this length for any changes: */
 | 
	
		
			
				|  |  | -    static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
 | 
	
		
			
				|  |  | -					      lenof(ssh2_rekey_special) +
 | 
	
		
			
				|  |  | -					      lenof(ssh2_session_specials) +
 | 
	
		
			
				|  |  | -					      lenof(specials_end)];
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    struct telnet_special *specials = NULL;
 | 
	
		
			
				|  |  | +    int nspecials = 0, specialsize = 0;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |      Ssh ssh = (Ssh) handle;
 | 
	
		
			
				|  |  | -    int i = 0;
 | 
	
		
			
				|  |  | -#define ADD_SPECIALS(name) \
 | 
	
		
			
				|  |  | -    do { \
 | 
	
		
			
				|  |  | -	assert((i + lenof(name)) <= lenof(ssh_specials)); \
 | 
	
		
			
				|  |  | -	memcpy(&ssh_specials[i], name, sizeof name); \
 | 
	
		
			
				|  |  | -	i += lenof(name); \
 | 
	
		
			
				|  |  | -    } while(0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    sfree(ssh->specials);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +#define ADD_SPECIALS(name) do                                           \
 | 
	
		
			
				|  |  | +    {                                                                   \
 | 
	
		
			
				|  |  | +        int len = lenof(name);                                          \
 | 
	
		
			
				|  |  | +        if (nspecials + len > specialsize) {                            \
 | 
	
		
			
				|  |  | +            specialsize = (nspecials + len) * 5 / 4 + 32;               \
 | 
	
		
			
				|  |  | +            specials = sresize(specials, specialsize, struct telnet_special); \
 | 
	
		
			
				|  |  | +        }                                                               \
 | 
	
		
			
				|  |  | +	memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
 | 
	
		
			
				|  |  | +        nspecials += len;                                               \
 | 
	
		
			
				|  |  | +    } while (0)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      if (ssh->version == 1) {
 | 
	
		
			
				|  |  |  	/* Don't bother offering IGNORE if we've decided the remote
 | 
	
	
		
			
				|  | @@ -11447,11 +11677,37 @@ static const struct telnet_special *ssh_get_specials(void *handle)
 | 
	
		
			
				|  |  |  	    ADD_SPECIALS(ssh2_rekey_special);
 | 
	
		
			
				|  |  |  	if (ssh->mainchan)
 | 
	
		
			
				|  |  |  	    ADD_SPECIALS(ssh2_session_specials);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        if (ssh->n_uncert_hostkeys) {
 | 
	
		
			
				|  |  | +            static const struct telnet_special uncert_start[] = {
 | 
	
		
			
				|  |  | +                {NULL, TS_SEP},
 | 
	
		
			
				|  |  | +                {"Cache new host key type", TS_SUBMENU},
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +            static const struct telnet_special uncert_end[] = {
 | 
	
		
			
				|  |  | +                {NULL, TS_EXITMENU},
 | 
	
		
			
				|  |  | +            };
 | 
	
		
			
				|  |  | +            int i;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            ADD_SPECIALS(uncert_start);
 | 
	
		
			
				|  |  | +            for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
 | 
	
		
			
				|  |  | +                struct telnet_special uncert[1];
 | 
	
		
			
				|  |  | +                const struct ssh_signkey *alg =
 | 
	
		
			
				|  |  | +                    hostkey_algs[ssh->uncert_hostkeys[i]].alg;
 | 
	
		
			
				|  |  | +                uncert[0].name = alg->name;
 | 
	
		
			
				|  |  | +                uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
 | 
	
		
			
				|  |  | +                ADD_SPECIALS(uncert);
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            ADD_SPECIALS(uncert_end);
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  |      } /* else we're not ready yet */
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    if (i) {
 | 
	
		
			
				|  |  | +    if (nspecials)
 | 
	
		
			
				|  |  |  	ADD_SPECIALS(specials_end);
 | 
	
		
			
				|  |  | -	return ssh_specials;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    ssh->specials = specials;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    if (nspecials) {
 | 
	
		
			
				|  |  | +        return specials;
 | 
	
		
			
				|  |  |      } else {
 | 
	
		
			
				|  |  |  	return NULL;
 | 
	
		
			
				|  |  |      }
 | 
	
	
		
			
				|  | @@ -11503,6 +11759,13 @@ static void ssh_special(void *handle, Telnet_Special code)
 | 
	
		
			
				|  |  |              ssh->version == 2) {
 | 
	
		
			
				|  |  |  	    do_ssh2_transport(ssh, "at user request", -1, NULL);
 | 
	
		
			
				|  |  |  	}
 | 
	
		
			
				|  |  | +    } else if (code >= TS_LOCALSTART) {
 | 
	
		
			
				|  |  | +        ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
 | 
	
		
			
				|  |  | +        ssh->cross_certifying = TRUE;
 | 
	
		
			
				|  |  | +	if (!ssh->kex_in_progress && !ssh->bare_connection &&
 | 
	
		
			
				|  |  | +            ssh->version == 2) {
 | 
	
		
			
				|  |  | +	    do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
 | 
	
		
			
				|  |  | +	}
 | 
	
		
			
				|  |  |      } else if (code == TS_BRK) {
 | 
	
		
			
				|  |  |  	if (ssh->state == SSH_STATE_CLOSED
 | 
	
		
			
				|  |  |  	    || ssh->state == SSH_STATE_PREPACKET) return;
 |