1
0
mirror of https://git.tartarus.org/simon/putty.git synced 2025-01-09 01:18:00 +00:00

Merge the 0.74 release branch back to master.

Two minor memory-leak fixes on 0.74 seem not to be needed on master:
the fix in an early exit path of pageant_add_keyfile is done already
on master in a different way, and the missing sfree(fdlist) in
uxsftp.c is in code that's been completely rewritten in the uxcliloop
refactoring.

Other minor conflicts: the rework in commit b52641644905 of
ssh1login.c collided with the change from FLAG_VERBOSE to
seat_verbose(), and master and 0.74 each added an unrelated extra
field to the end of struct SshServerConfig.
This commit is contained in:
Simon Tatham 2020-06-27 08:09:24 +01:00
commit 2762a2025f
24 changed files with 389 additions and 238 deletions

View File

@ -35,7 +35,7 @@ module putty
ifeq "$(RELEASE)" "" set Ndate $(!builddate) ifeq "$(RELEASE)" "" set Ndate $(!builddate)
ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date ifneq "$(Ndate)" "" in . do echo $(Ndate) | perl -pe 's/(....)(..)(..)/$$1-$$2-$$3/' > date
ifneq "$(Ndate)" "" read Date 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)" "" 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 ifneq "$(Ndate)" "" read Days days

View File

@ -1 +1 @@
0.73 0.74

View File

@ -1430,7 +1430,7 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
#endif #endif
) { ) {
#ifdef NAMED_CLIPBOARDS #ifdef NAMED_CLIPBOARDS
const char *sval = dlg_editbox_get(ctrl, dlg); char *sval = dlg_editbox_get(ctrl, dlg);
int i; int i;
for (i = 0; i < lenof(options); i++) for (i = 0; i < lenof(options); i++)
@ -1445,6 +1445,8 @@ static void clipboard_selector_handler(union control *ctrl, dlgparam *dlg,
sval++; sval++;
conf_set_str(conf, strsetting, sval); conf_set_str(conf, strsetting, sval);
} }
sfree(sval);
#else #else
int index = dlg_listbox_index(ctrl, dlg); int index = dlg_listbox_index(ctrl, dlg);
if (index >= 0) { if (index >= 0) {
@ -2570,6 +2572,10 @@ void setup_config_box(struct controlbox *b, bool midsession,
HELPCTX(ssh_hklist), HELPCTX(ssh_hklist),
hklist_handler, P(NULL)); hklist_handler, P(NULL));
c->listbox.height = 5; 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));
} }
/* /*

View File

@ -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 line, you will see a warning box when you make the connection, similar
to that for cipher selection (see \k{config-ssh-encryption}). 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} \S{config-ssh-kex-manual-hostkeys} \ii{Manually configuring host keys}
In some situations, if PuTTY's automated host key management is not In some situations, if PuTTY's automated host key management is not

View File

@ -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 version of Plink you're using, and gives you a brief summary of how to
use Plink: use Plink:
\c Z:\sysosd>plink \c C:\>plink
\c Plink: command-line connection utility \c Plink: command-line connection utility
\c Release 0.73 \c Release 0.74
\c Usage: plink [options] [user@]host [command] \c Usage: plink [options] [user@]host [command]
\c ("host" can also be a PuTTY saved session name) \c ("host" can also be a PuTTY saved session name)
\c Options: \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 To make a simple interactive connection to a remote server, just
type \c{plink} and then the host name: type \c{plink} and then the host name:
\c Z:\sysosd>plink login.example.com \c C:\>plink login.example.com
\c \c
\c Debian GNU/Linux 2.2 flunky.example.com \c Debian GNU/Linux 2.2 flunky.example.com
\c flunky login: \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}. command line options \c{-ssh}, \c{-telnet}, \c{-rlogin} or \c{-raw}.
To make an SSH connection, for example: 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: \c login as:
If you have already set up a PuTTY saved session, then instead of 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, allows you to use public-key authentication, specify a user name,
and use most of the other features of PuTTY: 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 Sent username "fred"
\c Authenticating with public key "fred@winbox" \c Authenticating with public key "fred@winbox"
\c Last login: Thu Dec 6 19:25:33 2001 from :0.0 \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 command on the SSH server machine and have it execute automatically
with no prompting: 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 hello, world
\c \c
\c Z:\sysosd> \c C:\>
Or, if you have set up a saved session with all the connection Or, if you have set up a saved session with all the connection
details: details:
\c Z:\sysosd>plink mysession echo hello, world \c C:\>plink mysession echo hello, world
\c hello, world \c hello, world
\c \c
\c Z:\sysosd> \c C:\>
Then you can set up other programs to run this Plink command and 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. talk to it as if it were a process on the server machine.

View File

@ -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 version of PSCP you're using, and gives you a brief summary of how to
use PSCP: use PSCP:
\c Z:\owendadmin>pscp \c C:\>pscp
\c PuTTY Secure Copy client \c PuTTY Secure Copy client
\c Release 0.73 \c Release 0.74
\c Usage: pscp [options] [user@]host:source target \c Usage: pscp [options] [user@]host:source target
\c pscp [options] source [source...] [user@]host:target \c pscp [options] source [source...] [user@]host:target
\c pscp [options] -ls [user@]host:filespec \c pscp [options] -ls [user@]host:filespec

View File

@ -572,8 +572,10 @@ static ssh2_userkey *openssh_pem_read(
strbuf *blob = strbuf_new_nm(); strbuf *blob = strbuf_new_nm();
int privptr = 0, publen; int privptr = 0, publen;
if (!key) if (!key) {
strbuf_free(blob);
return NULL; return NULL;
}
if (key->encrypted) { if (key->encrypted) {
unsigned char keybuf[32]; unsigned char keybuf[32];

View File

@ -1256,6 +1256,7 @@ NORETURN void cleanup_exit(int);
X(BOOL, NONE, compression) \ X(BOOL, NONE, compression) \
X(INT, INT, ssh_kexlist) \ X(INT, INT, ssh_kexlist) \
X(INT, INT, ssh_hklist) \ X(INT, INT, ssh_hklist) \
X(BOOL, NONE, ssh_prefer_known_hostkeys) \
X(INT, NONE, ssh_rekey_time) /* in minutes */ \ X(INT, NONE, ssh_rekey_time) /* in minutes */ \
X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \ X(STR, NONE, ssh_rekey_data) /* string encoding e.g. "100K", "2M", "1G" */ \
X(BOOL, NONE, tryagent) \ X(BOOL, NONE, tryagent) \

