diff --git a/Buildscr b/Buildscr index a965e451..a41fad3c 100644 --- a/Buildscr +++ b/Buildscr @@ -35,7 +35,7 @@ module putty ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" read Date date -set Epoch 17161 # update this at every release +set Epoch 17433 # update this at every release ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -ne 'use Time::Local; /(....)(..)(..)/ and print timegm(0,0,0,$$3,$$2-1,$$1) / 86400 - $(Epoch)' > days ifneq "$(Ndate)" "" read Days days diff --git a/LATEST.VER b/LATEST.VER index 6ab5ccfd..3ea25f59 100644 --- a/LATEST.VER +++ b/LATEST.VER @@ -1 +1 @@ -0.73 +0.74 diff --git a/config.c b/config.c index 4dfa49a3..a8d88e87 100644 --- a/config.c +++ b/config.c @@ -1430,7 +1430,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, #endif ) { #ifdef NAMED_CLIPBOARDS - const char *sval = dlg_editbox_get(ctrl, dlg); + char *sval = dlg_editbox_get(ctrl, dlg); int i; for (i = 0; i < lenof(options); i++) @@ -1445,6 +1445,8 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg, sval++; conf_set_str(conf, strsetting, sval); } + + sfree(sval); #else int index = dlg_listbox_index(ctrl, dlg); if (index >= 0) { @@ -2570,6 +2572,10 @@ void setup_config_box(struct controlbox *b, bool midsession, HELPCTX(ssh_hklist), hklist_handler, P(NULL)); c->listbox.height = 5; + + ctrl_checkbox(s, "Prefer algorithms for which a host key is known", + 'p', HELPCTX(ssh_hk_known), conf_checkbox_handler, + I(CONF_ssh_prefer_known_hostkeys)); } /* diff --git a/doc/config.but b/doc/config.but index 4ecf618c..58a5dc9a 100644 --- a/doc/config.but +++ b/doc/config.but @@ -2602,6 +2602,27 @@ If the first key type PuTTY finds is below the \q{warn below here} line, you will see a warning box when you make the connection, similar to that for cipher selection (see \k{config-ssh-encryption}). +\S{config-ssh-prefer-known-hostkeys} Preferring known host keys + +By default, PuTTY will adjust the preference order for host key +algorithms so that any host keys it already knows are moved to the top +of the list. + +This prevents you from having to check and confirm a new host key for +a server you already had one for (e.g. because the server has +generated an alternative key of a type higher in PuTTY's preference +order, or because you changed the preference order itself). + +However, on the other hand, it can leak information to a listener in +the network about \e{whether} you already know a host key for this +server. + +For this reason, this policy is configurable. By turning this checkbox +off, you can reset PuTTY to always use the exact order of host key +algorithms configured in the preference list described in +\k{config-ssh-hostkey-order}, so that a listener will find out nothing +about what keys you had stored. + \S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys} In some situations, if PuTTY's automated host key management is not diff --git a/doc/plink.but b/doc/plink.but index 25139bec..ea622151 100644 --- a/doc/plink.but +++ b/doc/plink.but @@ -39,9 +39,9 @@ Once you've got a console window to type into, you can just type version of Plink you're using, and gives you a brief summary of how to use Plink: -\c Z:\sysosd>plink +\c C:\>plink \c Plink: command-line connection utility -\c Release 0.73 +\c Release 0.74 \c Usage: plink [options] [user@]host [command] \c ("host" can also be a PuTTY saved session name) \c Options: @@ -100,7 +100,7 @@ Once this works, you are ready to use Plink. To make a simple interactive connection to a remote server, just type \c{plink} and then the host name: -\c Z:\sysosd>plink login.example.com +\c C:\>plink login.example.com \c \c Debian GNU/Linux 2.2 flunky.example.com \c flunky login: @@ -117,7 +117,7 @@ In order to connect with a different protocol, you can give the command line options \c{-ssh}, \c{-telnet}, \c{-rlogin} or \c{-raw}. To make an SSH connection, for example: -\c Z:\sysosd>plink -ssh login.example.com +\c C:\>plink -ssh login.example.com \c login as: If you have already set up a PuTTY saved session, then instead of @@ -125,7 +125,7 @@ supplying a host name, you can give the saved session name. This allows you to use public-key authentication, specify a user name, and use most of the other features of PuTTY: -\c Z:\sysosd>plink my-ssh-session +\c C:\>plink my-ssh-session \c Sent username "fred" \c Authenticating with public key "fred@winbox" \c Last login: Thu Dec 6 19:25:33 2001 from :0.0 @@ -196,18 +196,18 @@ Once you have done all this, you should be able to run a remote command on the SSH server machine and have it execute automatically with no prompting: -\c Z:\sysosd>plink login.example.com -l fred echo hello, world +\c C:\>plink login.example.com -l fred echo hello, world \c hello, world \c -\c Z:\sysosd> +\c C:\> Or, if you have set up a saved session with all the connection details: -\c Z:\sysosd>plink mysession echo hello, world +\c C:\>plink mysession echo hello, world \c hello, world \c -\c Z:\sysosd> +\c C:\> Then you can set up other programs to run this Plink command and talk to it as if it were a process on the server machine. diff --git a/doc/pscp.but b/doc/pscp.but index c8c5b94d..c8585764 100644 --- a/doc/pscp.but +++ b/doc/pscp.but @@ -37,9 +37,9 @@ Once you've got a console window to type into, you can just type version of PSCP you're using, and gives you a brief summary of how to use PSCP: -\c Z:\owendadmin>pscp +\c C:\>pscp \c PuTTY Secure Copy client -\c Release 0.73 +\c Release 0.74 \c Usage: pscp [options] [user@]host:source target \c pscp [options] source [source...] [user@]host:target \c pscp [options] -ls [user@]host:filespec diff --git a/import.c b/import.c index 86497e59..553fa750 100644 --- a/import.c +++ b/import.c @@ -572,8 +572,10 @@ static ssh2_userkey *openssh_pem_read( strbuf *blob = strbuf_new_nm(); int privptr = 0, publen; - if (!key) + if (!key) { + strbuf_free(blob); return NULL; + } if (key->encrypted) { unsigned char keybuf[32]; diff --git a/putty.h b/putty.h index 8fa76aa1..64b882b8 100644 --- a/putty.h +++ b/putty.h @@ -1256,6 +1256,7 @@ NORETURN void cleanup_exit(int); X(BOOL, NONE, compression) \ X(INT, INT, ssh_kexlist) \ X(INT, INT, ssh_hklist) \ + X(BOOL, NONE, ssh_prefer_known_hostkeys) \ X(INT, NONE, ssh_rekey_time) /* in minutes */ \ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ X(BOOL, NONE, tryagent) \ diff --git a/sesschan.c b/sesschan.c index e9acaae9..7e735ddc 100644 --- a/sesschan.c +++ b/sesschan.c @@ -640,10 +640,10 @@ static void sesschan_notify_remote_exit(Seat *seat) sshfwd_send_exit_signal( sess->c, signame, false, ptrlen_from_asciz(sigmsg)); - sfree(sigmsg); - got_signal = true; } + + sfree(sigmsg); } else { int signum = pty_backend_exit_signum(sess->backend); diff --git a/settings.c b/settings.c index 5f51f3b0..547af0dc 100644 --- a/settings.c +++ b/settings.c @@ -602,6 +602,7 @@ void save_open_settings(settings_w *sesskey, Conf *conf) wprefs(sesskey, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist); wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); + write_setting_b(sesskey, "PreferKnownHostKeys", conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys)); write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time)); #ifndef NO_GSSAPI write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); @@ -1006,6 +1007,7 @@ void load_open_settings(settings_r *sesskey, Conf *conf) } gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN", hknames, HK_MAX, conf, CONF_ssh_hklist); + gppb(sesskey, "PreferKnownHostKeys", true, conf, CONF_ssh_prefer_known_hostkeys); gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time); #ifndef NO_GSSAPI gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); diff --git a/ssh1login-server.c b/ssh1login-server.c index a9de19a4..c9c10eef 100644 --- a/ssh1login-server.c +++ b/ssh1login-server.c @@ -298,18 +298,34 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) mp_int *modulus = get_mp_ssh1(pktin); s->authkey = auth_publickey_ssh1( s->authpolicy, s->username, modulus); + + if (!s->authkey && + s->ssc->stunt_pretend_to_accept_any_pubkey) { + mp_int *zero = mp_from_integer(0); + mp_int *fake_challenge = mp_random_in_range(zero, modulus); + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_SMSG_AUTH_RSA_CHALLENGE); + put_mp_ssh1(pktout, fake_challenge); + pq_push(s->ppl.out_pq, pktout); + + mp_free(zero); + mp_free(fake_challenge); + } + mp_free(modulus); } - if (!s->authkey) + if (!s->authkey && + !s->ssc->stunt_pretend_to_accept_any_pubkey) continue; - if (s->authkey->bytes < 32) { + if (s->authkey && s->authkey->bytes < 32) { ppl_logevent("Auth key far too small"); continue; } - { + if (s->authkey) { unsigned char *rsabuf = snewn(s->authkey->bytes, unsigned char); @@ -349,6 +365,9 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl) return; } + if (!s->authkey) + continue; + { ptrlen response = get_data(pktin, 16); ptrlen expected = make_ptrlen( diff --git a/ssh1login.c b/ssh1login.c index 764123c9..cf191831 100644 --- a/ssh1login.c +++ b/ssh1login.c @@ -12,6 +12,12 @@ #include "sshppl.h" #include "sshcr.h" +typedef struct agent_key { + RSAKey key; + strbuf *comment; + ptrlen blob; /* only used during initial parsing of agent response */ +} agent_key; + struct ssh1_login_state { int crState; @@ -47,11 +53,11 @@ struct ssh1_login_state { void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* response from SSH agent */ - int keyi, nkeys; + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; bool authed; RSAKey key; - mp_int *challenge; - strbuf *agent_comment; int dlgret; Filename *keyfile; RSAKey servkey, hostkey; @@ -99,7 +105,6 @@ PacketProtocolLayer *ssh1_login_new( s->savedhost = dupstr(host); s->savedport = port; s->successor_layer = successor_layer; - s->agent_comment = strbuf_new(); return &s->ppl; } @@ -118,9 +123,15 @@ static void ssh1_login_free(PacketProtocolLayer *ppl) if (s->publickey_blob) strbuf_free(s->publickey_blob); sfree(s->publickey_comment); - strbuf_free(s->agent_comment); if (s->cur_prompt) free_prompts(s->cur_prompt); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + freersakey(&s->agent_keys[i].key); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } sfree(s->agent_response_to_free); if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); @@ -504,122 +515,165 @@ static void ssh1_login_process_queue(PacketProtocolLayer *ppl) get_uint32(s->asrc); /* skip length field */ if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { - s->nkeys = toint(get_uint32(s->asrc)); - if (s->nkeys < 0) { - ppl_logevent("Pageant reported negative key count %d", - s->nkeys); - s->nkeys = 0; - } - ppl_logevent("Pageant has %d SSH-1 keys", s->nkeys); - for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) { - size_t start, end; - start = s->asrc->pos; - get_rsa_ssh1_pub(s->asrc, &s->key, - RSA_SSH1_EXPONENT_FIRST); - end = s->asrc->pos; - strbuf_clear(s->agent_comment); - put_datapl(s->agent_comment, get_string(s->asrc)); - if (get_err(s->asrc)) { - ppl_logevent("Pageant key list packet was truncated"); - break; - } - if (s->publickey_blob) { - ptrlen keystr = make_ptrlen( - (const char *)s->asrc->data + start, end - start); + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; - if (keystr.len == s->publickey_blob->len && - !memcmp(keystr.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", s->keyi); - s->tried_publickey = true; - } else - /* Skip non-configured key */ - continue; + /* + * Check that the agent response is well formed. + */ + for (size_t i = 0; i < nkeys; i++) { + get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST); + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto parsed_agent_query; } - ppl_logevent("Trying Pageant key #%d", s->keyi); - pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); - put_mp_ssh1(pkt, s->key.modulus); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { - ppl_logevent("Key refused"); - continue; + } + + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + memset(&s->agent_keys[i].key, 0, + sizeof(s->agent_keys[i].key)); + + const char *blobstart = get_ptr(s->asrc); + get_rsa_ssh1_pub(s->asrc, &s->agent_keys[i].key, + RSA_SSH1_EXPONENT_FIRST); + const char *blobend = get_ptr(s->asrc); + + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); + + s->agent_keys[i].blob = make_ptrlen( + blobstart, blobend - blobstart); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-1 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent + * down to only that one, or none if it's not + * there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, s->agent_keys[i].blob)) + break; } - ppl_logevent("Received RSA challenge"); - s->challenge = get_mp_ssh1(pktin); + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { + ppl_logevent("Configured key file not in Pageant"); + s->agent_key_index = 0; + s->agent_key_limit = 0; + } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; + } + } else { + ppl_logevent("Failed to get reply from Pageant"); + } + parsed_agent_query:; + + for (; s->agent_key_index < s->agent_key_limit; + s->agent_key_index++) { + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); + pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_RSA); + put_mp_ssh1(pkt, + s->agent_keys[s->agent_key_index].key.modulus); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { + ppl_logevent("Key refused"); + continue; + } + ppl_logevent("Received RSA challenge"); + + { + mp_int *challenge = get_mp_ssh1(pktin); if (get_err(pktin)) { - mp_free(s->challenge); + mp_free(challenge); ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " "was badly formatted"); return; } - { - strbuf *agentreq; - const char *ret; + strbuf *agentreq = strbuf_new_for_agent_query(); + put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - agentreq = strbuf_new_for_agent_query(); - put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); - put_uint32(agentreq, mp_get_nbits(s->key.modulus)); - put_mp_ssh1(agentreq, s->key.exponent); - put_mp_ssh1(agentreq, s->key.modulus); - put_mp_ssh1(agentreq, s->challenge); - put_data(agentreq, s->session_id, 16); - put_uint32(agentreq, 1); /* response format */ - ssh1_login_agent_query(s, agentreq); - strbuf_free(agentreq); - crMaybeWaitUntilV(!s->auth_agent_query); + rsa_ssh1_public_blob( + BinarySink_UPCAST(agentreq), + &s->agent_keys[s->agent_key_index].key, + RSA_SSH1_EXPONENT_FIRST); - ret = s->agent_response.ptr; - if (ret) { - if (s->agent_response.len >= 5+16 && - ret[4] == SSH1_AGENT_RSA_RESPONSE) { - ppl_logevent("Sending Pageant's response"); - pkt = ssh_bpp_new_pktout( - s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); - put_data(pkt, ret + 5, 16); - pq_push(s->ppl.out_pq, pkt); - crMaybeWaitUntilV( - (pktin = ssh1_login_pop(s)) - != NULL); - if (pktin->type == SSH1_SMSG_SUCCESS) { - ppl_logevent("Pageant's response " - "accepted"); - if (seat_verbose(s->ppl.seat)) { - ptrlen comment = ptrlen_from_strbuf( - s->agent_comment); - ppl_printf("Authenticated using RSA " - "key \"%.*s\" from " - "agent\r\n", - PTRLEN_PRINTF(comment)); - } - s->authed = true; - } else - ppl_logevent("Pageant's response not " - "accepted"); - } else { - ppl_logevent("Pageant failed to answer " - "challenge"); - sfree((char *)ret); - } - } else { - ppl_logevent("No reply received from Pageant"); - } - } - mp_free(s->key.exponent); - mp_free(s->key.modulus); - mp_free(s->challenge); - if (s->authed) - break; + put_mp_ssh1(agentreq, challenge); + mp_free(challenge); + + put_data(agentreq, s->session_id, 16); + put_uint32(agentreq, 1); /* response format */ + ssh1_login_agent_query(s, agentreq); + strbuf_free(agentreq); + crMaybeWaitUntilV(!s->auth_agent_query); } - sfree(s->agent_response_to_free); - s->agent_response_to_free = NULL; - if (s->publickey_blob && !s->tried_publickey) - ppl_logevent("Configured key file not in Pageant"); - } else { - ppl_logevent("Failed to get reply from Pageant"); + + { + const unsigned char *ret = s->agent_response.ptr; + if (ret) { + if (s->agent_response.len >= 5+16 && + ret[4] == SSH1_AGENT_RSA_RESPONSE) { + ppl_logevent("Sending Pageant's response"); + pkt = ssh_bpp_new_pktout( + s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); + put_data(pkt, ret + 5, 16); + pq_push(s->ppl.out_pq, pkt); + crMaybeWaitUntilV( + (pktin = ssh1_login_pop(s)) + != NULL); + if (pktin->type == SSH1_SMSG_SUCCESS) { + ppl_logevent("Pageant's response " + "accepted"); + if (seat_verbose(s->ppl.seat)) { + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index]. + comment); + ppl_printf("Authenticated using RSA " + "key \"%.*s\" from " + "agent\r\n", + PTRLEN_PRINTF(comment)); + } + s->authed = true; + } else + ppl_logevent("Pageant's response not " + "accepted"); + } else { + ppl_logevent("Pageant failed to answer " + "challenge"); + sfree((char *)ret); + } + } else { + ppl_logevent("No reply received from Pageant"); + } + } + if (s->authed) + break; } if (s->authed) break; diff --git a/ssh2transport.c b/ssh2transport.c index cfe64e75..7d2b1631 100644 --- a/ssh2transport.c +++ b/ssh2transport.c @@ -576,9 +576,10 @@ static void ssh2_write_kexinit_lists( } } else if (first_time) { /* - * 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. + * In the first key exchange, we list all the algorithms we're + * prepared to cope with, but (if configured to) we 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 @@ -594,7 +595,8 @@ static void ssh2_write_kexinit_lists( for (j = 0; j < lenof(ssh2_hostkey_algs); j++) { if (ssh2_hostkey_algs[j].id != preferred_hk[i]) continue; - if (have_ssh_host_key(hk_host, hk_port, + if (conf_get_bool(conf, CONF_ssh_prefer_known_hostkeys) && + have_ssh_host_key(hk_host, hk_port, ssh2_hostkey_algs[j].alg->cache_id)) { alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], ssh2_hostkey_algs[j].alg->ssh_id); diff --git a/ssh2userauth-server.c b/ssh2userauth-server.c index d4dd92b4..cacddee6 100644 --- a/ssh2userauth-server.c +++ b/ssh2userauth-server.c @@ -199,7 +199,7 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) goto failure; } } else if (ptrlen_eq_string(s->method, "publickey")) { - bool has_signature, success; + bool has_signature, success, send_pk_ok, key_really_ok; ptrlen algorithm, blob, signature; const ssh_keyalg *keyalg; ssh_key *key; @@ -213,7 +213,23 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) algorithm = get_string(pktin); blob = get_string(pktin); - if (!auth_publickey(s->authpolicy, s->username, blob)) + key_really_ok = auth_publickey(s->authpolicy, s->username, blob); + send_pk_ok = key_really_ok || + s->ssc->stunt_pretend_to_accept_any_pubkey; + + if (!has_signature) { + if (!send_pk_ok) + goto failure; + + pktout = ssh_bpp_new_pktout( + s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); + put_stringpl(pktout, algorithm); + put_stringpl(pktout, blob); + pq_push(s->ppl.out_pq, pktout); + continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ + } + + if (!key_really_ok) goto failure; keyalg = find_pubkey_alg_len(algorithm); @@ -223,16 +239,6 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl) if (!key) goto failure; - if (!has_signature) { - ssh_key_free(key); - pktout = ssh_bpp_new_pktout( - s->ppl.bpp, SSH2_MSG_USERAUTH_PK_OK); - put_stringpl(pktout, algorithm); - put_stringpl(pktout, blob); - pq_push(s->ppl.out_pq, pktout); - continue; /* skip USERAUTH_{SUCCESS,FAILURE} epilogue */ - } - sigdata = strbuf_new(); ssh2_userauth_server_add_session_id(s, sigdata); put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); diff --git a/ssh2userauth.c b/ssh2userauth.c index 6d9afb80..34a3900d 100644 --- a/ssh2userauth.c +++ b/ssh2userauth.c @@ -18,6 +18,11 @@ #define BANNER_LIMIT 131072 +typedef struct agent_key { + strbuf *blob, *comment; + ptrlen algorithm; +} agent_key; + struct ssh2_userauth_state { int crState; @@ -69,9 +74,9 @@ struct ssh2_userauth_state { void *agent_response_to_free; ptrlen agent_response; BinarySource asrc[1]; /* for reading SSH agent response */ - size_t pkblob_pos_in_agent; - int keyi, nkeys; - ptrlen pk, alg, comment; + size_t agent_keys_len; + agent_key *agent_keys; + size_t agent_key_index, agent_key_limit; int len; PktOut *pktout; bool want_user_input; @@ -173,6 +178,13 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl) if (s->successor_layer) ssh_ppl_free(s->successor_layer); + if (s->agent_keys) { + for (size_t i = 0; i < s->agent_keys_len; i++) { + strbuf_free(s->agent_keys[i].blob); + strbuf_free(s->agent_keys[i].comment); + } + sfree(s->agent_keys); + } sfree(s->agent_response_to_free); if (s->auth_agent_query) agent_cancel_query(s->auth_agent_query); @@ -298,8 +310,6 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) * Find out about any keys Pageant has (but if there's a public * key configured, filter out all others). */ - s->nkeys = 0; - s->pkblob_pos_in_agent = 0; if (s->tryagent && agent_exists()) { ppl_logevent("Pageant is running. Requesting keys."); @@ -315,48 +325,75 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) get_uint32(s->asrc); /* skip length field */ if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { - int keyi; - - s->nkeys = toint(get_uint32(s->asrc)); + size_t nkeys = get_uint32(s->asrc); + size_t origpos = s->asrc->pos; /* - * Vet the Pageant response to ensure that the key count - * and blob lengths make sense. + * Check that the agent response is well formed. */ - if (s->nkeys < 0) { - ppl_logevent("Pageant response contained a negative" - " key count %d", s->nkeys); - s->nkeys = 0; - goto done_agent_query; - } else { - ppl_logevent("Pageant has %d SSH-2 keys", s->nkeys); + for (size_t i = 0; i < nkeys; i++) { + get_string(s->asrc); /* blob */ + get_string(s->asrc); /* comment */ + if (get_err(s->asrc)) { + ppl_logevent("Pageant's response was truncated"); + goto done_agent_query; + } + } - /* See if configured key is in agent. */ - for (keyi = 0; keyi < s->nkeys; keyi++) { - size_t pos = s->asrc->pos; - ptrlen blob = get_string(s->asrc); - get_string(s->asrc); /* skip comment */ - if (get_err(s->asrc)) { - ppl_logevent("Pageant response was truncated"); - s->nkeys = 0; - goto done_agent_query; - } + /* + * Copy the list of public-key blobs out of the Pageant + * response. + */ + BinarySource_REWIND_TO(s->asrc, origpos); + s->agent_keys_len = nkeys; + s->agent_keys = snewn(s->agent_keys_len, agent_key); + for (size_t i = 0; i < nkeys; i++) { + s->agent_keys[i].blob = strbuf_new(); + put_datapl(s->agent_keys[i].blob, get_string(s->asrc)); + s->agent_keys[i].comment = strbuf_new(); + put_datapl(s->agent_keys[i].comment, get_string(s->asrc)); - if (s->publickey_blob && - blob.len == s->publickey_blob->len && - !memcmp(blob.ptr, s->publickey_blob->s, - s->publickey_blob->len)) { - ppl_logevent("Pageant key #%d matches " - "configured key file", keyi); - s->keyi = keyi; - s->pkblob_pos_in_agent = pos; + /* Also, extract the algorithm string from the start + * of the public-key blob. */ + BinarySource src[1]; + BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf( + s->agent_keys[i].blob)); + s->agent_keys[i].algorithm = get_string(src); + } + + ppl_logevent("Pageant has %"SIZEu" SSH-2 keys", nkeys); + + if (s->publickey_blob) { + /* + * If we've been given a specific public key blob, + * filter the list of keys to try from the agent down + * to only that one, or none if it's not there. + */ + ptrlen our_blob = ptrlen_from_strbuf(s->publickey_blob); + size_t i; + + for (i = 0; i < nkeys; i++) { + if (ptrlen_eq_ptrlen(our_blob, ptrlen_from_strbuf( + s->agent_keys[i].blob))) break; - } } - if (s->publickey_blob && !s->pkblob_pos_in_agent) { + + if (i < nkeys) { + ppl_logevent("Pageant key #%"SIZEu" matches " + "configured key file", i); + s->agent_key_index = i; + s->agent_key_limit = i+1; + } else { ppl_logevent("Configured key file not in Pageant"); - s->nkeys = 0; + s->agent_key_index = 0; + s->agent_key_limit = 0; } + } else { + /* + * Otherwise, try them all. + */ + s->agent_key_index = 0; + s->agent_key_limit = nkeys; } } else { ppl_logevent("Failed to get reply from Pageant"); @@ -455,17 +492,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->tried_pubkey_config = false; s->kbd_inter_refused = false; - - /* Reset agent request state. */ s->done_agent = false; - if (s->agent_response.ptr) { - if (s->pkblob_pos_in_agent) { - s->asrc->pos = s->pkblob_pos_in_agent; - } else { - s->asrc->pos = 9; /* skip length + type + key count */ - s->keyi = 0; - } - } while (1) { /* @@ -686,7 +713,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else #endif /* NO_GSSAPI */ - if (s->can_pubkey && !s->done_agent && s->nkeys) { + if (s->can_pubkey && !s->done_agent && + s->agent_key_index < s->agent_key_limit) { /* * Attempt public-key authentication using a key from Pageant. @@ -694,16 +722,7 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY; - ppl_logevent("Trying Pageant key #%d", s->keyi); - - /* Unpack key from agent response */ - s->pk = get_string(s->asrc); - s->comment = get_string(s->asrc); - { - BinarySource src[1]; - BinarySource_BARE_INIT_PL(src, s->pk); - s->alg = get_string(src); - } + ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index); /* See if server will accept it */ s->pktout = ssh_bpp_new_pktout( @@ -713,8 +732,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, false); /* no signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; @@ -727,11 +748,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } else { strbuf *agentreq, *sigdata; + ptrlen comment = ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].comment); if (seat_verbose(s->ppl.seat)) ppl_printf("Authenticating with public key " "\"%.*s\" from agent\r\n", - PTRLEN_PRINTF(s->comment)); + PTRLEN_PRINTF(comment)); /* * Server is willing to accept the key. @@ -744,13 +767,16 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) put_stringz(s->pktout, "publickey"); /* method */ put_bool(s->pktout, true); /* signature included */ - put_stringpl(s->pktout, s->alg); - put_stringpl(s->pktout, s->pk); + put_stringpl(s->pktout, + s->agent_keys[s->agent_key_index].algorithm); + put_stringpl(s->pktout, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Ask agent for signature. */ agentreq = strbuf_new_for_agent_query(); put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); - put_stringpl(agentreq, s->pk); + put_stringpl(agentreq, ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob)); /* Now the data to be signed... */ sigdata = strbuf_new(); ssh2_userauth_add_session_id(s, sigdata); @@ -772,8 +798,11 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE && (sigblob = get_string(src), !get_err(src))) { ppl_logevent("Sending Pageant's response"); - ssh2_userauth_add_sigblob(s, s->pktout, - s->pk, sigblob); + ssh2_userauth_add_sigblob( + s, s->pktout, + ptrlen_from_strbuf( + s->agent_keys[s->agent_key_index].blob), + sigblob); pq_push(s->ppl.out_pq, s->pktout); s->type = AUTH_TYPE_PUBLICKEY; } else { @@ -794,14 +823,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl) } /* Do we have any keys left to try? */ - if (s->pkblob_pos_in_agent) { + if (++s->agent_key_index >= s->agent_key_limit) s->done_agent = true; - s->tried_pubkey_config = true; - } else { - s->keyi++; - if (s->keyi >= s->nkeys) - s->done_agent = true; - } } else if (s->can_pubkey && s->publickey_blob && s->privatekey_available && !s->tried_pubkey_config) { @@ -1786,7 +1809,7 @@ static void ssh2_userauth_add_sigblob( /* debug("modulus length is %d\n", len); */ /* debug("signature length is %d\n", siglen); */ - if (mod_mp.len != sig_mp.len) { + if (mod_mp.len > sig_mp.len) { strbuf *substr = strbuf_new(); put_data(substr, sigblob.ptr, sig_prefix_len); put_uint32(substr, mod_mp.len); diff --git a/sshdss.c b/sshdss.c index 2f637d89..3e0c7618 100644 --- a/sshdss.c +++ b/sshdss.c @@ -72,8 +72,10 @@ static char *dss_cache_str(ssh_key *key) struct dss_key *dss = container_of(key, struct dss_key, sshk); strbuf *sb = strbuf_new(); - if (!dss->p) + if (!dss->p) { + strbuf_free(sb); return NULL; + } append_hex_to_strbuf(sb, dss->p); append_hex_to_strbuf(sb, dss->q); diff --git a/sshserver.h b/sshserver.h index 588636b3..f8c53bad 100644 --- a/sshserver.h +++ b/sshserver.h @@ -17,6 +17,8 @@ struct SshServerConfig { unsigned long ssh1_cipher_mask; bool ssh1_allow_compression; bool bare_connection; + + bool stunt_pretend_to_accept_any_pubkey; }; Plug *ssh_server_plug( diff --git a/terminal.c b/terminal.c index aa4a9e7d..bdf10ffd 100644 --- a/terminal.c +++ b/terminal.c @@ -2072,7 +2072,9 @@ static void swap_screen(Terminal *term, int which, ttr = term->alt_screen; term->alt_screen = term->screen; term->screen = ttr; - term->alt_sblines = find_last_nonempty_line(term, term->alt_screen) + 1; + term->alt_sblines = ( + term->alt_screen ? + find_last_nonempty_line(term, term->alt_screen) + 1 : 0); t = term->curs.x; if (!reset && !keep_cur_pos) term->curs.x = term->alt_x; diff --git a/unix/gtkmain.c b/unix/gtkmain.c index e7a0eff3..ec8f7da4 100644 --- a/unix/gtkmain.c +++ b/unix/gtkmain.c @@ -315,22 +315,25 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf) char *val; /* - * Macros to make argument handling easier. Note that because - * they need to call `continue', they cannot be contained in - * the usual do {...} while (0) wrapper to make them - * syntactically single statements; hence it is not legal to - * use one of these macros as an unbraced statement between - * `if' and `else'. + * Macros to make argument handling easier. + * + * Note that because they need to call `continue', they cannot be + * contained in the usual do {...} while (0) wrapper to make them + * syntactically single statements. I use the alternative if (1) + * {...} else ((void)0). */ -#define EXPECTS_ARG { \ - if (--argc <= 0) { \ - err = true; \ - fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ - continue; \ - } else \ - val = *++argv; \ -} -#define SECOND_PASS_ONLY do { if (!do_everything) continue; } while (0) +#define EXPECTS_ARG if (1) { \ + if (--argc <= 0) { \ + err = true; \ + fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ + continue; \ + } else \ + val = *++argv; \ + } else ((void)0) +#define SECOND_PASS_ONLY if (1) { \ + if (!do_everything) \ + continue; \ + } else ((void)0) while (--argc > 0) { const char *p = *++argv; diff --git a/unix/uxagentsock.c b/unix/uxagentsock.c index 3fbbb84f..ba87cd1a 100644 --- a/unix/uxagentsock.c +++ b/unix/uxagentsock.c @@ -29,6 +29,7 @@ Socket *platform_make_agent_socket( if ((errw = make_dir_and_check_ours(socketdir)) != NULL) { *error = dupprintf("%s: %s\n", socketdir, errw); sfree(errw); + sfree(socketdir); return NULL; } diff --git a/unix/uxpty.c b/unix/uxpty.c index 09c5ecb0..bfd0de57 100644 --- a/unix/uxpty.c +++ b/unix/uxpty.c @@ -206,6 +206,8 @@ static void setup_utmp(char *ttyname, char *location) struct timeval tv; pw = getpwuid(getuid()); + if (!pw) + return; /* can't stamp utmp if we don't have a username */ memset(&utmp_entry, 0, sizeof(utmp_entry)); utmp_entry.ut_type = USER_PROCESS; utmp_entry.ut_pid = getpid(); diff --git a/unix/uxserver.c b/unix/uxserver.c index eb0056d6..48a27d2c 100644 --- a/unix/uxserver.c +++ b/unix/uxserver.c @@ -775,6 +775,8 @@ int main(int argc, char **argv) filename_free(logfile); conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); conf_set_int(conf, CONF_logxfovr, LGXF_OVR); + } else if (!strcmp(arg, "--pretend-to-accept-any-pubkey")) { + ssc.stunt_pretend_to_accept_any_pubkey = true; } else { fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); exit(1); diff --git a/windows/window.c b/windows/window.c index 0ccf700f..a90f713e 100644 --- a/windows/window.c +++ b/windows/window.c @@ -5225,7 +5225,7 @@ static void wintw_clip_write( (int)udata[uindex]); alen = 1; strcpy(after, "}"); } else { - blen = sprintf(before, "\\u%d", udata[uindex]); + blen = sprintf(before, "\\u%d", (int)udata[uindex]); alen = 0; after[0] = '\0'; } } diff --git a/windows/winhelp.h b/windows/winhelp.h index e5bf6c14..b9fbd1d3 100644 --- a/windows/winhelp.h +++ b/windows/winhelp.h @@ -106,6 +106,7 @@ #define WINHELP_CTX_ssh_share "config-ssh-sharing" #define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" #define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-order" +#define WINHELP_CTX_ssh_hk_known "config-ssh-prefer-known-hostkeys" #define WINHELP_CTX_ssh_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys"