X-Git-Url: https://asedeno.scripts.mit.edu/gitweb/?a=blobdiff_plain;f=ssh.c;h=75515df11a4558e77853cc158f0bd150f2622df6;hb=15386cbe927fc85ac2fed0bb47704645c4b67dad;hp=cc503029eaf633b61f37365996291546fb1d8122;hpb=fe16b577efd7f649828cfe18c1666eb2607bfa9a;p=PuTTY.git diff --git a/ssh.c b/ssh.c index cc503029..75515df1 100644 --- a/ssh.c +++ b/ssh.c @@ -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 }; @@ -792,6 +800,7 @@ struct ssh_tag { int send_ok; int echoing, editing; + int session_started; void *frontend; int ospeed, ispeed; /* temporaries */ @@ -953,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) @@ -3070,6 +3097,8 @@ static int do_ssh_init(Ssh ssh, unsigned char c) crReturn(1); } + ssh->session_started = TRUE; + s->vstrsize = sizeof(protoname) + 16; s->vstring = snewn(s->vstrsize, char); strcpy(s->vstring, protoname); @@ -3452,34 +3481,20 @@ static void ssh_socket_log(Plug plug, int type, SockAddr addr, int port, const char *error_msg, int error_code) { Ssh ssh = (Ssh) plug; - char addrbuf[256], *msg; - - if (ssh->attempting_connshare) { - /* - * While we're attempting connection sharing, don't loudly log - * everything that happens. Real TCP connections need to be - * logged when we _start_ trying to connect, because it might - * be ages before they respond if something goes wrong; but - * connection sharing is local and quick to respond, and it's - * sufficient to simply wait and see whether it worked - * afterwards. - */ - } else { - sk_getaddr(addr, addrbuf, lenof(addrbuf)); - if (type == 0) { - if (sk_addr_needs_port(addr)) { - msg = dupprintf("Connecting to %s port %d", addrbuf, port); - } else { - msg = dupprintf("Connecting to %s", addrbuf); - } - } else { - msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg); - } + /* + * While we're attempting connection sharing, don't loudly log + * everything that happens. Real TCP connections need to be logged + * when we _start_ trying to connect, because it might be ages + * before they respond if something goes wrong; but connection + * sharing is local and quick to respond, and it's sufficient to + * simply wait and see whether it worked afterwards. + */ - logevent(msg); - sfree(msg); - } + if (!ssh->attempting_connshare) + backend_socket_log(ssh->frontend, type, addr, port, + error_msg, error_code, ssh->conf, + ssh->session_started); } void ssh_connshare_log(Ssh ssh, int event, const char *logtext, @@ -3674,10 +3689,8 @@ static const char *connect_to_host(Ssh ssh, const char *host, int port, * Try to find host. */ addressfamily = conf_get_int(ssh->conf, CONF_addressfamily); - logeventf(ssh, "Looking up host \"%s\"%s", host, - (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" : - (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : ""))); - addr = name_lookup(host, port, realhost, ssh->conf, addressfamily); + addr = name_lookup(host, port, realhost, ssh->conf, addressfamily, + ssh->frontend, "SSH connection"); if ((err = sk_addr_error(addr)) != NULL) { sk_addr_free(addr); return err; @@ -5575,7 +5588,7 @@ static void ssh1_msg_port_open(Ssh ssh, struct Packet *pktin) ssh_pkt_getstring(pktin, &host, &hostsize); port = ssh_pkt_getuint32(pktin); - pf.dhost = dupprintf("%.*s", hostsize, host); + pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host)); pf.dport = port; pfp = find234(ssh->rportfwds, &pf, NULL); @@ -6058,7 +6071,7 @@ static void ssh1_msg_debug(Ssh ssh, struct Packet *pktin) int msglen; ssh_pkt_getstring(pktin, &msg, &msglen); - logeventf(ssh, "Remote debug message: %.*s", msglen, msg); + logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); } static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) @@ -6068,7 +6081,8 @@ static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin) int msglen; ssh_pkt_getstring(pktin, &msg, &msglen); - bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg)); + bombout(("Server sent disconnect message:\n\"%.*s\"", + msglen, NULLTOEMPTY(msg))); } static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin) @@ -6235,7 +6249,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; @@ -6290,12 +6307,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; @@ -6312,6 +6329,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; @@ -6388,6 +6407,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) */ @@ -6464,20 +6497,43 @@ 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(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]; + 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(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 @@ -6489,7 +6545,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++) { @@ -6610,7 +6667,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 */ @@ -6654,7 +6712,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; @@ -6678,10 +6737,37 @@ 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(ssh->savedhost, ssh->savedport, + hostkey_algs[j].alg->keytype)) { + ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j; + } + } + } } if (s->pending_compression) { @@ -6726,6 +6812,30 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, } } + if (s->warn_hk) { + ssh_set_frozen(ssh, 1); + 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, @@ -7142,6 +7252,46 @@ 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 host key format we'd have preferred to use, + * had we already known the corresponding keys. + */ + { + int i, j = 0; + char *list = NULL; + for (i = 0; i < lenof(hostkey_algs); i++) { + if (hostkey_algs[i].alg == ssh->hostkey) + /* Not worth mentioning key types we wouldn't use */ + break; + else if (ssh->uncert_hostkeys[j] == i) { + 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; + j++; + /* Assumes that hostkey_algs and uncert_hostkeys are + * sorted in the same order */ + if (j == ssh->n_uncert_hostkeys) + break; + else + assert(ssh->uncert_hostkeys[j] > + ssh->uncert_hostkeys[j-1]); + } + } + if (list) { + logeventf(ssh, + "Server has %s host key%s, but we don't know %s; " + "using %s instead", + list, j ? "s" : "", j ? "any of them" : "it", + ssh->hostkey->name); + sfree(list); + } + } + /* * Authenticate remote host: verify host key. (We've already * checked the signature of the exchange hash.) @@ -7189,6 +7339,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 @@ -7312,14 +7474,14 @@ static void do_ssh2_transport(Ssh ssh, const void *vin, int inlen, */ if (ssh->sc_cipher_ctx) ssh->sccipher->free_context(ssh->sc_cipher_ctx); - if (ssh->sccipher) { + if (s->sccipher_tobe) { ssh->sccipher = s->sccipher_tobe; ssh->sc_cipher_ctx = ssh->sccipher->make_context(); } if (ssh->sc_mac_ctx) ssh->scmac->free_context(ssh->sc_mac_ctx); - if (ssh->scmac) { + if (s->scmac_tobe) { ssh->scmac = s->scmac_tobe; ssh->scmac_etm = s->scmac_etm_tobe; ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx); @@ -7375,6 +7537,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. @@ -8277,7 +8445,8 @@ static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin) reason_code = 0; /* ensure reasons[reason_code] in range */ ssh_pkt_getstring(pktin, &reason_string, &reason_length); logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]", - reasons[reason_code], reason_length, reason_string); + reasons[reason_code], reason_length, + NULLTOEMPTY(reason_string)); pfd_close(c->u.pfd.pf); } else if (c->type == CHAN_ZOMBIE) { @@ -8573,9 +8742,7 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) char *addrstr; ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); - addrstr = snewn(peeraddrlen+1, char); - memcpy(addrstr, peeraddr, peeraddrlen); - addrstr[peeraddrlen] = '\0'; + addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr)); peerport = ssh_pkt_getuint32(pktin); logeventf(ssh, "Received X11 connect request from %s:%d", @@ -8610,13 +8777,14 @@ static void ssh2_msg_channel_open(Ssh ssh, struct Packet *pktin) char *shost; int shostlen; ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */ - pf.shost = dupprintf("%.*s", shostlen, shost); + pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost)); pf.sport = ssh_pkt_getuint32(pktin); ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen); peerport = ssh_pkt_getuint32(pktin); realpf = find234(ssh->rportfwds, &pf, NULL); logeventf(ssh, "Received remote port %s:%d open request " - "from %s:%d", pf.shost, pf.sport, peeraddr, peerport); + "from %.*s:%d", pf.shost, pf.sport, + peeraddrlen, NULLTOEMPTY(peeraddr), peerport); sfree(pf.shost); if (realpf == NULL) { @@ -10280,7 +10448,7 @@ static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen, s->cur_prompt->to_server = TRUE; s->cur_prompt->name = dupstr("New SSH password"); s->cur_prompt->instruction = - dupprintf("%.*s", prompt_len, prompt); + dupprintf("%.*s", prompt_len, NULLTOEMPTY(prompt)); s->cur_prompt->instr_reqd = TRUE; /* * There's no explicit requirement in the protocol @@ -10718,13 +10886,13 @@ static void ssh2_msg_disconnect(Ssh ssh, struct Packet *pktin) logevent(buf); sfree(buf); buf = dupprintf("Disconnection message text: %.*s", - msglen, msg); + msglen, NULLTOEMPTY(msg)); logevent(buf); bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"", reason, (reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ? ssh2_disconnect_reasons[reason] : "unknown", - msglen, msg)); + msglen, NULLTOEMPTY(msg))); sfree(buf); } @@ -10738,7 +10906,7 @@ static void ssh2_msg_debug(Ssh ssh, struct Packet *pktin) ssh2_pkt_getbool(pktin); ssh_pkt_getstring(pktin, &msg, &msglen); - logeventf(ssh, "Remote debug message: %.*s", msglen, msg); + logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg)); } static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin) @@ -11015,6 +11183,10 @@ static const char *ssh_init(void *frontend_handle, void **backend_handle, ssh->X11_fwd_enabled = FALSE; 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; @@ -11162,6 +11334,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; @@ -11375,19 +11548,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 @@ -11402,11 +11580,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; } @@ -11458,6 +11662,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;