View File

@ -640,10 +640,10 @@ static void sesschan_notify_remote_exit(Seat *seat)
sshfwd_send_exit_signal( sshfwd_send_exit_signal(
sess->c, signame, false, ptrlen_from_asciz(sigmsg)); sess->c, signame, false, ptrlen_from_asciz(sigmsg));
sfree(sigmsg);
got_signal = true; got_signal = true;
} }
sfree(sigmsg);
} else { } else {
int signum = pty_backend_exit_signum(sess->backend); int signum = pty_backend_exit_signum(sess->backend);

View File

@ -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, "Cipher", ciphernames, CIPHER_MAX, conf, CONF_ssh_cipherlist);
wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist); wprefs(sesskey, "KEX", kexnames, KEX_MAX, conf, CONF_ssh_kexlist);
wprefs(sesskey, "HostKey", hknames, HK_MAX, conf, CONF_ssh_hklist); 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)); write_setting_i(sesskey, "RekeyTime", conf_get_int(conf, CONF_ssh_rekey_time));
#ifndef NO_GSSAPI #ifndef NO_GSSAPI
write_setting_i(sesskey, "GssapiRekey", conf_get_int(conf, CONF_gssapirekey)); 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", gprefs(sesskey, "HostKey", "ed25519,ecdsa,rsa,dsa,WARN",
hknames, HK_MAX, conf, CONF_ssh_hklist); 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); gppi(sesskey, "RekeyTime", 60, conf, CONF_ssh_rekey_time);
#ifndef NO_GSSAPI #ifndef NO_GSSAPI
gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey); gppi(sesskey, "GssapiRekey", GSS_DEF_REKEY_MINS, conf, CONF_gssapirekey);

View File

