Move most of ssh.c out into separate source files.
I've tried to separate out as many individually coherent changes from
this work as I could into their own commits, but here's where I run
out and have to commit the rest of this major refactoring as a
big-bang change.
Most of ssh.c is now no longer in ssh.c: all five of the main
coroutines that handle layers of the SSH-1 and SSH-2 protocols now
each have their own source file to live in, and a lot of the
supporting functions have moved into the appropriate one of those too.
The new abstraction is a vtable called 'PacketProtocolLayer', which
has an input and output packet queue. Each layer's main coroutine is
invoked from the method ssh_ppl_process_queue(), which is usually
(though not exclusively) triggered automatically when things are
pushed on the input queue. In SSH-2, the base layer is the transport
protocol, and it contains a pair of subsidiary queues by which it
passes some of its packets to the higher SSH-2 layers - first userauth
and then connection, which are peers at the same level, with the
former abdicating in favour of the latter at the appropriate moment.
SSH-1 is simpler: the whole login phase of the protocol (crypto setup
and authentication) is all in one module, and since SSH-1 has no
repeat key exchange, that setup layer abdicates in favour of the
connection phase when it's done.
ssh.c itself is now about a tenth of its old size (which all by itself
is cause for celebration!). Its main job is to set up all the layers,
hook them up to each other and to the BPP, and to funnel data back and
forth between that collection of modules and external things such as
the network and the terminal. Once it's set up a collection of packet
protocol layers, it communicates with them partly by calling methods
of the base layer (and if that's ssh2transport then it will delegate
some functionality to the corresponding methods of its higher layer),
and partly by talking directly to the connection layer no matter where
it is in the stack by means of the separate ConnectionLayer vtable
which I introduced in commit 8001dd4cb, and to which I've now added
quite a few extra methods replacing services that used to be internal
function calls within ssh.c.
(One effect of this is that the SSH-1 and SSH-2 channel storage is now
no longer shared - there are distinct struct types ssh1_channel and
ssh2_channel. That means a bit more code duplication, but on the plus
side, a lot fewer confusing conditionals in the middle of half-shared
functions, and less risk of a piece of SSH-1 escaping into SSH-2 or
vice versa, which I remember has happened at least once in the past.)
The bulk of this commit introduces the five new source files, their
common header sshppl.h and some shared supporting routines in
sshcommon.c, and rewrites nearly all of ssh.c itself. But it also
includes a couple of other changes that I couldn't separate easily
enough:
Firstly, there's a new handling for socket EOF, in which ssh.c sets an
'input_eof' flag in the BPP, and that responds by checking a flag that
tells it whether to report the EOF as an error or not. (This is the
main reason for those new BPP_READ / BPP_WAITFOR macros - they can
check the EOF flag every time the coroutine is resumed.)
Secondly, the error reporting itself is changed around again. I'd
expected to put some data fields in the public PacketProtocolLayer
structure that it could set to report errors in the same way as the
BPPs have been doing, but in the end, I decided propagating all those
data fields around was a pain and that even the BPPs shouldn't have
been doing it that way. So I've reverted to a system where everything
calls back to functions in ssh.c itself to report any connection-
ending condition. But there's a new family of those functions,
categorising the possible such conditions by semantics, and each one
has a different set of detailed effects (e.g. how rudely to close the
network connection, what exit status should be passed back to the
whole application, whether to send a disconnect message and/or display
a GUI error box).
I don't expect this to be immediately perfect: of course, the code has
been through a big upheaval, new bugs are expected, and I haven't been
able to do a full job of testing (e.g. I haven't tested every auth or
kex method). But I've checked that it _basically_ works - both SSH
protocols, all the different kinds of forwarding channel, more than
one auth method, Windows and Linux, connection sharing - and I think
it's now at the point where the easiest way to find further bugs is to
let it out into the wild and see what users can spot.
2018-09-24 18:28:16 +01:00
|
|
|
/*
|
|
|
|
* Packet protocol layer for the SSH-1 login phase (combining what
|
|
|
|
* SSH-2 would think of as key exchange and user authentication).
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <assert.h>
|
|
|
|
|
|
|
|
#include "putty.h"
|
|
|
|
#include "ssh.h"
|
|
|
|
#include "sshbpp.h"
|
|
|
|
#include "sshppl.h"
|
|
|
|
#include "sshcr.h"
|
|
|
|
|
|
|
|
struct ssh1_login_state {
|
|
|
|
int crState;
|
|
|
|
|
|
|
|
PacketProtocolLayer *successor_layer;
|
|
|
|
|
|
|
|
Conf *conf;
|
|
|
|
|
|
|
|
char *savedhost;
|
|
|
|
int savedport;
|
|
|
|
int try_agent_auth;
|
|
|
|
|
|
|
|
int remote_protoflags;
|
|
|
|
int local_protoflags;
|
|
|
|
unsigned char session_key[32];
|
|
|
|
char *username;
|
|
|
|
agent_pending_query *auth_agent_query;
|
|
|
|
|
|
|
|
int len;
|
|
|
|
unsigned char *rsabuf;
|
|
|
|
unsigned long supported_ciphers_mask, supported_auths_mask;
|
|
|
|
int tried_publickey, tried_agent;
|
|
|
|
int tis_auth_refused, ccard_auth_refused;
|
|
|
|
unsigned char cookie[8];
|
|
|
|
unsigned char session_id[16];
|
|
|
|
int cipher_type;
|
|
|
|
strbuf *publickey_blob;
|
|
|
|
char *publickey_comment;
|
|
|
|
int privatekey_available, privatekey_encrypted;
|
|
|
|
prompts_t *cur_prompt;
|
|
|
|
int userpass_ret;
|
|
|
|
char c;
|
|
|
|
int pwpkt_type;
|
|
|
|
void *agent_response_to_free;
|
|
|
|
ptrlen agent_response;
|
|
|
|
BinarySource asrc[1]; /* response from SSH agent */
|
|
|
|
int keyi, nkeys;
|
|
|
|
int authed;
|
|
|
|
struct RSAKey key;
|
|
|
|
Bignum challenge;
|
|
|
|
ptrlen comment;
|
|
|
|
int dlgret;
|
|
|
|
Filename *keyfile;
|
|
|
|
struct RSAKey servkey, hostkey;
|
|
|
|
int want_user_input;
|
|
|
|
|
|
|
|
PacketProtocolLayer ppl;
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ssh1_login_free(PacketProtocolLayer *);
|
|
|
|
static void ssh1_login_process_queue(PacketProtocolLayer *);
|
|
|
|
static void ssh1_login_dialog_callback(void *, int);
|
|
|
|
static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
|
|
|
|
SessionSpecialCode code, int arg);
|
|
|
|
static int ssh1_login_want_user_input(PacketProtocolLayer *ppl);
|
|
|
|
static void ssh1_login_got_user_input(PacketProtocolLayer *ppl);
|
|
|
|
static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf);
|
|
|
|
|
|
|
|
static const struct PacketProtocolLayerVtable ssh1_login_vtable = {
|
|
|
|
ssh1_login_free,
|
|
|
|
ssh1_login_process_queue,
|
|
|
|
ssh1_common_get_specials,
|
|
|
|
ssh1_login_special_cmd,
|
|
|
|
ssh1_login_want_user_input,
|
|
|
|
ssh1_login_got_user_input,
|
|
|
|
ssh1_login_reconfigure,
|
|
|
|
NULL /* no layer names in SSH-1 */,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req);
|
|
|
|
static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen);
|
|
|
|
|
|
|
|
PacketProtocolLayer *ssh1_login_new(
|
|
|
|
Conf *conf, const char *host, int port,
|
|
|
|
PacketProtocolLayer *successor_layer)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s = snew(struct ssh1_login_state);
|
|
|
|
memset(s, 0, sizeof(*s));
|
|
|
|
s->ppl.vt = &ssh1_login_vtable;
|
|
|
|
|
|
|
|
s->conf = conf_copy(conf);
|
|
|
|
s->savedhost = dupstr(host);
|
|
|
|
s->savedport = port;
|
|
|
|
s->successor_layer = successor_layer;
|
|
|
|
return &s->ppl;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_free(PacketProtocolLayer *ppl)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
|
|
|
|
if (s->successor_layer)
|
|
|
|
ssh_ppl_free(s->successor_layer);
|
|
|
|
|
|
|
|
conf_free(s->conf);
|
|
|
|
sfree(s->savedhost);
|
|
|
|
sfree(s->rsabuf);
|
|
|
|
sfree(s->username);
|
|
|
|
if (s->publickey_blob)
|
|
|
|
strbuf_free(s->publickey_blob);
|
|
|
|
sfree(s->publickey_comment);
|
|
|
|
if (s->cur_prompt)
|
|
|
|
free_prompts(s->cur_prompt);
|
|
|
|
sfree(s->agent_response_to_free);
|
|
|
|
if (s->auth_agent_query)
|
|
|
|
agent_cancel_query(s->auth_agent_query);
|
|
|
|
sfree(s);
|
|
|
|
}
|
|
|
|
|
|
|
|
int ssh1_common_filter_queue(PacketProtocolLayer *ppl)
|
|
|
|
{
|
|
|
|
PktIn *pktin;
|
|
|
|
ptrlen msg;
|
|
|
|
|
|
|
|
while ((pktin = pq_peek(ppl->in_pq)) != NULL) {
|
|
|
|
switch (pktin->type) {
|
|
|
|
case SSH1_MSG_DISCONNECT:
|
|
|
|
msg = get_string(pktin);
|
|
|
|
ssh_remote_error(ppl->ssh,
|
|
|
|
"Server sent disconnect message:\n\"%.*s\"",
|
|
|
|
PTRLEN_PRINTF(msg));
|
|
|
|
return TRUE; /* indicate that we've been freed */
|
|
|
|
|
|
|
|
case SSH1_MSG_DEBUG:
|
|
|
|
msg = get_string(pktin);
|
|
|
|
ppl_logevent(("Remote debug message: %.*s", PTRLEN_PRINTF(msg)));
|
|
|
|
pq_pop(ppl->in_pq);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SSH1_MSG_IGNORE:
|
|
|
|
/* Do nothing, because we're ignoring it! Duhh. */
|
|
|
|
pq_pop(ppl->in_pq);
|
|
|
|
break;
|
|
|
|
|
|
|
|
default:
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ssh1_login_filter_queue(struct ssh1_login_state *s)
|
|
|
|
{
|
|
|
|
return ssh1_common_filter_queue(&s->ppl);
|
|
|
|
}
|
|
|
|
|
|
|
|
static PktIn *ssh1_login_pop(struct ssh1_login_state *s)
|
|
|
|
{
|
|
|
|
if (ssh1_login_filter_queue(s))
|
|
|
|
return NULL;
|
|
|
|
return pq_pop(s->ppl.in_pq);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_process_queue(PacketProtocolLayer *ppl)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s = FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
PktIn *pktin;
|
|
|
|
PktOut *pkt;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
/* Filter centrally handled messages off the front of the queue on
|
|
|
|
* every entry to this coroutine, no matter where we're resuming
|
|
|
|
* from, even if we're _not_ looping on pq_pop. That way we can
|
|
|
|
* still proactively handle those messages even if we're waiting
|
|
|
|
* for a user response. */
|
|
|
|
if (ssh1_login_filter_queue(s))
|
|
|
|
return;
|
|
|
|
|
|
|
|
crBegin(s->crState);
|
|
|
|
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
|
|
|
|
if (pktin->type != SSH1_SMSG_PUBLIC_KEY) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Public key packet not received");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ppl_logevent(("Received public keys"));
|
|
|
|
|
|
|
|
{
|
|
|
|
ptrlen pl = get_data(pktin, 8);
|
|
|
|
memcpy(s->cookie, pl.ptr, pl.len);
|
|
|
|
}
|
|
|
|
|
|
|
|
get_rsa_ssh1_pub(pktin, &s->servkey, RSA_SSH1_EXPONENT_FIRST);
|
|
|
|
get_rsa_ssh1_pub(pktin, &s->hostkey, RSA_SSH1_EXPONENT_FIRST);
|
|
|
|
|
|
|
|
s->hostkey.comment = NULL; /* avoid confusing rsa_ssh1_fingerprint */
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Log the host key fingerprint.
|
|
|
|
*/
|
|
|
|
if (!get_err(pktin)) {
|
|
|
|
char *fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
|
|
|
|
ppl_logevent(("Host key fingerprint is:"));
|
|
|
|
ppl_logevent((" %s", fingerprint));
|
|
|
|
sfree(fingerprint);
|
|
|
|
}
|
|
|
|
|
|
|
|
s->remote_protoflags = get_uint32(pktin);
|
|
|
|
s->supported_ciphers_mask = get_uint32(pktin);
|
|
|
|
s->supported_auths_mask = get_uint32(pktin);
|
|
|
|
|
|
|
|
if (get_err(pktin)) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Bad SSH-1 public key packet");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((s->ppl.remote_bugs & BUG_CHOKES_ON_RSA))
|
|
|
|
s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
|
|
|
|
|
|
|
|
s->local_protoflags =
|
|
|
|
s->remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
|
|
|
|
s->local_protoflags |= SSH1_PROTOFLAG_SCREEN_NUMBER;
|
|
|
|
|
|
|
|
{
|
|
|
|
struct MD5Context md5c;
|
|
|
|
|
|
|
|
MD5Init(&md5c);
|
|
|
|
for (i = (bignum_bitcount(s->hostkey.modulus) + 7) / 8; i-- ;)
|
|
|
|
put_byte(&md5c, bignum_byte(s->hostkey.modulus, i));
|
|
|
|
for (i = (bignum_bitcount(s->servkey.modulus) + 7) / 8; i-- ;)
|
|
|
|
put_byte(&md5c, bignum_byte(s->servkey.modulus, i));
|
|
|
|
put_data(&md5c, s->cookie, 8);
|
|
|
|
MD5Final(s->session_id, &md5c);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++)
|
|
|
|
s->session_key[i] = random_byte();
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify that the `bits' and `bytes' parameters match.
|
|
|
|
*/
|
|
|
|
if (s->hostkey.bits > s->hostkey.bytes * 8 ||
|
|
|
|
s->servkey.bits > s->servkey.bytes * 8) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "SSH-1 public keys were badly formatted");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
s->len = (s->hostkey.bytes > s->servkey.bytes ?
|
|
|
|
s->hostkey.bytes : s->servkey.bytes);
|
|
|
|
|
|
|
|
s->rsabuf = snewn(s->len, unsigned char);
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Verify the host key.
|
|
|
|
*/
|
|
|
|
{
|
|
|
|
/*
|
|
|
|
* First format the key into a string.
|
|
|
|
*/
|
|
|
|
int len = rsastr_len(&s->hostkey);
|
|
|
|
char *fingerprint;
|
|
|
|
char *keystr = snewn(len, char);
|
|
|
|
rsastr_fmt(keystr, &s->hostkey);
|
|
|
|
fingerprint = rsa_ssh1_fingerprint(&s->hostkey);
|
|
|
|
|
|
|
|
/* First check against manually configured host keys. */
|
|
|
|
s->dlgret = verify_ssh_manual_host_key(s->conf, fingerprint, NULL);
|
|
|
|
sfree(fingerprint);
|
|
|
|
if (s->dlgret == 0) { /* did not match */
|
|
|
|
sfree(keystr);
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Host key did not appear in manually "
|
|
|
|
"configured list");
|
|
|
|
return;
|
|
|
|
} else if (s->dlgret < 0) { /* none configured; use standard handling */
|
|
|
|
s->dlgret = verify_ssh_host_key(
|
|
|
|
s->ppl.frontend, s->savedhost, s->savedport,
|
|
|
|
"rsa", keystr, fingerprint, ssh1_login_dialog_callback, s);
|
|
|
|
sfree(keystr);
|
|
|
|
#ifdef FUZZING
|
|
|
|
s->dlgret = 1;
|
|
|
|
#endif
|
|
|
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
|
|
|
|
|
|
if (s->dlgret == 0) {
|
|
|
|
ssh_user_close(s->ppl.ssh,
|
|
|
|
"User aborted at host key verification");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sfree(keystr);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
s->rsabuf[i] = s->session_key[i];
|
|
|
|
if (i < 16)
|
|
|
|
s->rsabuf[i] ^= s->session_id[i];
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
struct RSAKey *smaller = (s->hostkey.bytes > s->servkey.bytes ?
|
|
|
|
&s->servkey : &s->hostkey);
|
|
|
|
struct RSAKey *larger = (s->hostkey.bytes > s->servkey.bytes ?
|
|
|
|
&s->hostkey : &s->servkey);
|
|
|
|
|
|
|
|
if (!rsa_ssh1_encrypt(s->rsabuf, 32, smaller) ||
|
|
|
|
!rsa_ssh1_encrypt(s->rsabuf, smaller->bytes, larger)) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "SSH-1 public key encryptions failed "
|
|
|
|
"due to bad formatting");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ppl_logevent(("Encrypted session key"));
|
|
|
|
|
|
|
|
{
|
|
|
|
int cipher_chosen = 0, warn = 0;
|
|
|
|
const char *cipher_string = NULL;
|
|
|
|
int i;
|
|
|
|
for (i = 0; !cipher_chosen && i < CIPHER_MAX; i++) {
|
|
|
|
int next_cipher = conf_get_int_int(
|
|
|
|
s->conf, CONF_ssh_cipherlist, i);
|
|
|
|
if (next_cipher == CIPHER_WARN) {
|
|
|
|
/* If/when we choose a cipher, warn about it */
|
|
|
|
warn = 1;
|
|
|
|
} else if (next_cipher == CIPHER_AES) {
|
|
|
|
/* XXX Probably don't need to mention this. */
|
|
|
|
ppl_logevent(("AES not supported in SSH-1, skipping"));
|
|
|
|
} else {
|
|
|
|
switch (next_cipher) {
|
|
|
|
case CIPHER_3DES: s->cipher_type = SSH_CIPHER_3DES;
|
|
|
|
cipher_string = "3DES"; break;
|
|
|
|
case CIPHER_BLOWFISH: s->cipher_type = SSH_CIPHER_BLOWFISH;
|
|
|
|
cipher_string = "Blowfish"; break;
|
|
|
|
case CIPHER_DES: s->cipher_type = SSH_CIPHER_DES;
|
|
|
|
cipher_string = "single-DES"; break;
|
|
|
|
}
|
|
|
|
if (s->supported_ciphers_mask & (1 << s->cipher_type))
|
|
|
|
cipher_chosen = 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (!cipher_chosen) {
|
|
|
|
if ((s->supported_ciphers_mask & (1 << SSH_CIPHER_3DES)) == 0) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Server violates SSH-1 protocol "
|
|
|
|
"by not supporting 3DES encryption");
|
|
|
|
} else {
|
|
|
|
/* shouldn't happen */
|
|
|
|
ssh_sw_abort(s->ppl.ssh, "No supported ciphers found");
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Warn about chosen cipher if necessary. */
|
|
|
|
if (warn) {
|
|
|
|
s->dlgret = askalg(s->ppl.frontend, "cipher", cipher_string,
|
|
|
|
ssh1_login_dialog_callback, s);
|
|
|
|
crMaybeWaitUntilV(s->dlgret >= 0);
|
|
|
|
if (s->dlgret == 0) {
|
|
|
|
ssh_user_close(s->ppl.ssh, "User aborted at cipher warning");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (s->cipher_type) {
|
|
|
|
case SSH_CIPHER_3DES:
|
|
|
|
ppl_logevent(("Using 3DES encryption"));
|
|
|
|
break;
|
|
|
|
case SSH_CIPHER_DES:
|
|
|
|
ppl_logevent(("Using single-DES encryption"));
|
|
|
|
break;
|
|
|
|
case SSH_CIPHER_BLOWFISH:
|
|
|
|
ppl_logevent(("Using Blowfish encryption"));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_SESSION_KEY);
|
|
|
|
put_byte(pkt, s->cipher_type);
|
|
|
|
put_data(pkt, s->cookie, 8);
|
|
|
|
put_uint16(pkt, s->len * 8);
|
|
|
|
put_data(pkt, s->rsabuf, s->len);
|
|
|
|
put_uint32(pkt, s->local_protoflags);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
|
|
|
|
ppl_logevent(("Trying to enable encryption..."));
|
|
|
|
|
|
|
|
sfree(s->rsabuf);
|
|
|
|
s->rsabuf = NULL;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Force the BPP to synchronously marshal all packets up to and
|
|
|
|
* including the SESSION_KEY into wire format, before we turn on
|
|
|
|
* crypto.
|
|
|
|
*/
|
|
|
|
ssh_bpp_handle_output(s->ppl.bpp);
|
|
|
|
|
|
|
|
{
|
|
|
|
const struct ssh1_cipheralg *cipher =
|
|
|
|
(s->cipher_type == SSH_CIPHER_BLOWFISH ? &ssh1_blowfish :
|
|
|
|
s->cipher_type == SSH_CIPHER_DES ? &ssh1_des : &ssh1_3des);
|
|
|
|
ssh1_bpp_new_cipher(s->ppl.bpp, cipher, s->session_key);
|
|
|
|
ppl_logevent(("Initialised %s encryption", cipher->text_name));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s->servkey.modulus) {
|
|
|
|
sfree(s->servkey.modulus);
|
|
|
|
s->servkey.modulus = NULL;
|
|
|
|
}
|
|
|
|
if (s->servkey.exponent) {
|
|
|
|
sfree(s->servkey.exponent);
|
|
|
|
s->servkey.exponent = NULL;
|
|
|
|
}
|
|
|
|
if (s->hostkey.modulus) {
|
|
|
|
sfree(s->hostkey.modulus);
|
|
|
|
s->hostkey.modulus = NULL;
|
|
|
|
}
|
|
|
|
if (s->hostkey.exponent) {
|
|
|
|
sfree(s->hostkey.exponent);
|
|
|
|
s->hostkey.exponent = NULL;
|
|
|
|
}
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
|
|
|
|
if (pktin->type != SSH1_SMSG_SUCCESS) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Encryption not successfully enabled");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ppl_logevent(("Successfully started encryption"));
|
|
|
|
|
|
|
|
if ((s->username = get_remote_username(s->conf)) == NULL) {
|
|
|
|
s->cur_prompt = new_prompts(s->ppl.frontend);
|
|
|
|
s->cur_prompt->to_server = TRUE;
|
|
|
|
s->cur_prompt->name = dupstr("SSH login name");
|
|
|
|
add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
|
|
|
|
s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
|
|
|
|
while (1) {
|
|
|
|
while (s->userpass_ret < 0 &&
|
|
|
|
bufchain_size(s->ppl.user_input) > 0)
|
|
|
|
s->userpass_ret = get_userpass_input(
|
|
|
|
s->cur_prompt, s->ppl.user_input);
|
|
|
|
|
|
|
|
if (s->userpass_ret >= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
s->want_user_input = TRUE;
|
|
|
|
crReturnV;
|
|
|
|
s->want_user_input = FALSE;
|
|
|
|
}
|
|
|
|
if (!s->userpass_ret) {
|
|
|
|
/*
|
|
|
|
* Failed to get a username. Terminate.
|
|
|
|
*/
|
|
|
|
ssh_user_close(s->ppl.ssh, "No username provided");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
s->username = dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
|
free_prompts(s->cur_prompt);
|
|
|
|
s->cur_prompt = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_USER);
|
|
|
|
put_stringz(pkt, s->username);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
|
|
|
|
ppl_logevent(("Sent username \"%s\"", s->username));
|
|
|
|
if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE))
|
|
|
|
ppl_printf(("Sent username \"%s\"\r\n", s->username));
|
|
|
|
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
|
|
|
|
if (!(s->supported_auths_mask & (1 << SSH1_AUTH_RSA))) {
|
|
|
|
/* We must not attempt PK auth. Pretend we've already tried it. */
|
|
|
|
s->tried_publickey = s->tried_agent = TRUE;
|
|
|
|
} else {
|
|
|
|
s->tried_publickey = s->tried_agent = FALSE;
|
|
|
|
}
|
|
|
|
s->tis_auth_refused = s->ccard_auth_refused = FALSE;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Load the public half of any configured keyfile for later use.
|
|
|
|
*/
|
|
|
|
s->keyfile = conf_get_filename(s->conf, CONF_keyfile);
|
|
|
|
if (!filename_is_null(s->keyfile)) {
|
|
|
|
int keytype;
|
|
|
|
ppl_logevent(("Reading key file \"%.150s\"",
|
|
|
|
filename_to_str(s->keyfile)));
|
|
|
|
keytype = key_type(s->keyfile);
|
|
|
|
if (keytype == SSH_KEYTYPE_SSH1 ||
|
|
|
|
keytype == SSH_KEYTYPE_SSH1_PUBLIC) {
|
|
|
|
const char *error;
|
|
|
|
s->publickey_blob = strbuf_new();
|
|
|
|
if (rsa_ssh1_loadpub(s->keyfile,
|
|
|
|
BinarySink_UPCAST(s->publickey_blob),
|
|
|
|
&s->publickey_comment, &error)) {
|
|
|
|
s->privatekey_available = (keytype == SSH_KEYTYPE_SSH1);
|
|
|
|
if (!s->privatekey_available)
|
|
|
|
ppl_logevent(("Key file contains public key only"));
|
|
|
|
s->privatekey_encrypted = rsa_ssh1_encrypted(s->keyfile, NULL);
|
|
|
|
} else {
|
|
|
|
ppl_logevent(("Unable to load key (%s)", error));
|
|
|
|
ppl_printf(("Unable to load key file \"%s\" (%s)\r\n",
|
|
|
|
filename_to_str(s->keyfile), error));
|
|
|
|
|
|
|
|
strbuf_free(s->publickey_blob);
|
|
|
|
s->publickey_blob = NULL;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
ppl_logevent(("Unable to use this key file (%s)",
|
|
|
|
key_type_to_str(keytype)));
|
|
|
|
ppl_printf(("Unable to use key file \"%s\" (%s)\r\n",
|
|
|
|
filename_to_str(s->keyfile),
|
|
|
|
key_type_to_str(keytype)));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Check whether we're configured to try Pageant, and also whether
|
|
|
|
* it's available. */
|
|
|
|
s->try_agent_auth = (conf_get_int(s->conf, CONF_tryagent) &&
|
|
|
|
agent_exists());
|
|
|
|
|
|
|
|
while (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
s->pwpkt_type = SSH1_CMSG_AUTH_PASSWORD;
|
|
|
|
|
|
|
|
if (s->try_agent_auth && !s->tried_agent) {
|
|
|
|
/*
|
|
|
|
* Attempt RSA authentication using Pageant.
|
|
|
|
*/
|
|
|
|
s->authed = FALSE;
|
|
|
|
s->tried_agent = 1;
|
|
|
|
ppl_logevent(("Pageant is running. Requesting keys."));
|
|
|
|
|
|
|
|
/* Request the keys held by the agent. */
|
|
|
|
{
|
|
|
|
strbuf *request = strbuf_new_for_agent_query();
|
|
|
|
put_byte(request, SSH1_AGENTC_REQUEST_RSA_IDENTITIES);
|
|
|
|
ssh1_login_agent_query(s, request);
|
|
|
|
strbuf_free(request);
|
|
|
|
crMaybeWaitUntilV(!s->auth_agent_query);
|
|
|
|
}
|
|
|
|
BinarySource_BARE_INIT(
|
|
|
|
s->asrc, s->agent_response.ptr, s->agent_response.len);
|
|
|
|
|
|
|
|
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;
|
|
|
|
s->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,
|
|
|
|
s->publickey_blob->len)) {
|
|
|
|
ppl_logevent(("Pageant key #%d matches "
|
|
|
|
"configured key file", s->keyi));
|
|
|
|
s->tried_publickey = 1;
|
|
|
|
} else
|
|
|
|
/* Skip non-configured key */
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
ppl_logevent(("Received RSA challenge"));
|
|
|
|
s->challenge = get_mp_ssh1(pktin);
|
|
|
|
if (get_err(pktin)) {
|
|
|
|
freebn(s->challenge);
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
|
|
|
|
"was badly formatted");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
strbuf *agentreq;
|
|
|
|
const char *ret;
|
|
|
|
|
|
|
|
agentreq = strbuf_new_for_agent_query();
|
|
|
|
put_byte(agentreq, SSH1_AGENTC_RSA_CHALLENGE);
|
|
|
|
put_uint32(agentreq, bignum_bitcount(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);
|
|
|
|
|
|
|
|
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);
|
|
|
|
sfree((char *)ret);
|
|
|
|
crMaybeWaitUntilV(
|
|
|
|
(pktin = ssh1_login_pop(s))
|
|
|
|
!= NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_SUCCESS) {
|
|
|
|
ppl_logevent(("Pageant's response "
|
|
|
|
"accepted"));
|
|
|
|
if (flags & FLAG_VERBOSE) {
|
|
|
|
ppl_printf(("Authenticated using RSA "
|
|
|
|
"key \"%.*s\" from "
|
|
|
|
"agent\r\n", PTRLEN_PRINTF(
|
|
|
|
s->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"));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
freebn(s->key.exponent);
|
|
|
|
freebn(s->key.modulus);
|
|
|
|
freebn(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)
|
|
|
|
ppl_logevent(("Configured key file not in Pageant"));
|
|
|
|
} else {
|
|
|
|
ppl_logevent(("Failed to get reply from Pageant"));
|
|
|
|
}
|
|
|
|
if (s->authed)
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (s->publickey_blob && s->privatekey_available &&
|
|
|
|
!s->tried_publickey) {
|
|
|
|
/*
|
|
|
|
* Try public key authentication with the specified
|
|
|
|
* key file.
|
|
|
|
*/
|
|
|
|
int got_passphrase; /* need not be kept over crReturn */
|
|
|
|
if (flags & FLAG_VERBOSE)
|
|
|
|
ppl_printf(("Trying public key authentication.\r\n"));
|
|
|
|
ppl_logevent(("Trying public key \"%s\"",
|
|
|
|
filename_to_str(s->keyfile)));
|
|
|
|
s->tried_publickey = 1;
|
|
|
|
got_passphrase = FALSE;
|
|
|
|
while (!got_passphrase) {
|
|
|
|
/*
|
|
|
|
* Get a passphrase, if necessary.
|
|
|
|
*/
|
|
|
|
int retd;
|
|
|
|
char *passphrase = NULL; /* only written after crReturn */
|
|
|
|
const char *error;
|
|
|
|
if (!s->privatekey_encrypted) {
|
|
|
|
if (flags & FLAG_VERBOSE)
|
|
|
|
ppl_printf(("No passphrase required.\r\n"));
|
|
|
|
passphrase = NULL;
|
|
|
|
} else {
|
|
|
|
s->cur_prompt = new_prompts(s->ppl.frontend);
|
|
|
|
s->cur_prompt->to_server = FALSE;
|
|
|
|
s->cur_prompt->name = dupstr("SSH key passphrase");
|
|
|
|
add_prompt(s->cur_prompt,
|
|
|
|
dupprintf("Passphrase for key \"%.100s\": ",
|
|
|
|
s->publickey_comment), FALSE);
|
|
|
|
s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
|
|
|
|
while (1) {
|
|
|
|
while (s->userpass_ret < 0 &&
|
|
|
|
bufchain_size(s->ppl.user_input) > 0)
|
|
|
|
s->userpass_ret = get_userpass_input(
|
|
|
|
s->cur_prompt, s->ppl.user_input);
|
|
|
|
|
|
|
|
if (s->userpass_ret >= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
s->want_user_input = TRUE;
|
|
|
|
crReturnV;
|
|
|
|
s->want_user_input = FALSE;
|
|
|
|
}
|
|
|
|
if (!s->userpass_ret) {
|
|
|
|
/* Failed to get a passphrase. Terminate. */
|
2018-09-25 08:58:46 +01:00
|
|
|
ssh_user_close(s->ppl.ssh,
|
|
|
|
"User aborted at passphrase prompt");
|
Move most of ssh.c out into separate source files.
I've tried to separate out as many individually coherent changes from
this work as I could into their own commits, but here's where I run
out and have to commit the rest of this major refactoring as a
big-bang change.
Most of ssh.c is now no longer in ssh.c: all five of the main
coroutines that handle layers of the SSH-1 and SSH-2 protocols now
each have their own source file to live in, and a lot of the
supporting functions have moved into the appropriate one of those too.
The new abstraction is a vtable called 'PacketProtocolLayer', which
has an input and output packet queue. Each layer's main coroutine is
invoked from the method ssh_ppl_process_queue(), which is usually
(though not exclusively) triggered automatically when things are
pushed on the input queue. In SSH-2, the base layer is the transport
protocol, and it contains a pair of subsidiary queues by which it
passes some of its packets to the higher SSH-2 layers - first userauth
and then connection, which are peers at the same level, with the
former abdicating in favour of the latter at the appropriate moment.
SSH-1 is simpler: the whole login phase of the protocol (crypto setup
and authentication) is all in one module, and since SSH-1 has no
repeat key exchange, that setup layer abdicates in favour of the
connection phase when it's done.
ssh.c itself is now about a tenth of its old size (which all by itself
is cause for celebration!). Its main job is to set up all the layers,
hook them up to each other and to the BPP, and to funnel data back and
forth between that collection of modules and external things such as
the network and the terminal. Once it's set up a collection of packet
protocol layers, it communicates with them partly by calling methods
of the base layer (and if that's ssh2transport then it will delegate
some functionality to the corresponding methods of its higher layer),
and partly by talking directly to the connection layer no matter where
it is in the stack by means of the separate ConnectionLayer vtable
which I introduced in commit 8001dd4cb, and to which I've now added
quite a few extra methods replacing services that used to be internal
function calls within ssh.c.
(One effect of this is that the SSH-1 and SSH-2 channel storage is now
no longer shared - there are distinct struct types ssh1_channel and
ssh2_channel. That means a bit more code duplication, but on the plus
side, a lot fewer confusing conditionals in the middle of half-shared
functions, and less risk of a piece of SSH-1 escaping into SSH-2 or
vice versa, which I remember has happened at least once in the past.)
The bulk of this commit introduces the five new source files, their
common header sshppl.h and some shared supporting routines in
sshcommon.c, and rewrites nearly all of ssh.c itself. But it also
includes a couple of other changes that I couldn't separate easily
enough:
Firstly, there's a new handling for socket EOF, in which ssh.c sets an
'input_eof' flag in the BPP, and that responds by checking a flag that
tells it whether to report the EOF as an error or not. (This is the
main reason for those new BPP_READ / BPP_WAITFOR macros - they can
check the EOF flag every time the coroutine is resumed.)
Secondly, the error reporting itself is changed around again. I'd
expected to put some data fields in the public PacketProtocolLayer
structure that it could set to report errors in the same way as the
BPPs have been doing, but in the end, I decided propagating all those
data fields around was a pain and that even the BPPs shouldn't have
been doing it that way. So I've reverted to a system where everything
calls back to functions in ssh.c itself to report any connection-
ending condition. But there's a new family of those functions,
categorising the possible such conditions by semantics, and each one
has a different set of detailed effects (e.g. how rudely to close the
network connection, what exit status should be passed back to the
whole application, whether to send a disconnect message and/or display
a GUI error box).
I don't expect this to be immediately perfect: of course, the code has
been through a big upheaval, new bugs are expected, and I haven't been
able to do a full job of testing (e.g. I haven't tested every auth or
kex method). But I've checked that it _basically_ works - both SSH
protocols, all the different kinds of forwarding channel, more than
one auth method, Windows and Linux, connection sharing - and I think
it's now at the point where the easiest way to find further bugs is to
let it out into the wild and see what users can spot.
2018-09-24 18:28:16 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
passphrase = dupstr(s->cur_prompt->prompts[0]->result);
|
|
|
|
free_prompts(s->cur_prompt);
|
|
|
|
s->cur_prompt = NULL;
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
* Try decrypting key with passphrase.
|
|
|
|
*/
|
|
|
|
retd = rsa_ssh1_loadkey(
|
|
|
|
s->keyfile, &s->key, passphrase, &error);
|
|
|
|
if (passphrase) {
|
|
|
|
smemclr(passphrase, strlen(passphrase));
|
|
|
|
sfree(passphrase);
|
|
|
|
}
|
|
|
|
if (retd == 1) {
|
|
|
|
/* Correct passphrase. */
|
|
|
|
got_passphrase = TRUE;
|
|
|
|
} else if (retd == 0) {
|
|
|
|
ppl_printf(("Couldn't load private key from %s (%s).\r\n",
|
|
|
|
filename_to_str(s->keyfile), error));
|
|
|
|
got_passphrase = FALSE;
|
|
|
|
break; /* go and try something else */
|
|
|
|
} else if (retd == -1) {
|
|
|
|
ppl_printf(("Wrong passphrase.\r\n"));
|
|
|
|
got_passphrase = FALSE;
|
|
|
|
/* and try again */
|
|
|
|
} else {
|
|
|
|
assert(0 && "unexpected return from rsa_ssh1_loadkey()");
|
|
|
|
got_passphrase = FALSE; /* placate optimisers */
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (got_passphrase) {
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Send a public key attempt.
|
|
|
|
*/
|
|
|
|
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_FAILURE) {
|
|
|
|
ppl_printf(("Server refused our public key.\r\n"));
|
|
|
|
continue; /* go and try something else */
|
|
|
|
}
|
|
|
|
if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to offer of public key, "
|
|
|
|
"type %d (%s)", pktin->type,
|
|
|
|
ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
unsigned char buffer[32];
|
|
|
|
Bignum challenge, response;
|
|
|
|
|
|
|
|
challenge = get_mp_ssh1(pktin);
|
|
|
|
if (get_err(pktin)) {
|
|
|
|
freebn(challenge);
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Server's RSA challenge "
|
|
|
|
"was badly formatted");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
response = rsa_ssh1_decrypt(challenge, &s->key);
|
|
|
|
freebn(s->key.private_exponent);/* burn the evidence */
|
|
|
|
|
|
|
|
for (i = 0; i < 32; i++) {
|
|
|
|
buffer[i] = bignum_byte(response, 31 - i);
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
struct MD5Context md5c;
|
|
|
|
MD5Init(&md5c);
|
|
|
|
put_data(&md5c, buffer, 32);
|
|
|
|
put_data(&md5c, s->session_id, 16);
|
|
|
|
MD5Final(buffer, &md5c);
|
|
|
|
}
|
|
|
|
|
|
|
|
pkt = ssh_bpp_new_pktout(
|
|
|
|
s->ppl.bpp, SSH1_CMSG_AUTH_RSA_RESPONSE);
|
|
|
|
put_data(pkt, buffer, 16);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
|
|
|
|
freebn(challenge);
|
|
|
|
freebn(response);
|
|
|
|
}
|
|
|
|
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s))
|
|
|
|
!= NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
if (flags & FLAG_VERBOSE)
|
|
|
|
ppl_printf(("Failed to authenticate with"
|
|
|
|
" our public key.\r\n"));
|
|
|
|
continue; /* go and try something else */
|
|
|
|
} else if (pktin->type != SSH1_SMSG_SUCCESS) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to RSA authentication, "
|
|
|
|
"type %d (%s)", pktin->type,
|
|
|
|
ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
break; /* we're through! */
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Otherwise, try various forms of password-like authentication.
|
|
|
|
*/
|
|
|
|
s->cur_prompt = new_prompts(s->ppl.frontend);
|
|
|
|
|
|
|
|
if (conf_get_int(s->conf, CONF_try_tis_auth) &&
|
|
|
|
(s->supported_auths_mask & (1 << SSH1_AUTH_TIS)) &&
|
|
|
|
!s->tis_auth_refused) {
|
|
|
|
s->pwpkt_type = SSH1_CMSG_AUTH_TIS_RESPONSE;
|
|
|
|
ppl_logevent(("Requested TIS authentication"));
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_TIS);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
ppl_logevent(("TIS authentication declined"));
|
|
|
|
if (flags & FLAG_INTERACTIVE)
|
|
|
|
ppl_printf(("TIS authentication refused.\r\n"));
|
|
|
|
s->tis_auth_refused = 1;
|
|
|
|
continue;
|
|
|
|
} else if (pktin->type == SSH1_SMSG_AUTH_TIS_CHALLENGE) {
|
|
|
|
ptrlen challenge;
|
|
|
|
char *instr_suf, *prompt;
|
|
|
|
|
|
|
|
challenge = get_string(pktin);
|
|
|
|
if (get_err(pktin)) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "TIS challenge packet was "
|
|
|
|
"badly formed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ppl_logevent(("Received TIS challenge"));
|
|
|
|
s->cur_prompt->to_server = TRUE;
|
|
|
|
s->cur_prompt->name = dupstr("SSH TIS authentication");
|
|
|
|
/* Prompt heuristic comes from OpenSSH */
|
|
|
|
if (memchr(challenge.ptr, '\n', challenge.len)) {
|
|
|
|
instr_suf = dupstr("");
|
|
|
|
prompt = mkstr(challenge);
|
|
|
|
} else {
|
|
|
|
instr_suf = mkstr(challenge);
|
|
|
|
prompt = dupstr("Response: ");
|
|
|
|
}
|
|
|
|
s->cur_prompt->instruction =
|
|
|
|
dupprintf("Using TIS authentication.%s%s",
|
|
|
|
(*instr_suf) ? "\n" : "",
|
|
|
|
instr_suf);
|
|
|
|
s->cur_prompt->instr_reqd = TRUE;
|
|
|
|
add_prompt(s->cur_prompt, prompt, FALSE);
|
|
|
|
sfree(instr_suf);
|
|
|
|
} else {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to TIS authentication, "
|
|
|
|
"type %d (%s)", pktin->type,
|
|
|
|
ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (conf_get_int(s->conf, CONF_try_tis_auth) &&
|
|
|
|
(s->supported_auths_mask & (1 << SSH1_AUTH_CCARD)) &&
|
|
|
|
!s->ccard_auth_refused) {
|
|
|
|
s->pwpkt_type = SSH1_CMSG_AUTH_CCARD_RESPONSE;
|
|
|
|
ppl_logevent(("Requested CryptoCard authentication"));
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_AUTH_CCARD);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
ppl_logevent(("CryptoCard authentication declined"));
|
|
|
|
ppl_printf(("CryptoCard authentication refused.\r\n"));
|
|
|
|
s->ccard_auth_refused = 1;
|
|
|
|
continue;
|
|
|
|
} else if (pktin->type == SSH1_SMSG_AUTH_CCARD_CHALLENGE) {
|
|
|
|
ptrlen challenge;
|
|
|
|
char *instr_suf, *prompt;
|
|
|
|
|
|
|
|
challenge = get_string(pktin);
|
|
|
|
if (get_err(pktin)) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "CryptoCard challenge packet "
|
|
|
|
"was badly formed");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
ppl_logevent(("Received CryptoCard challenge"));
|
|
|
|
s->cur_prompt->to_server = TRUE;
|
|
|
|
s->cur_prompt->name = dupstr("SSH CryptoCard authentication");
|
|
|
|
s->cur_prompt->name_reqd = FALSE;
|
|
|
|
/* Prompt heuristic comes from OpenSSH */
|
|
|
|
if (memchr(challenge.ptr, '\n', challenge.len)) {
|
|
|
|
instr_suf = dupstr("");
|
|
|
|
prompt = mkstr(challenge);
|
|
|
|
} else {
|
|
|
|
instr_suf = mkstr(challenge);
|
|
|
|
prompt = dupstr("Response: ");
|
|
|
|
}
|
|
|
|
s->cur_prompt->instruction =
|
|
|
|
dupprintf("Using CryptoCard authentication.%s%s",
|
|
|
|
(*instr_suf) ? "\n" : "",
|
|
|
|
instr_suf);
|
|
|
|
s->cur_prompt->instr_reqd = TRUE;
|
|
|
|
add_prompt(s->cur_prompt, prompt, FALSE);
|
|
|
|
sfree(instr_suf);
|
|
|
|
} else {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to TIS authentication, "
|
|
|
|
"type %d (%s)", pktin->type,
|
|
|
|
ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
|
|
|
|
if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
|
|
|
|
ssh_sw_abort(s->ppl.ssh, "No supported authentication methods "
|
|
|
|
"available");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
s->cur_prompt->to_server = TRUE;
|
|
|
|
s->cur_prompt->name = dupstr("SSH password");
|
|
|
|
add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
|
|
|
|
s->username, s->savedhost),
|
|
|
|
FALSE);
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Show password prompt, having first obtained it via a TIS
|
|
|
|
* or CryptoCard exchange if we're doing TIS or CryptoCard
|
|
|
|
* authentication.
|
|
|
|
*/
|
|
|
|
s->userpass_ret = get_userpass_input(s->cur_prompt, NULL);
|
|
|
|
while (1) {
|
|
|
|
while (s->userpass_ret < 0 &&
|
|
|
|
bufchain_size(s->ppl.user_input) > 0)
|
|
|
|
s->userpass_ret = get_userpass_input(
|
|
|
|
s->cur_prompt, s->ppl.user_input);
|
|
|
|
|
|
|
|
if (s->userpass_ret >= 0)
|
|
|
|
break;
|
|
|
|
|
|
|
|
s->want_user_input = TRUE;
|
|
|
|
crReturnV;
|
|
|
|
s->want_user_input = FALSE;
|
|
|
|
}
|
|
|
|
if (!s->userpass_ret) {
|
|
|
|
/*
|
|
|
|
* Failed to get a password (for example
|
|
|
|
* because one was supplied on the command line
|
|
|
|
* which has already failed to work). Terminate.
|
|
|
|
*/
|
2018-09-25 08:58:46 +01:00
|
|
|
ssh_user_close(s->ppl.ssh, "User aborted at password prompt");
|
Move most of ssh.c out into separate source files.
I've tried to separate out as many individually coherent changes from
this work as I could into their own commits, but here's where I run
out and have to commit the rest of this major refactoring as a
big-bang change.
Most of ssh.c is now no longer in ssh.c: all five of the main
coroutines that handle layers of the SSH-1 and SSH-2 protocols now
each have their own source file to live in, and a lot of the
supporting functions have moved into the appropriate one of those too.
The new abstraction is a vtable called 'PacketProtocolLayer', which
has an input and output packet queue. Each layer's main coroutine is
invoked from the method ssh_ppl_process_queue(), which is usually
(though not exclusively) triggered automatically when things are
pushed on the input queue. In SSH-2, the base layer is the transport
protocol, and it contains a pair of subsidiary queues by which it
passes some of its packets to the higher SSH-2 layers - first userauth
and then connection, which are peers at the same level, with the
former abdicating in favour of the latter at the appropriate moment.
SSH-1 is simpler: the whole login phase of the protocol (crypto setup
and authentication) is all in one module, and since SSH-1 has no
repeat key exchange, that setup layer abdicates in favour of the
connection phase when it's done.
ssh.c itself is now about a tenth of its old size (which all by itself
is cause for celebration!). Its main job is to set up all the layers,
hook them up to each other and to the BPP, and to funnel data back and
forth between that collection of modules and external things such as
the network and the terminal. Once it's set up a collection of packet
protocol layers, it communicates with them partly by calling methods
of the base layer (and if that's ssh2transport then it will delegate
some functionality to the corresponding methods of its higher layer),
and partly by talking directly to the connection layer no matter where
it is in the stack by means of the separate ConnectionLayer vtable
which I introduced in commit 8001dd4cb, and to which I've now added
quite a few extra methods replacing services that used to be internal
function calls within ssh.c.
(One effect of this is that the SSH-1 and SSH-2 channel storage is now
no longer shared - there are distinct struct types ssh1_channel and
ssh2_channel. That means a bit more code duplication, but on the plus
side, a lot fewer confusing conditionals in the middle of half-shared
functions, and less risk of a piece of SSH-1 escaping into SSH-2 or
vice versa, which I remember has happened at least once in the past.)
The bulk of this commit introduces the five new source files, their
common header sshppl.h and some shared supporting routines in
sshcommon.c, and rewrites nearly all of ssh.c itself. But it also
includes a couple of other changes that I couldn't separate easily
enough:
Firstly, there's a new handling for socket EOF, in which ssh.c sets an
'input_eof' flag in the BPP, and that responds by checking a flag that
tells it whether to report the EOF as an error or not. (This is the
main reason for those new BPP_READ / BPP_WAITFOR macros - they can
check the EOF flag every time the coroutine is resumed.)
Secondly, the error reporting itself is changed around again. I'd
expected to put some data fields in the public PacketProtocolLayer
structure that it could set to report errors in the same way as the
BPPs have been doing, but in the end, I decided propagating all those
data fields around was a pain and that even the BPPs shouldn't have
been doing it that way. So I've reverted to a system where everything
calls back to functions in ssh.c itself to report any connection-
ending condition. But there's a new family of those functions,
categorising the possible such conditions by semantics, and each one
has a different set of detailed effects (e.g. how rudely to close the
network connection, what exit status should be passed back to the
whole application, whether to send a disconnect message and/or display
a GUI error box).
I don't expect this to be immediately perfect: of course, the code has
been through a big upheaval, new bugs are expected, and I haven't been
able to do a full job of testing (e.g. I haven't tested every auth or
kex method). But I've checked that it _basically_ works - both SSH
protocols, all the different kinds of forwarding channel, more than
one auth method, Windows and Linux, connection sharing - and I think
it's now at the point where the easiest way to find further bugs is to
let it out into the wild and see what users can spot.
2018-09-24 18:28:16 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
|
|
|
|
/*
|
|
|
|
* Defence against traffic analysis: we send a
|
|
|
|
* whole bunch of packets containing strings of
|
|
|
|
* different lengths. One of these strings is the
|
|
|
|
* password, in a SSH1_CMSG_AUTH_PASSWORD packet.
|
|
|
|
* The others are all random data in
|
|
|
|
* SSH1_MSG_IGNORE packets. This way a passive
|
|
|
|
* listener can't tell which is the password, and
|
|
|
|
* hence can't deduce the password length.
|
|
|
|
*
|
|
|
|
* Anybody with a password length greater than 16
|
|
|
|
* bytes is going to have enough entropy in their
|
|
|
|
* password that a listener won't find it _that_
|
|
|
|
* much help to know how long it is. So what we'll
|
|
|
|
* do is:
|
|
|
|
*
|
|
|
|
* - if password length < 16, we send 15 packets
|
|
|
|
* containing string lengths 1 through 15
|
|
|
|
*
|
|
|
|
* - otherwise, we let N be the nearest multiple
|
|
|
|
* of 8 below the password length, and send 8
|
|
|
|
* packets containing string lengths N through
|
|
|
|
* N+7. This won't obscure the order of
|
|
|
|
* magnitude of the password length, but it will
|
|
|
|
* introduce a bit of extra uncertainty.
|
|
|
|
*
|
|
|
|
* A few servers can't deal with SSH1_MSG_IGNORE, at
|
|
|
|
* least in this context. For these servers, we need
|
|
|
|
* an alternative defence. We make use of the fact
|
|
|
|
* that the password is interpreted as a C string:
|
|
|
|
* so we can append a NUL, then some random data.
|
|
|
|
*
|
|
|
|
* A few servers can deal with neither SSH1_MSG_IGNORE
|
|
|
|
* here _nor_ a padded password string.
|
|
|
|
* For these servers we are left with no defences
|
|
|
|
* against password length sniffing.
|
|
|
|
*/
|
|
|
|
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) &&
|
|
|
|
!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
|
|
|
|
/*
|
|
|
|
* The server can deal with SSH1_MSG_IGNORE, so
|
|
|
|
* we can use the primary defence.
|
|
|
|
*/
|
|
|
|
int bottom, top, pwlen, i;
|
|
|
|
|
|
|
|
pwlen = strlen(s->cur_prompt->prompts[0]->result);
|
|
|
|
if (pwlen < 16) {
|
|
|
|
bottom = 0; /* zero length passwords are OK! :-) */
|
|
|
|
top = 15;
|
|
|
|
} else {
|
|
|
|
bottom = pwlen & ~7;
|
|
|
|
top = bottom + 7;
|
|
|
|
}
|
|
|
|
|
|
|
|
assert(pwlen >= bottom && pwlen <= top);
|
|
|
|
|
|
|
|
for (i = bottom; i <= top; i++) {
|
|
|
|
if (i == pwlen) {
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
|
|
|
|
put_stringz(pkt, s->cur_prompt->prompts[0]->result);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
} else {
|
|
|
|
int j;
|
|
|
|
strbuf *random_data = strbuf_new();
|
|
|
|
for (j = 0; j < i; j++)
|
|
|
|
put_byte(random_data, random_byte());
|
|
|
|
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
|
|
|
|
put_stringsb(pkt, random_data);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
ppl_logevent(("Sending password with camouflage packets"));
|
|
|
|
}
|
|
|
|
else if (!(s->ppl.remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
|
|
|
|
/*
|
|
|
|
* The server can't deal with SSH1_MSG_IGNORE
|
|
|
|
* but can deal with padded passwords, so we
|
|
|
|
* can use the secondary defence.
|
|
|
|
*/
|
|
|
|
strbuf *padded_pw = strbuf_new();
|
|
|
|
|
|
|
|
ppl_logevent(("Sending length-padded password"));
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
|
|
|
|
put_asciz(padded_pw, s->cur_prompt->prompts[0]->result);
|
|
|
|
do {
|
|
|
|
put_byte(padded_pw, random_byte());
|
|
|
|
} while (padded_pw->len % 64 != 0);
|
|
|
|
put_stringsb(pkt, padded_pw);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* The server is believed unable to cope with
|
|
|
|
* any of our password camouflage methods.
|
|
|
|
*/
|
|
|
|
ppl_logevent(("Sending unpadded password"));
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
|
|
|
|
put_stringz(pkt, s->cur_prompt->prompts[0]->result);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, s->pwpkt_type);
|
|
|
|
put_stringz(pkt, s->cur_prompt->prompts[0]->result);
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
}
|
|
|
|
ppl_logevent(("Sent password"));
|
|
|
|
free_prompts(s->cur_prompt);
|
|
|
|
s->cur_prompt = NULL;
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
if (flags & FLAG_VERBOSE)
|
|
|
|
ppl_printf(("Access denied\r\n"));
|
|
|
|
ppl_logevent(("Authentication refused"));
|
|
|
|
} else if (pktin->type != SSH1_SMSG_SUCCESS) {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to password authentication, type %d "
|
|
|
|
"(%s)", pktin->type, ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ppl_logevent(("Authentication successful"));
|
|
|
|
|
|
|
|
if (conf_get_int(s->conf, CONF_compression)) {
|
|
|
|
ppl_logevent(("Requesting compression"));
|
|
|
|
pkt = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_CMSG_REQUEST_COMPRESSION);
|
|
|
|
put_uint32(pkt, 6); /* gzip compression level */
|
|
|
|
pq_push(s->ppl.out_pq, pkt);
|
|
|
|
crMaybeWaitUntilV((pktin = ssh1_login_pop(s)) != NULL);
|
|
|
|
if (pktin->type == SSH1_SMSG_SUCCESS) {
|
|
|
|
/*
|
|
|
|
* We don't have to actually do anything here: the SSH-1
|
|
|
|
* BPP will take care of automatically starting the
|
|
|
|
* compression, by recognising our outgoing request packet
|
|
|
|
* and the success response. (Horrible, but it's the
|
|
|
|
* easiest way to avoid race conditions if other packets
|
|
|
|
* cross in transit.)
|
|
|
|
*/
|
|
|
|
ppl_logevent(("Started zlib (RFC1950) compression"));
|
|
|
|
} else if (pktin->type == SSH1_SMSG_FAILURE) {
|
|
|
|
ppl_logevent(("Server refused to enable compression"));
|
|
|
|
ppl_printf(("Server refused to compress\r\n"));
|
|
|
|
} else {
|
|
|
|
ssh_proto_error(s->ppl.ssh, "Received unexpected packet"
|
|
|
|
" in response to compression request, type %d "
|
|
|
|
"(%s)", pktin->type, ssh1_pkt_type(pktin->type));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ssh1_connection_set_local_protoflags(
|
|
|
|
s->successor_layer, s->local_protoflags);
|
|
|
|
{
|
|
|
|
PacketProtocolLayer *successor = s->successor_layer;
|
|
|
|
s->successor_layer = NULL; /* avoid freeing it ourself */
|
|
|
|
ssh_ppl_replace(&s->ppl, successor);
|
|
|
|
return; /* we've just freed s, so avoid even touching s->crState */
|
|
|
|
}
|
|
|
|
|
|
|
|
crFinishV;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_dialog_callback(void *loginv, int ret)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
|
|
|
|
s->dlgret = ret;
|
|
|
|
ssh_ppl_process_queue(&s->ppl);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_agent_query(struct ssh1_login_state *s, strbuf *req)
|
|
|
|
{
|
|
|
|
void *response;
|
|
|
|
int response_len;
|
|
|
|
|
|
|
|
sfree(s->agent_response_to_free);
|
|
|
|
s->agent_response_to_free = NULL;
|
|
|
|
|
|
|
|
s->auth_agent_query = agent_query(req, &response, &response_len,
|
|
|
|
ssh1_login_agent_callback, s);
|
|
|
|
if (!s->auth_agent_query)
|
|
|
|
ssh1_login_agent_callback(s, response, response_len);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_agent_callback(void *loginv, void *reply, int replylen)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s = (struct ssh1_login_state *)loginv;
|
|
|
|
|
|
|
|
s->auth_agent_query = NULL;
|
|
|
|
s->agent_response_to_free = reply;
|
|
|
|
s->agent_response = make_ptrlen(reply, replylen);
|
|
|
|
|
|
|
|
queue_idempotent_callback(&s->ppl.ic_process_queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_special_cmd(PacketProtocolLayer *ppl,
|
|
|
|
SessionSpecialCode code, int arg)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s =
|
|
|
|
FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
PktOut *pktout;
|
|
|
|
|
|
|
|
if (code == SS_PING || code == SS_NOP) {
|
|
|
|
if (!(s->ppl.remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
|
|
|
|
pktout = ssh_bpp_new_pktout(s->ppl.bpp, SSH1_MSG_IGNORE);
|
|
|
|
put_stringz(pktout, "");
|
|
|
|
pq_push(s->ppl.out_pq, pktout);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int ssh1_login_want_user_input(PacketProtocolLayer *ppl)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s =
|
|
|
|
FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
return s->want_user_input;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_got_user_input(PacketProtocolLayer *ppl)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s =
|
|
|
|
FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
if (s->want_user_input)
|
|
|
|
queue_idempotent_callback(&s->ppl.ic_process_queue);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void ssh1_login_reconfigure(PacketProtocolLayer *ppl, Conf *conf)
|
|
|
|
{
|
|
|
|
struct ssh1_login_state *s =
|
|
|
|
FROMFIELD(ppl, struct ssh1_login_state, ppl);
|
|
|
|
ssh_ppl_reconfigure(s->successor_layer, conf);
|
|
|
|
}
|