@ -298,18 +298,34 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
mp_int *modulus = get_mp_ssh1(pktin); mp_int *modulus = get_mp_ssh1(pktin);
s->authkey = auth_publickey_ssh1( s->authkey = auth_publickey_ssh1(
s->authpolicy, s->username, modulus); 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); mp_free(modulus);
} }
if (!s->authkey) if (!s->authkey &&
!s->ssc->stunt_pretend_to_accept_any_pubkey)
continue; continue;
if (s->authkey->bytes < 32) { if (s->authkey && s->authkey->bytes < 32) {
ppl_logevent("Auth key far too small"); ppl_logevent("Auth key far too small");
continue; continue;
} }
{ if (s->authkey) {
unsigned char *rsabuf = unsigned char *rsabuf =
snewn(s->authkey->bytes, unsigned char); snewn(s->authkey->bytes, unsigned char);
@ -349,6 +365,9 @@ static void ssh1_login_server_process_queue(PacketProtocolLayer *ppl)
return; return;
} }
if (!s->authkey)
continue;
{ {
ptrlen response = get_data(pktin, 16); ptrlen response = get_data(pktin, 16);
ptrlen expected = make_ptrlen( ptrlen expected = make_ptrlen(

View File

@ -12,6 +12,12 @@
#include "sshppl.h" #include "sshppl.h"
#include "sshcr.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 { struct ssh1_login_state {
int crState; int crState;
@ -47,11 +53,11 @@ struct ssh1_login_state {
void *agent_response_to_free; void *agent_response_to_free;
ptrlen agent_response; ptrlen agent_response;
BinarySource asrc[1]; /* response from SSH agent */ 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; bool authed;
RSAKey key; RSAKey key;
mp_int *challenge;
strbuf *agent_comment;
int dlgret; int dlgret;
Filename *keyfile; Filename *keyfile;
RSAKey servkey, hostkey; RSAKey servkey, hostkey;
@ -99,7 +105,6 @@ PacketProtocolLayer *ssh1_login_new(
s->savedhost = dupstr(host); s->savedhost = dupstr(host);
s->savedport = port; s->savedport = port;
s->successor_layer = successor_layer; s->successor_layer = successor_layer;
s->agent_comment = strbuf_new();
return &s->ppl; return &s->ppl;
} }
@ -118,9 +123,15 @@ static void ssh1_login_free(PacketProtocolLayer *ppl)
if (s->publickey_blob) if (s->publickey_blob)
strbuf_free(s->publickey_blob); strbuf_free(s->publickey_blob);
sfree(s->publickey_comment); sfree(s->publickey_comment);
strbuf_free(s->agent_comment);
if (s->cur_prompt) if (s->cur_prompt)
free_prompts(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); sfree(s->agent_response_to_free);
if (s->auth_agent_query) if (s->auth_agent_query)
agent_cancel_query(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 */ get_uint32(s->asrc); /* skip length field */
if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) { if (get_byte(s->asrc) == SSH1_AGENT_RSA_IDENTITIES_ANSWER) {
s->nkeys = toint(get_uint32(s->asrc)); size_t nkeys = get_uint32(s->asrc);
if (s->nkeys < 0) { size_t origpos = s->asrc->pos;
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);
if (keystr.len == s->publickey_blob->len && /*
!memcmp(keystr.ptr, s->publickey_blob->s, * Check that the agent response is well formed.
s->publickey_blob->len)) { */
ppl_logevent("Pageant key #%d matches " for (size_t i = 0; i < nkeys; i++) {
"configured key file", s->keyi); get_rsa_ssh1_pub(s->asrc, NULL, RSA_SSH1_EXPONENT_FIRST);
s->tried_publickey = true; get_string(s->asrc); /* comment */
} else if (get_err(s->asrc)) {
/* Skip non-configured key */ ppl_logevent("Pageant's response was truncated");
continue; 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); * Copy the list of public-key blobs out of the Pageant
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) * response.
!= NULL); */
if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { BinarySource_REWIND_TO(s->asrc, origpos);
ppl_logevent("Key refused"); s->agent_keys_len = nkeys;
continue; 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)) { if (get_err(pktin)) {
mp_free(s->challenge); mp_free(challenge);
ssh_proto_error(s->ppl.ssh, "Server's RSA challenge " ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
"was badly formatted"); "was badly formatted");
return; return;
} }
{ strbuf *agentreq = strbuf_new_for_agent_query();
strbuf *agentreq; put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
const char *ret;
agentreq = strbuf_new_for_agent_query(); rsa_ssh1_public_blob(
put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE); BinarySink_UPCAST(agentreq),
put_uint32(agentreq, mp_get_nbits(s->key.modulus)); &s->agent_keys[s->agent_key_index].key,
put_mp_ssh1(agentreq, s->key.exponent); RSA_SSH1_EXPONENT_FIRST);
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);
ret = s->agent_response.ptr; put_mp_ssh1(agentreq, challenge);
if (ret) { mp_free(challenge);
if (s->agent_response.len >= 5+16 &&
ret[4] == SSH1_AGENT_RSA_RESPONSE) { put_data(agentreq, s->session_id, 16);
ppl_logevent("Sending Pageant's response"); put_uint32(agentreq, 1); /* response format */
pkt = ssh_bpp_new_pktout( ssh1_login_agent_query(s, agentreq);
s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE); strbuf_free(agentreq);
put_data(pkt, ret + 5, 16); crMaybeWaitUntilV(!s->auth_agent_query);
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;
} }
sfree(s->agent_response_to_free);
s->agent_response_to_free = NULL; {
if (s->publickey_blob && !s->tried_publickey) const unsigned char *ret = s->agent_response.ptr;
ppl_logevent("Configured key file not in Pageant"); if (ret) {
} else { if (s->agent_response.len >= 5+16 &&
ppl_logevent("Failed to get reply from Pageant"); 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) if (s->authed)
break; break;

View File

@ -576,9 +576,10 @@ static void ssh2_write_kexinit_lists(
} }
} else if (first_time) { } else if (first_time) {
/* /*
* In the first key exchange, we list all the algorithms * In the first key exchange, we list all the algorithms we're
* we're prepared to cope with, but prefer those algorithms * prepared to cope with, but (if configured to) we prefer
* for which we have a host key for this host. * those algorithms for which we have a host key for this
* host.
* *
* If the host key algorithm is below the warning * If the host key algorithm is below the warning
* threshold, we warn even if we did already have a key * 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++) { for (j = 0; j < lenof(ssh2_hostkey_algs); j++) {
if (ssh2_hostkey_algs[j].id != preferred_hk[i]) if (ssh2_hostkey_algs[j].id != preferred_hk[i])
continue; 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)) { ssh2_hostkey_algs[j].alg->cache_id)) {
alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY], alg = ssh2_kexinit_addalg(kexlists[KEXLIST_HOSTKEY],
ssh2_hostkey_algs[j].alg->ssh_id); ssh2_hostkey_algs[j].alg->ssh_id);

View File

@ -199,7 +199,7 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
goto failure; goto failure;
} }
} else if (ptrlen_eq_string(s->method, "publickey")) { } 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; ptrlen algorithm, blob, signature;
const ssh_keyalg *keyalg; const ssh_keyalg *keyalg;
ssh_key *key; ssh_key *key;
@ -213,7 +213,23 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
algorithm = get_string(pktin); algorithm = get_string(pktin);
blob = 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; goto failure;
keyalg = find_pubkey_alg_len(algorithm); keyalg = find_pubkey_alg_len(algorithm);
@ -223,16 +239,6 @@ static void ssh2_userauth_server_process_queue(PacketProtocolLayer *ppl)
if (!key) if (!key)
goto failure; 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(); sigdata = strbuf_new();
ssh2_userauth_server_add_session_id(s, sigdata); ssh2_userauth_server_add_session_id(s, sigdata);
put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST); put_byte(sigdata, SSH2_MSG_USERAUTH_REQUEST);

View File

@ -18,6 +18,11 @@
#define BANNER_LIMIT 131072 #define BANNER_LIMIT 131072
typedef struct agent_key {
strbuf *blob, *comment;
ptrlen algorithm;
} agent_key;
struct ssh2_userauth_state { struct ssh2_userauth_state {
int crState; int crState;
@ -69,9 +74,9 @@ struct ssh2_userauth_state {
void *agent_response_to_free; void *agent_response_to_free;
ptrlen agent_response; ptrlen agent_response;
BinarySource asrc[1]; /* for reading SSH agent response */ BinarySource asrc[1]; /* for reading SSH agent response */
size_t pkblob_pos_in_agent; size_t agent_keys_len;
int keyi, nkeys; agent_key *agent_keys;
ptrlen pk, alg, comment; size_t agent_key_index, agent_key_limit;
int len; int len;
PktOut *pktout; PktOut *pktout;
bool want_user_input; bool want_user_input;
@ -173,6 +178,13 @@ static void ssh2_userauth_free(PacketProtocolLayer *ppl)
if (s->successor_layer) if (s->successor_layer)
ssh_ppl_free(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); sfree(s->agent_response_to_free);
if (s->auth_agent_query) if (s->auth_agent_query)
agent_cancel_query(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 * Find out about any keys Pageant has (but if there's a public
* key configured, filter out all others). * key configured, filter out all others).
*/ */
s->nkeys = 0;
s->pkblob_pos_in_agent = 0;
if (s->tryagent && agent_exists()) { if (s->tryagent && agent_exists()) {
ppl_logevent("Pageant is running. Requesting keys."); 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 */ get_uint32(s->asrc); /* skip length field */
if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) { if (get_byte(s->asrc) == SSH2_AGENT_IDENTITIES_ANSWER) {
int keyi; size_t nkeys = get_uint32(s->asrc);
size_t origpos = s->asrc->pos;
s->nkeys = toint(get_uint32(s->asrc));
/* /*
* Vet the Pageant response to ensure that the key count * Check that the agent response is well formed.
* and blob lengths make sense.
*/ */
if (s->nkeys < 0) { for (size_t i = 0; i < nkeys; i++) {
ppl_logevent("Pageant response contained a negative" get_string(s->asrc); /* blob */
" key count %d", s->nkeys); get_string(s->asrc); /* comment */
s->nkeys = 0; if (get_err(s->asrc)) {
goto done_agent_query; ppl_logevent("Pageant's response was truncated");
} else { goto done_agent_query;
ppl_logevent("Pageant has %d SSH-2 keys", s->nkeys); }
}
/* See if configured key is in agent. */ /*
for (keyi = 0; keyi < s->nkeys; keyi++) { * Copy the list of public-key blobs out of the Pageant
size_t pos = s->asrc->pos; * response.
ptrlen blob = get_string(s->asrc); */
get_string(s->asrc); /* skip comment */ BinarySource_REWIND_TO(s->asrc, origpos);
if (get_err(s->asrc)) { s->agent_keys_len = nkeys;
ppl_logevent("Pageant response was truncated"); s->agent_keys = snewn(s->agent_keys_len, agent_key);
s->nkeys = 0; for (size_t i = 0; i < nkeys; i++) {
goto done_agent_query; 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 && /* Also, extract the algorithm string from the start
blob.len == s->publickey_blob->len && * of the public-key blob. */
!memcmp(blob.ptr, s->publickey_blob->s, BinarySource src[1];
s->publickey_blob->len)) { BinarySource_BARE_INIT_PL(src, ptrlen_from_strbuf(
ppl_logevent("Pageant key #%d matches " s->agent_keys[i].blob));
"configured key file", keyi); s->agent_keys[i].algorithm = get_string(src);
s->keyi = keyi; }
s->pkblob_pos_in_agent = pos;
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; 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"); 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 { } else {
ppl_logevent("Failed to get reply from Pageant"); 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->tried_pubkey_config = false;
s->kbd_inter_refused = false; s->kbd_inter_refused = false;
/* Reset agent request state. */
s->done_agent = false; 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) { while (1) {
/* /*
@ -686,7 +713,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
} else } else
#endif /* NO_GSSAPI */ #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. * 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; s->ppl.bpp->pls->actx = SSH2_PKTCTX_PUBLICKEY;
ppl_logevent("Trying Pageant key #%d", s->keyi); ppl_logevent("Trying Pageant key #%"SIZEu, s->agent_key_index);
/* 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);
}
/* See if server will accept it */ /* See if server will accept it */
s->pktout = ssh_bpp_new_pktout( s->pktout = ssh_bpp_new_pktout(
@ -713,8 +732,10 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
put_stringz(s->pktout, "publickey"); put_stringz(s->pktout, "publickey");
/* method */ /* method */
put_bool(s->pktout, false); /* no signature included */ put_bool(s->pktout, false); /* no signature included */
put_stringpl(s->pktout, s->alg); put_stringpl(s->pktout,
put_stringpl(s->pktout, s->pk); 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); pq_push(s->ppl.out_pq, s->pktout);
s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET; s->type = AUTH_TYPE_PUBLICKEY_OFFER_QUIET;
@ -727,11 +748,13 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
} else { } else {
strbuf *agentreq, *sigdata; strbuf *agentreq, *sigdata;
ptrlen comment = ptrlen_from_strbuf(
s->agent_keys[s->agent_key_index].comment);
if (seat_verbose(s->ppl.seat)) if (seat_verbose(s->ppl.seat))
ppl_printf("Authenticating with public key " ppl_printf("Authenticating with public key "
"\"%.*s\" from agent\r\n", "\"%.*s\" from agent\r\n",
PTRLEN_PRINTF(s->comment)); PTRLEN_PRINTF(comment));
/* /*
* Server is willing to accept the key. * Server is willing to accept the key.
@ -744,13 +767,16 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
put_stringz(s->pktout, "publickey"); put_stringz(s->pktout, "publickey");
/* method */ /* method */
put_bool(s->pktout, true); /* signature included */ put_bool(s->pktout, true); /* signature included */
put_stringpl(s->pktout, s->alg); put_stringpl(s->pktout,
put_stringpl(s->pktout, s->pk); 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. */ /* Ask agent for signature. */
agentreq = strbuf_new_for_agent_query(); agentreq = strbuf_new_for_agent_query();
put_byte(agentreq, SSH2_AGENTC_SIGN_REQUEST); 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... */ /* Now the data to be signed... */
sigdata = strbuf_new(); sigdata = strbuf_new();
ssh2_userauth_add_session_id(s, sigdata); 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 && if (get_byte(src) == SSH2_AGENT_SIGN_RESPONSE &&
(sigblob = get_string(src), !get_err(src))) { (sigblob = get_string(src), !get_err(src))) {
ppl_logevent("Sending Pageant's response"); ppl_logevent("Sending Pageant's response");
ssh2_userauth_add_sigblob(s, s->pktout, ssh2_userauth_add_sigblob(
s->pk, sigblob); s, s->pktout,
ptrlen_from_strbuf(
s->agent_keys[s->agent_key_index].blob),
sigblob);
pq_push(s->ppl.out_pq, s->pktout); pq_push(s->ppl.out_pq, s->pktout);
s->type = AUTH_TYPE_PUBLICKEY; s->type = AUTH_TYPE_PUBLICKEY;
} else { } else {
@ -794,14 +823,8 @@ static void ssh2_userauth_process_queue(PacketProtocolLayer *ppl)
} }
/* Do we have any keys left to try? */ /* 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->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 && } else if (s->can_pubkey && s->publickey_blob &&
s->privatekey_available && !s->tried_pubkey_config) { 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("modulus length is %d\n", len); */
/* debug("signature length is %d\n", siglen); */ /* 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(); strbuf *substr = strbuf_new();
put_data(substr, sigblob.ptr, sig_prefix_len); put_data(substr, sigblob.ptr, sig_prefix_len);
put_uint32(substr, mod_mp.len); put_uint32(substr, mod_mp.len);

View File

@ -72,8 +72,10 @@ static char *dss_cache_str(ssh_key *key)
struct dss_key *dss = container_of(key, struct dss_key, sshk); struct dss_key *dss = container_of(key, struct dss_key, sshk);
strbuf *sb = strbuf_new(); strbuf *sb = strbuf_new();
if (!dss->p) if (!dss->p) {
strbuf_free(sb);
return NULL; return NULL;
}
append_hex_to_strbuf(sb, dss->p); append_hex_to_strbuf(sb, dss->p);
append_hex_to_strbuf(sb, dss->q); append_hex_to_strbuf(sb, dss->q);

View File

@ -17,6 +17,8 @@ struct SshServerConfig {
unsigned long ssh1_cipher_mask; unsigned long ssh1_cipher_mask;
bool ssh1_allow_compression; bool ssh1_allow_compression;
bool bare_connection; bool bare_connection;
bool stunt_pretend_to_accept_any_pubkey;
}; };
Plug *ssh_server_plug( Plug *ssh_server_plug(

View File

@ -2072,7 +2072,9 @@ static void swap_screen(Terminal *term, int which,
ttr = term->alt_screen; ttr = term->alt_screen;
term->alt_screen = term->screen; term->alt_screen = term->screen;
term->screen = ttr; 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; t = term->curs.x;
if (!reset && !keep_cur_pos) if (!reset && !keep_cur_pos)
term->curs.x = term->alt_x; term->curs.x = term->alt_x;

View File

@ -315,22 +315,25 @@ bool do_cmdline(int argc, char **argv, bool do_everything, Conf *conf)
char *val; char *val;
/* /*
* Macros to make argument handling easier. Note that because * Macros to make argument handling easier.
* they need to call `continue', they cannot be contained in *
* the usual do {...} while (0) wrapper to make them * Note that because they need to call `continue', they cannot be
* syntactically single statements; hence it is not legal to * contained in the usual do {...} while (0) wrapper to make them
* use one of these macros as an unbraced statement between * syntactically single statements. I use the alternative if (1)
* `if' and `else'. * {...} else ((void)0).
*/ */
#define EXPECTS_ARG { \ #define EXPECTS_ARG if (1) { \
if (--argc <= 0) { \ if (--argc <= 0) { \
err = true; \ err = true; \
fprintf(stderr, "%s: %s expects an argument\n", appname, p); \ fprintf(stderr, "%s: %s expects an argument\n", appname, p); \
continue; \ continue; \
} else \ } else \
val = *++argv; \ val = *++argv; \
} } else ((void)0)
#define SECOND_PASS_ONLY do { if (!do_everything) continue; } while (0) #define SECOND_PASS_ONLY if (1) { \
if (!do_everything) \
continue; \
} else ((void)0)
while (--argc > 0) { while (--argc > 0) {
const char *p = *++argv; const char *p = *++argv;

View File

@ -29,6 +29,7 @@ Socket *platform_make_agent_socket(
if ((errw = make_dir_and_check_ours(socketdir)) != NULL) { if ((errw = make_dir_and_check_ours(socketdir)) != NULL) {
*error = dupprintf("%s: %s\n", socketdir, errw); *error = dupprintf("%s: %s\n", socketdir, errw);
sfree(errw); sfree(errw);
sfree(socketdir);
return NULL; return NULL;
} }

View File

@ -206,6 +206,8 @@ static void setup_utmp(char *ttyname, char *location)
struct timeval tv; struct timeval tv;
pw = getpwuid(getuid()); pw = getpwuid(getuid());
if (!pw)
return; /* can't stamp utmp if we don't have a username */
memset(&utmp_entry, 0, sizeof(utmp_entry)); memset(&utmp_entry, 0, sizeof(utmp_entry));
utmp_entry.ut_type = USER_PROCESS; utmp_entry.ut_type = USER_PROCESS;
utmp_entry.ut_pid = getpid(); utmp_entry.ut_pid = getpid();

View File

@ -775,6 +775,8 @@ int main(int argc, char **argv)
filename_free(logfile); filename_free(logfile);
conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW); conf_set_int(conf, CONF_logtype, LGTYP_SSHRAW);
conf_set_int(conf, CONF_logxfovr, LGXF_OVR); 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 { } else {
fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg); fprintf(stderr, "%s: unrecognised option '%s'\n", appname, arg);
exit(1); exit(1);

View File

@ -5225,7 +5225,7 @@ static void wintw_clip_write(
(int)udata[uindex]); (int)udata[uindex]);
alen = 1; strcpy(after, "}"); alen = 1; strcpy(after, "}");
} else { } else {
blen = sprintf(before, "\\u%d", udata[uindex]); blen = sprintf(before, "\\u%d", (int)udata[uindex]);
alen = 0; after[0] = '\0'; alen = 0; after[0] = '\0';
} }
} }

View File

@ -106,6 +106,7 @@
#define WINHELP_CTX_ssh_share "config-ssh-sharing" #define WINHELP_CTX_ssh_share "config-ssh-sharing"
#define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order" #define WINHELP_CTX_ssh_kexlist "config-ssh-kex-order"
#define WINHELP_CTX_ssh_hklist "config-ssh-hostkey-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_gssapi_kex_delegation "config-ssh-kex-gssapi-delegation"
#define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey" #define WINHELP_CTX_ssh_kex_repeat "config-ssh-kex-rekey"
#define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys" #define WINHELP_CTX_ssh_kex_manual_hostkeys "config-ssh-kex-manual-hostkeys"