#include "putty.h"
#include "tree234.h"
+#include "storage.h"
#include "ssh.h"
#ifndef NO_GSSAPI
#include "sshgssc.h"
* This list is derived from RFC 4254 and
* SSH-1 RFC-1.2.31.
*/
-static const struct {
+static const struct ssh_ttymode {
const char* const mode;
int opcode;
enum { TTY_OP_CHAR, TTY_OP_BOOL } type;
{ "IXANY", 39, TTY_OP_BOOL },
{ "IXOFF", 40, TTY_OP_BOOL },
{ "IMAXBEL", 41, TTY_OP_BOOL },
+ { "IUTF8", 42, TTY_OP_BOOL },
{ "ISIG", 50, TTY_OP_BOOL },
{ "ICANON", 51, TTY_OP_BOOL },
{ "XCASE", 52, TTY_OP_BOOL },
struct Packet *pktin);
static void do_ssh2_authconn(Ssh ssh, const unsigned char *in, int inlen,
struct Packet *pktin);
+static void ssh_channel_init(struct ssh_channel *c);
+static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin);
+static void ssh_channel_got_eof(struct ssh_channel *c);
static void ssh2_channel_check_close(struct ssh_channel *c);
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason);
static void ssh_channel_destroy(struct ssh_channel *c);
+static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize);
+static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin);
/*
* Buffer management constants. There are several of these for
* ensure that the server never has any need to throttle its end
* of the connection), so we set this high as well.
*
- * - OUR_V2_WINSIZE is the maximum window size we present on SSH-2
+ * - OUR_V2_WINSIZE is the default window size we present on SSH-2
* channels.
*
* - OUR_V2_BIGWIN is the window size we advertise for the only
#define OUR_V2_MAXPKT 0x4000UL
#define OUR_V2_PACKETLIMIT 0x9000UL
-const static struct ssh_signkey *hostkey_algs[] = {
- &ssh_ecdsa_ed25519,
- &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
- &ssh_rsa, &ssh_dss
+struct ssh_signkey_with_user_pref_id {
+ const struct ssh_signkey *alg;
+ int id;
+};
+const static struct ssh_signkey_with_user_pref_id hostkey_algs[] = {
+ { &ssh_ecdsa_ed25519, HK_ED25519 },
+ { &ssh_ecdsa_nistp256, HK_ECDSA },
+ { &ssh_ecdsa_nistp384, HK_ECDSA },
+ { &ssh_ecdsa_nistp521, HK_ECDSA },
+ { &ssh_dss, HK_DSA },
+ { &ssh_rsa, HK_RSA },
};
-const static struct ssh_mac *macs[] = {
+const static struct ssh_mac *const macs[] = {
&ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
};
-const static struct ssh_mac *buggymacs[] = {
+const static struct ssh_mac *const buggymacs[] = {
&ssh_hmac_sha1_buggy, &ssh_hmac_sha1_96_buggy, &ssh_hmac_md5
};
ssh_comp_none_disable, NULL
};
extern const struct ssh_compress ssh_zlib;
-const static struct ssh_compress *compressions[] = {
+const static struct ssh_compress *const compressions[] = {
&ssh_zlib, &ssh_comp_none
};
CHAN_X11,
CHAN_AGENT,
CHAN_SOCKDATA,
- CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */
/*
* CHAN_SHARING indicates a channel which is tracked here on
* behalf of a connection-sharing downstream. We do almost nothing
} v;
union {
struct ssh_agent_channel {
- unsigned char *message;
- unsigned char msglen[4];
- unsigned lensofar, totallen;
- int outstanding_requests;
+ bufchain inbuffer;
+ agent_pending_query *pending;
} a;
struct ssh_x11_channel {
struct X11Connection *xconn;
static void ssh_size(void *handle, int width, int height);
static void ssh_special(void *handle, Telnet_Special);
static int ssh2_try_send(struct ssh_channel *c);
-static void ssh2_add_channel_data(struct ssh_channel *c,
- const char *buf, int len);
+static int ssh_send_channel_data(struct ssh_channel *c,
+ const char *buf, int len);
static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
static void ssh2_set_window(struct ssh_channel *c, int newwin);
static int ssh_sendbuffer(void *handle);
int send_ok;
int echoing, editing;
+ int session_started;
void *frontend;
int ospeed, ispeed; /* temporaries */
*/
struct ssh_gss_liblist *gsslibs;
#endif
+
+ /*
+ * The last list returned from get_specials.
+ */
+ struct telnet_special *specials;
+
+ /*
+ * List of host key algorithms for which we _don't_ have a stored
+ * host key. These are indices into the main hostkey_algs[] array
+ */
+ int uncert_hostkeys[lenof(hostkey_algs)];
+ int n_uncert_hostkeys;
+
+ /*
+ * Flag indicating that the current rekey is intended to finish
+ * with a newly cross-certified host key.
+ */
+ int cross_certifying;
+
+ /*
+ * Any asynchronous query to our SSH agent that we might have in
+ * flight from the main authentication loop. (Queries from
+ * agent-forwarding channels live in their channel structure.)
+ */
+ agent_pending_query *auth_agent_query;
};
+static const char *ssh_pkt_type(Ssh ssh, int type)
+{
+ if (ssh->version == 1)
+ return ssh1_pkt_type(type);
+ else
+ return ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx, type);
+}
+
#define logevent(s) logevent(ssh->frontend, s)
/* logevent, only printf-formatted. */
/* Helper function for common bits of parsing ttymodes. */
static void parse_ttymodes(Ssh ssh,
- void (*do_mode)(void *data, char *mode, char *val),
+ void (*do_mode)(void *data,
+ const struct ssh_ttymode *mode,
+ char *val),
void *data)
{
- char *key, *val;
+ int i;
+ const struct ssh_ttymode *mode;
+ char *val;
+ char default_val[2];
+
+ strcpy(default_val, "A");
+
+ for (i = 0; i < lenof(ssh_ttymodes); i++) {
+ mode = ssh_ttymodes + i;
+ val = conf_get_str_str_opt(ssh->conf, CONF_ttymodes, mode->mode);
+ if (!val)
+ val = default_val;
- for (val = conf_get_str_strs(ssh->conf, CONF_ttymodes, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(ssh->conf, CONF_ttymodes, key, &key)) {
/*
* val[0] is either 'V', indicating that an explicit value
* follows it, or 'A' indicating that we should pass the
* value through from the local environment via get_ttymode.
*/
if (val[0] == 'A') {
- val = get_ttymode(ssh->frontend, key);
+ val = get_ttymode(ssh->frontend, mode->mode);
if (val) {
- do_mode(data, key, val);
+ do_mode(data, mode, val);
sfree(val);
}
} else
- do_mode(data, key, val + 1); /* skip the 'V' */
+ do_mode(data, mode, val + 1); /* skip the 'V' */
}
}
}
}
+ /*
+ * RFC 4253 doesn't explicitly say that completely empty packets
+ * with no type byte are forbidden, so treat them as deserving
+ * an SSH_MSG_UNIMPLEMENTED.
+ */
+ if (st->pktin->length <= 5) { /* == 5 we hope, but robustness */
+ ssh2_msg_something_unimplemented(ssh, st->pktin);
+ crStop(NULL);
+ }
/*
* pktin->body and pktin->length should identify the semantic
* content of the packet, excluding the initial type byte.
if (backlog > SSH_MAX_BACKLOG)
ssh_throttle_all(ssh, 1, backlog);
- ssh->outgoing_data_size += ssh->deferred_data_size;
- if (!ssh->kex_in_progress &&
- !ssh->bare_connection &&
- ssh->max_data_size != 0 &&
- ssh->outgoing_data_size > ssh->max_data_size)
- do_ssh2_transport(ssh, "too much data sent", -1, NULL);
- ssh->deferred_data_size = 0;
+ if (ssh->version == 2) {
+ ssh->outgoing_data_size += ssh->deferred_data_size;
+ ssh->deferred_data_size = 0;
+ if (!ssh->kex_in_progress &&
+ !ssh->bare_connection &&
+ ssh->max_data_size != 0 &&
+ ssh->outgoing_data_size > ssh->max_data_size)
+ do_ssh2_transport(ssh, "too much data sent", -1, NULL);
+ }
}
/*
}
ssh_fix_verstring(verstring + strlen(protoname));
+#ifdef FUZZING
+ /* FUZZING make PuTTY insecure, so make live use difficult. */
+ verstring[0] = 'I';
+#endif
if (ssh->version == 2) {
size_t len;
crReturn(1);
}
+ ssh->session_started = TRUE;
+
s->vstrsize = sizeof(protoname) + 16;
s->vstring = snewn(s->vstrsize, char);
strcpy(s->vstring, protoname);
/* Anything greater or equal to "1.99" means protocol 2 is supported. */
s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
- if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
- bombout(("SSH protocol version 1 required by configuration but "
- "not provided by server"));
- crStop(0);
- }
- if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
- bombout(("SSH protocol version 2 required by configuration but "
- "not provided by server"));
- crStop(0);
+ if (conf_get_int(ssh->conf, CONF_sshprot) == 0) {
+ if (!s->proto1) {
+ bombout(("SSH protocol version 1 required by our configuration "
+ "but not provided by server"));
+ crStop(0);
+ }
+ } else if (conf_get_int(ssh->conf, CONF_sshprot) == 3) {
+ if (!s->proto2) {
+ bombout(("SSH protocol version 2 required by our configuration "
+ "but server only provides (old, insecure) SSH-1"));
+ crStop(0);
+ }
+ } else {
+ /* No longer support values 1 or 2 for CONF_sshprot */
+ assert(!"Unexpected value for CONF_sshprot");
}
if (s->proto2 && (conf_get_int(ssh->conf, CONF_sshprot) >= 2 || !s->proto1))
*/
if (ssh->channels) {
while (NULL != (c = index234(ssh->channels, 0))) {
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.xconn);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- pfd_close(c->u.pfd.pf);
- break;
- }
+ ssh_channel_close_local(c, NULL);
del234(ssh->channels, c); /* moving next one to index 0 */
if (ssh->version == 2)
bufchain_clear(&c->v.v2.outbuffer);
const char *error_msg, int error_code)
{
Ssh ssh = (Ssh) plug;
- char addrbuf[256], *msg;
- if (ssh->attempting_connshare) {
- /*
- * While we're attempting connection sharing, don't loudly log
- * everything that happens. Real TCP connections need to be
- * logged when we _start_ trying to connect, because it might
- * be ages before they respond if something goes wrong; but
- * connection sharing is local and quick to respond, and it's
- * sufficient to simply wait and see whether it worked
- * afterwards.
- */
- } else {
- sk_getaddr(addr, addrbuf, lenof(addrbuf));
-
- if (type == 0) {
- if (sk_addr_needs_port(addr)) {
- msg = dupprintf("Connecting to %s port %d", addrbuf, port);
- } else {
- msg = dupprintf("Connecting to %s", addrbuf);
- }
- } else {
- msg = dupprintf("Failed to connect to %s: %s", addrbuf, error_msg);
- }
+ /*
+ * While we're attempting connection sharing, don't loudly log
+ * everything that happens. Real TCP connections need to be logged
+ * when we _start_ trying to connect, because it might be ages
+ * before they respond if something goes wrong; but connection
+ * sharing is local and quick to respond, and it's sufficient to
+ * simply wait and see whether it worked afterwards.
+ */
- logevent(msg);
- sfree(msg);
- }
+ if (!ssh->attempting_connshare)
+ backend_socket_log(ssh->frontend, type, addr, port,
+ error_msg, error_code, ssh->conf,
+ ssh->session_started);
}
void ssh_connshare_log(Ssh ssh, int event, const char *logtext,
ssh_throttle_all(ssh, 0, bufsize);
}
-/*
- * Connect to specified host and port.
- * Returns an error message, or NULL on success.
- * Also places the canonical host name into `realhost'. It must be
- * freed by the caller.
- */
-static const char *connect_to_host(Ssh ssh, const char *host, int port,
- char **realhost, int nodelay, int keepalive)
+static void ssh_hostport_setup(const char *host, int port, Conf *conf,
+ char **savedhost, int *savedport,
+ char **loghost_ret)
{
- static const struct plug_function_table fn_table = {
- ssh_socket_log,
- ssh_closing,
- ssh_receive,
- ssh_sent,
- NULL
- };
+ char *loghost = conf_get_str(conf, CONF_loghost);
+ if (loghost_ret)
+ *loghost_ret = loghost;
- SockAddr addr;
- const char *err;
- char *loghost;
- int addressfamily, sshprot;
-
- loghost = conf_get_str(ssh->conf, CONF_loghost);
if (*loghost) {
char *tmphost;
char *colon;
tmphost = dupstr(loghost);
- ssh->savedport = 22; /* default ssh port */
+ *savedport = 22; /* default ssh port */
/*
* A colon suffix on the hostname string also lets us affect
if (colon && colon == host_strchr(tmphost, ':')) {
*colon++ = '\0';
if (*colon)
- ssh->savedport = atoi(colon);
+ *savedport = atoi(colon);
}
- ssh->savedhost = host_strduptrim(tmphost);
+ *savedhost = host_strduptrim(tmphost);
sfree(tmphost);
} else {
- ssh->savedhost = host_strduptrim(host);
+ *savedhost = host_strduptrim(host);
if (port < 0)
port = 22; /* default ssh port */
- ssh->savedport = port;
+ *savedport = port;
}
+}
+
+static int ssh_test_for_upstream(const char *host, int port, Conf *conf)
+{
+ char *savedhost;
+ int savedport;
+ int ret;
+
+ random_ref(); /* platform may need this to determine share socket name */
+ ssh_hostport_setup(host, port, conf, &savedhost, &savedport, NULL);
+ ret = ssh_share_test_for_upstream(savedhost, savedport, conf);
+ sfree(savedhost);
+ random_unref();
+
+ return ret;
+}
+
+/*
+ * Connect to specified host and port.
+ * Returns an error message, or NULL on success.
+ * Also places the canonical host name into `realhost'. It must be
+ * freed by the caller.
+ */
+static const char *connect_to_host(Ssh ssh, const char *host, int port,
+ char **realhost, int nodelay, int keepalive)
+{
+ static const struct plug_function_table fn_table = {
+ ssh_socket_log,
+ ssh_closing,
+ ssh_receive,
+ ssh_sent,
+ NULL
+ };
+
+ SockAddr addr;
+ const char *err;
+ char *loghost;
+ int addressfamily, sshprot;
+
+ ssh_hostport_setup(host, port, ssh->conf,
+ &ssh->savedhost, &ssh->savedport, &loghost);
ssh->fn = &fn_table; /* make 'ssh' usable as a Plug */
* Try to find host.
*/
addressfamily = conf_get_int(ssh->conf, CONF_addressfamily);
- logeventf(ssh, "Looking up host \"%s\"%s", host,
- (addressfamily == ADDRTYPE_IPV4 ? " (IPv4)" :
- (addressfamily == ADDRTYPE_IPV6 ? " (IPv6)" : "")));
- addr = name_lookup(host, port, realhost, ssh->conf, addressfamily);
+ addr = name_lookup(host, port, realhost, ssh->conf, addressfamily,
+ ssh->frontend, "SSH connection");
if ((err = sk_addr_error(addr)) != NULL) {
sk_addr_free(addr);
return err;
}
/*
- * If the SSH version number's fixed, set it now, and if it's SSH-2,
- * send the version string too.
+ * The SSH version number is always fixed (since we no longer support
+ * fallback between versions), so set it now, and if it's SSH-2,
+ * send the version string now too.
*/
sshprot = conf_get_int(ssh->conf, CONF_sshprot);
+ assert(sshprot == 0 || sshprot == 3);
if (sshprot == 0)
+ /* SSH-1 only */
ssh->version = 1;
if (sshprot == 3 && !ssh->bare_connection) {
+ /* SSH-2 only */
ssh->version = 2;
ssh_send_verstring(ssh, "SSH-", NULL);
}
}
}
+static void ssh_agentf_try_forward(struct ssh_channel *c);
+
/*
* Throttle or unthrottle _all_ local data streams (for when sends
* on the SSH connection itself back up).
x11_override_throttle(c->u.x11.xconn, enable);
break;
case CHAN_AGENT:
- /* Agent channels require no buffer management. */
+ /* Agent forwarding channels are buffer-managed by
+ * checking ssh->throttled_all in ssh_agentf_try_forward.
+ * So at the moment we _un_throttle again, we must make an
+ * attempt to do something. */
+ if (!enable)
+ ssh_agentf_try_forward(c);
break;
case CHAN_SOCKDATA:
pfd_override_throttle(c->u.pfd.pf, enable);
{
Ssh ssh = (Ssh) sshv;
+ ssh->auth_agent_query = NULL;
+
ssh->agent_response = reply;
ssh->agent_response_len = replylen;
ssh_process_queued_incoming_data(ssh);
}
-static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+static void ssh_agentf_got_response(struct ssh_channel *c,
+ void *reply, int replylen)
{
- struct ssh_channel *c = (struct ssh_channel *)cv;
- Ssh ssh = c->ssh;
- const void *sentreply = reply;
+ c->u.a.pending = NULL;
- c->u.a.outstanding_requests--;
- if (!sentreply) {
- /* Fake SSH_AGENT_FAILURE. */
- sentreply = "\0\0\0\1\5";
+ if (!reply) {
+ /* The real agent didn't send any kind of reply at all for
+ * some reason, so fake an SSH_AGENT_FAILURE. */
+ reply = "\0\0\0\1\5";
replylen = 5;
}
- if (ssh->version == 2) {
- ssh2_add_channel_data(c, sentreply, replylen);
- ssh2_try_send(c);
- } else {
- send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
- PKT_INT, c->remoteid,
- PKT_INT, replylen,
- PKT_DATA, sentreply, replylen,
- PKT_END);
+
+ ssh_send_channel_data(c, reply, replylen);
+}
+
+static void ssh_agentf_callback(void *cv, void *reply, int replylen);
+
+static void ssh_agentf_try_forward(struct ssh_channel *c)
+{
+ unsigned datalen, lengthfield, messagelen;
+ unsigned char *message;
+ unsigned char msglen[4];
+ void *reply;
+ int replylen;
+
+ /*
+ * Don't try to parallelise agent requests. Wait for each one to
+ * return before attempting the next.
+ */
+ if (c->u.a.pending)
+ return;
+
+ /*
+ * If the outgoing side of the channel connection is currently
+ * throttled (for any reason, either that channel's window size or
+ * the entire SSH connection being throttled), don't submit any
+ * new forwarded requests to the real agent. This causes the input
+ * side of the agent forwarding not to be emptied, exerting the
+ * required back-pressure on the remote client, and encouraging it
+ * to read our responses before sending too many more requests.
+ */
+ if (c->ssh->throttled_all ||
+ (c->ssh->version == 2 && c->v.v2.remwindow == 0))
+ return;
+
+ while (1) {
+ /*
+ * Try to extract a complete message from the input buffer.
+ */
+ datalen = bufchain_size(&c->u.a.inbuffer);
+ if (datalen < 4)
+ break; /* not even a length field available yet */
+
+ bufchain_fetch(&c->u.a.inbuffer, msglen, 4);
+ lengthfield = GET_32BIT(msglen);
+ if (lengthfield > datalen - 4)
+ break; /* a whole message is not yet available */
+
+ messagelen = lengthfield + 4;
+
+ message = snewn(messagelen, unsigned char);
+ bufchain_fetch(&c->u.a.inbuffer, message, messagelen);
+ bufchain_consume(&c->u.a.inbuffer, messagelen);
+ c->u.a.pending = agent_query(
+ message, messagelen, &reply, &replylen, ssh_agentf_callback, c);
+ sfree(message);
+
+ if (c->u.a.pending)
+ return; /* agent_query promised to reply in due course */
+
+ /*
+ * If the agent gave us an answer immediately, pass it
+ * straight on and go round this loop again.
+ */
+ ssh_agentf_got_response(c, reply, replylen);
}
- if (reply)
- sfree(reply);
+
/*
- * If we've already seen an incoming EOF but haven't sent an
- * outgoing one, this may be the moment to send it.
+ * If we get here (i.e. we left the above while loop via 'break'
+ * rather than 'return'), that means we've determined that the
+ * input buffer for the agent forwarding connection doesn't
+ * contain a complete request.
+ *
+ * So if there's potentially more data to come, we can return now,
+ * and wait for the remote client to send it. But if the remote
+ * has sent EOF, it would be a mistake to do that, because we'd be
+ * waiting a long time. So this is the moment to check for EOF,
+ * and respond appropriately.
*/
- if (c->u.a.outstanding_requests == 0 && (c->closes & CLOSES_RCVD_EOF))
+ if (c->closes & CLOSES_RCVD_EOF)
sshfwd_write_eof(c);
}
+static void ssh_agentf_callback(void *cv, void *reply, int replylen)
+{
+ struct ssh_channel *c = (struct ssh_channel *)cv;
+
+ ssh_agentf_got_response(c, reply, replylen);
+ sfree(reply);
+
+ /*
+ * Now try to extract and send further messages from the channel's
+ * input-side buffer.
+ */
+ ssh_agentf_try_forward(c);
+}
+
/*
* Client-initiated disconnection. Send a DISCONNECT if `wire_reason'
* non-NULL, otherwise just close the connection. `client_reason' == NULL
"rsa", keystr, fingerprint,
ssh_dialog_callback, ssh);
sfree(keystr);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturn(0);
/* Request the keys held by the agent. */
PUT_32BIT(s->request, 1);
s->request[4] = SSH1_AGENTC_REQUEST_RSA_IDENTITIES;
- if (!agent_query(s->request, 5, &r, &s->responselen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->request, 5, &r, &s->responselen, ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturn(0);
if (pktin) {
memcpy(q, s->session_id, 16);
q += 16;
PUT_32BIT(q, 1); /* response format */
- if (!agent_query(agentreq, len + 4, &vret, &retlen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ agentreq, len + 4, &vret, &retlen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
sfree(agentreq);
do {
crReturn(0);
void sshfwd_unclean_close(struct ssh_channel *c, const char *err)
{
Ssh ssh = c->ssh;
+ char *reason;
if (ssh->state == SSH_STATE_CLOSED)
return;
- switch (c->type) {
- case CHAN_X11:
- x11_close(c->u.x11.xconn);
- logeventf(ssh, "Forwarded X11 connection terminated due to local "
- "error: %s", err);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- pfd_close(c->u.pfd.pf);
- logeventf(ssh, "Forwarded port closed due to local error: %s", err);
- break;
- }
- c->type = CHAN_ZOMBIE;
+ reason = dupprintf("due to local error: %s", err);
+ ssh_channel_close_local(c, reason);
+ sfree(reason);
c->pending_eof = FALSE; /* this will confuse a zombie channel */
ssh2_channel_check_close(c);
if (ssh->state == SSH_STATE_CLOSED)
return 0;
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_CHANNEL_DATA,
- PKT_INT, c->remoteid,
- PKT_INT, len, PKT_DATA, buf, len,
- PKT_END);
- /*
- * In SSH-1 we can return 0 here - implying that forwarded
- * connections are never individually throttled - because
- * the only circumstance that can cause throttling will be
- * the whole SSH connection backing up, in which case
- * _everything_ will be throttled as a whole.
- */
- return 0;
- } else {
- ssh2_add_channel_data(c, buf, len);
- return ssh2_try_send(c);
- }
+ return ssh_send_channel_data(c, buf, len);
}
void sshfwd_unthrottle(struct ssh_channel *c, int bufsize)
{
Ssh ssh = c->ssh;
- int buflimit;
if (ssh->state == SSH_STATE_CLOSED)
return;
- if (ssh->version == 1) {
- buflimit = SSH1_BUFFER_LIMIT;
- } else {
- buflimit = c->v.v2.locmaxwin;
- ssh2_set_window(c, bufsize < buflimit ? buflimit - bufsize : 0);
- }
- if (c->throttling_conn && bufsize <= buflimit) {
- c->throttling_conn = 0;
- ssh_throttle_conn(ssh, -1);
- }
+ ssh_channel_unthrottle(c, bufsize);
}
static void ssh_queueing_handler(Ssh ssh, struct Packet *pktin)
c = snew(struct ssh_channel);
c->ssh = ssh;
+ ssh_channel_init(c);
c->u.x11.xconn = x11_init(ssh->x11authtree, c, NULL, -1);
c->remoteid = remoteid;
c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
c->type = CHAN_X11; /* identify channel type */
- add234(ssh->channels, c);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
PKT_INT, c->remoteid, PKT_INT,
c->localid, PKT_END);
} else {
c = snew(struct ssh_channel);
c->ssh = ssh;
+ ssh_channel_init(c);
c->remoteid = remoteid;
c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- c->u.a.message = NULL;
- c->u.a.outstanding_requests = 0;
- add234(ssh->channels, c);
+ c->u.a.pending = NULL;
+ bufchain_init(&c->u.a.inbuffer);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
PKT_INT, c->remoteid, PKT_INT, c->localid,
PKT_END);
ssh_pkt_getstring(pktin, &host, &hostsize);
port = ssh_pkt_getuint32(pktin);
- pf.dhost = dupprintf("%.*s", hostsize, host);
+ pf.dhost = dupprintf("%.*s", hostsize, NULLTOEMPTY(host));
pf.dport = port;
pfp = find234(ssh->rportfwds, &pf, NULL);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_FAILURE,
PKT_INT, remoteid, PKT_END);
} else {
+ ssh_channel_init(c);
c->remoteid = remoteid;
c->halfopen = FALSE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->pending_eof = FALSE;
- c->throttling_conn = 0;
c->type = CHAN_SOCKDATA; /* identify channel type */
- add234(ssh->channels, c);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
PKT_INT, c->remoteid, PKT_INT,
c->localid, PKT_END);
static void ssh1_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
{
- unsigned int remoteid = ssh_pkt_getuint32(pktin);
- unsigned int localid = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &remoteid, ssh_channelfind);
- if (c && c->type == CHAN_SOCKDATA_DORMANT) {
- c->remoteid = localid;
+ c = ssh_channel_msg(ssh, pktin);
+ if (c && c->type == CHAN_SOCKDATA) {
+ c->remoteid = ssh_pkt_getuint32(pktin);
c->halfopen = FALSE;
- c->type = CHAN_SOCKDATA;
c->throttling_conn = 0;
pfd_confirm(c->u.pfd.pf);
}
static void ssh1_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
{
- unsigned int remoteid = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &remoteid, ssh_channelfind);
- if (c && c->type == CHAN_SOCKDATA_DORMANT) {
+ c = ssh_channel_msg(ssh, pktin);
+ if (c && c->type == CHAN_SOCKDATA) {
logevent("Forwarded connection refused by server");
pfd_close(c->u.pfd.pf);
del234(ssh->channels, c);
static void ssh1_msg_channel_close(Ssh ssh, struct Packet *pktin)
{
/* Remote side closes a channel. */
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
- if (c && !c->halfopen) {
- if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
- !(c->closes & CLOSES_RCVD_EOF)) {
+ c = ssh_channel_msg(ssh, pktin);
+ if (c) {
+
+ if (pktin->type == SSH1_MSG_CHANNEL_CLOSE) {
/*
* Received CHANNEL_CLOSE, which we translate into
* outgoing EOF.
*/
- int send_close = FALSE;
-
- c->closes |= CLOSES_RCVD_EOF;
-
- switch (c->type) {
- case CHAN_X11:
- if (c->u.x11.xconn)
- x11_send_eof(c->u.x11.xconn);
- else
- send_close = TRUE;
- break;
- case CHAN_SOCKDATA:
- if (c->u.pfd.pf)
- pfd_send_eof(c->u.pfd.pf);
- else
- send_close = TRUE;
- break;
- case CHAN_AGENT:
- send_close = TRUE;
- break;
- }
-
- if (send_close && !(c->closes & CLOSES_SENT_EOF)) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- c->closes |= CLOSES_SENT_EOF;
- }
+ ssh_channel_got_eof(c);
}
if (pktin->type == SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION &&
!(c->closes & CLOSES_RCVD_CLOSE)) {
if (!(c->closes & CLOSES_SENT_EOF)) {
- bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %d"
- " for which we never sent CHANNEL_CLOSE\n", i));
+ bombout(("Received CHANNEL_CLOSE_CONFIRMATION for channel %u"
+ " for which we never sent CHANNEL_CLOSE\n",
+ c->localid));
}
c->closes |= CLOSES_RCVD_CLOSE;
if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes))
ssh_channel_destroy(c);
- } else {
- bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
- pktin->type == SSH1_MSG_CHANNEL_CLOSE ? "" :
- "_CONFIRMATION", c ? "half-open" : "nonexistent",
- i));
}
}
+/*
+ * Handle incoming data on an SSH-1 or SSH-2 agent-forwarding channel.
+ */
+static int ssh_agent_channel_data(struct ssh_channel *c, char *data,
+ int length)
+{
+ bufchain_add(&c->u.a.inbuffer, data, length);
+ ssh_agentf_try_forward(c);
+
+ /*
+ * We exert back-pressure on an agent forwarding client if and
+ * only if we're waiting for the response to an asynchronous agent
+ * request. This prevents the client running out of window while
+ * receiving the _first_ message, but means that if any message
+ * takes time to process, the client will be discouraged from
+ * sending an endless stream of further ones after it.
+ */
+ return (c->u.a.pending ? bufchain_size(&c->u.a.inbuffer) : 0);
+}
+
+static int ssh_channel_data(struct ssh_channel *c, int is_stderr,
+ char *data, int length)
+{
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ return from_backend(c->ssh->frontend, is_stderr, data, length);
+ case CHAN_X11:
+ return x11_send(c->u.x11.xconn, data, length);
+ case CHAN_SOCKDATA:
+ return pfd_send(c->u.pfd.pf, data, length);
+ case CHAN_AGENT:
+ return ssh_agent_channel_data(c, data, length);
+ }
+ return 0;
+}
+
static void ssh1_msg_channel_data(Ssh ssh, struct Packet *pktin)
{
/* Data sent down one of our channels. */
- int i = ssh_pkt_getuint32(pktin);
char *p;
int len;
struct ssh_channel *c;
+ c = ssh_channel_msg(ssh, pktin);
ssh_pkt_getstring(pktin, &p, &len);
- c = find234(ssh->channels, &i, ssh_channelfind);
if (c) {
- int bufsize = 0;
- switch (c->type) {
- case CHAN_X11:
- bufsize = x11_send(c->u.x11.xconn, p, len);
- break;
- case CHAN_SOCKDATA:
- bufsize = pfd_send(c->u.pfd.pf, p, len);
- break;
- case CHAN_AGENT:
- /* Data for an agent message. Buffer it. */
- while (len > 0) {
- if (c->u.a.lensofar < 4) {
- unsigned int l = min(4 - c->u.a.lensofar, (unsigned)len);
- memcpy(c->u.a.msglen + c->u.a.lensofar, p,
- l);
- p += l;
- len -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == 4) {
- c->u.a.totallen =
- 4 + GET_32BIT(c->u.a.msglen);
- c->u.a.message = snewn(c->u.a.totallen,
- unsigned char);
- memcpy(c->u.a.message, c->u.a.msglen, 4);
- }
- if (c->u.a.lensofar >= 4 && len > 0) {
- unsigned int l =
- min(c->u.a.totallen - c->u.a.lensofar,
- (unsigned)len);
- memcpy(c->u.a.message + c->u.a.lensofar, p,
- l);
- p += l;
- len -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == c->u.a.totallen) {
- void *reply;
- int replylen;
- c->u.a.outstanding_requests++;
- if (agent_query(c->u.a.message,
- c->u.a.totallen,
- &reply, &replylen,
- ssh_agentf_callback, c))
- ssh_agentf_callback(c, reply, replylen);
- sfree(c->u.a.message);
- c->u.a.lensofar = 0;
- }
- }
- bufsize = 0; /* agent channels never back up */
- break;
- }
+ int bufsize = ssh_channel_data(c, FALSE, p, len);
if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
c->throttling_conn = 1;
ssh_throttle_conn(ssh, +1);
}
/* Helper function to deal with sending tty modes for REQUEST_PTY */
-static void ssh1_send_ttymode(void *data, char *mode, char *val)
+static void ssh1_send_ttymode(void *data,
+ const struct ssh_ttymode *mode, char *val)
{
struct Packet *pktout = (struct Packet *)data;
- int i = 0;
unsigned int arg = 0;
- while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
- if (i == lenof(ssh_ttymodes)) return;
- switch (ssh_ttymodes[i].type) {
+
+ switch (mode->type) {
case TTY_OP_CHAR:
arg = ssh_tty_parse_specchar(val);
break;
arg = ssh_tty_parse_boolean(val);
break;
}
- ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_addbyte(pktout, mode->opcode);
ssh2_pkt_addbyte(pktout, arg);
}
int msglen;
ssh_pkt_getstring(pktin, &msg, &msglen);
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
}
static void ssh1_msg_disconnect(Ssh ssh, struct Packet *pktin)
int msglen;
ssh_pkt_getstring(pktin, &msg, &msglen);
- bombout(("Server sent disconnect message:\n\"%.*s\"", msglen, msg));
+ bombout(("Server sent disconnect message:\n\"%.*s\"",
+ msglen, NULLTOEMPTY(msg)));
}
static void ssh_msg_ignore(Ssh ssh, struct Packet *pktin)
/*
- * SSH-2 key creation method.
- * (Currently assumes 2 lots of any hash are sufficient to generate
- * keys/IVs for any cipher/MAC. SSH2_MKKEY_ITERS documents this assumption.)
+ * SSH-2 key derivation (RFC 4253 section 7.2).
*/
-#define SSH2_MKKEY_ITERS (2)
-static void ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H, char chr,
- unsigned char *keyspace)
+static unsigned char *ssh2_mkkey(Ssh ssh, Bignum K, unsigned char *H,
+ char chr, int keylen)
{
const struct ssh_hash *h = ssh->kex->hash;
- void *s;
+ int keylen_padded;
+ unsigned char *key;
+ void *s, *s2;
+
+ if (keylen == 0)
+ return NULL;
+
+ /* Round up to the next multiple of hash length. */
+ keylen_padded = ((keylen + h->hlen - 1) / h->hlen) * h->hlen;
+
+ key = snewn(keylen_padded, unsigned char);
+
/* First hlen bytes. */
s = h->init();
if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
h->bytes(s, H, h->hlen);
h->bytes(s, &chr, 1);
h->bytes(s, ssh->v2_session_id, ssh->v2_session_id_len);
- h->final(s, keyspace);
- /* Next hlen bytes. */
- s = h->init();
- if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
- hash_mpint(h, s, K);
- h->bytes(s, H, h->hlen);
- h->bytes(s, keyspace, h->hlen);
- h->final(s, keyspace + h->hlen);
+ h->final(s, key);
+
+ /* Subsequent blocks of hlen bytes. */
+ if (keylen_padded > h->hlen) {
+ int offset;
+
+ s = h->init();
+ if (!(ssh->remote_bugs & BUG_SSH2_DERIVEKEY))
+ hash_mpint(h, s, K);
+ h->bytes(s, H, h->hlen);
+
+ for (offset = h->hlen; offset < keylen_padded; offset += h->hlen) {
+ h->bytes(s, key + offset - h->hlen, h->hlen);
+ s2 = h->copy(s);
+ h->final(s2, key + offset);
+ }
+
+ h->free(s);
+ }
+
+ /* Now clear any extra bytes of key material beyond the length
+ * we're officially returning, because the caller won't know to
+ * smemclr those. */
+ if (keylen_padded > keylen)
+ smemclr(key + keylen, keylen_padded - keylen);
+
+ return key;
}
/*
const struct ssh_kex *kex;
int warn;
} kex;
- const struct ssh_signkey *hostkey;
+ struct {
+ const struct ssh_signkey *hostkey;
+ int warn;
+ } hk;
struct {
const struct ssh2_cipher *cipher;
int warn;
return &list[i];
}
assert(!"No space in KEXINIT list");
+ return NULL;
}
/*
"server-to-client compression method" };
struct do_ssh2_transport_state {
int crLine;
- int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
+ int nbits, pbits, warn_kex, warn_hk, warn_cscipher, warn_sccipher;
Bignum p, g, e, f, K;
void *our_kexinit;
int our_kexinitlen;
int kex_init_value, kex_reply_value;
- const struct ssh_mac **maclist;
+ const struct ssh_mac *const *maclist;
int nmacs;
const struct ssh2_cipher *cscipher_tobe;
const struct ssh2_cipher *sccipher_tobe;
unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
int n_preferred_kex;
const struct ssh_kexes *preferred_kex[KEX_MAX];
+ int n_preferred_hk;
+ int preferred_hk[HK_MAX];
int n_preferred_ciphers;
const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
const struct ssh_compress *preferred_comp;
crState(do_ssh2_transport_state);
assert(!ssh->bare_connection);
+ assert(ssh->version == 2);
crBeginState;
}
}
+ /*
+ * Set up the preferred host key types. These are just the ids
+ * in the enum in putty.h, so 'warn below here' is indicated
+ * by HK_WARN.
+ */
+ s->n_preferred_hk = 0;
+ for (i = 0; i < HK_MAX; i++) {
+ int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, i);
+ /* As above, don't bother with HK_WARN if it's last in the
+ * list */
+ if (id != HK_WARN || i < HK_MAX - 1)
+ s->preferred_hk[s->n_preferred_hk++] = id;
+ }
+
/*
* Set up the preferred ciphers. (NULL => warn below here)
*/
* In the first key exchange, we list all the algorithms
* we're prepared to cope with, but prefer those algorithms
* for which we have a host key for this host.
+ *
+ * If the host key algorithm is below the warning
+ * threshold, we warn even if we did already have a key
+ * for it, on the basis that if the user has just
+ * reconfigured that host key type to be warned about,
+ * they surely _do_ want to be alerted that a server
+ * they're actually connecting to is using it.
*/
- for (i = 0; i < lenof(hostkey_algs); i++) {
- if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
- hostkey_algs[i]->keytype)) {
- alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
- hostkey_algs[i]->name);
- alg->u.hostkey = hostkey_algs[i];
- }
- }
- for (i = 0; i < lenof(hostkey_algs); i++) {
- alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
- hostkey_algs[i]->name);
- alg->u.hostkey = hostkey_algs[i];
+ warn = FALSE;
+ for (i = 0; i < s->n_preferred_hk; i++) {
+ if (s->preferred_hk[i] == HK_WARN)
+ warn = TRUE;
+ for (j = 0; j < lenof(hostkey_algs); j++) {
+ if (hostkey_algs[j].id != s->preferred_hk[i])
+ continue;
+ if (have_ssh_host_key(ssh->savedhost, ssh->savedport,
+ hostkey_algs[j].alg->keytype)) {
+ alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+ hostkey_algs[j].alg->name);
+ alg->u.hk.hostkey = hostkey_algs[j].alg;
+ alg->u.hk.warn = warn;
+ }
+ }
}
+ warn = FALSE;
+ for (i = 0; i < s->n_preferred_hk; i++) {
+ if (s->preferred_hk[i] == HK_WARN)
+ warn = TRUE;
+ for (j = 0; j < lenof(hostkey_algs); j++) {
+ if (hostkey_algs[j].id != s->preferred_hk[i])
+ continue;
+ alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
+ hostkey_algs[j].alg->name);
+ alg->u.hk.hostkey = hostkey_algs[j].alg;
+ alg->u.hk.warn = warn;
+ }
+ }
} else {
/*
* In subsequent key exchanges, we list only the kex
assert(ssh->kex);
alg = ssh2_kexinit_addalg(s->kexlists[KEXLIST_HOSTKEY],
ssh->hostkey->name);
- alg->u.hostkey = ssh->hostkey;
+ alg->u.hk.hostkey = ssh->hostkey;
+ alg->u.hk.warn = FALSE;
}
/* List encryption algorithms (client->server then server->client). */
for (k = KEXLIST_CSCIPHER; k <= KEXLIST_SCCIPHER; k++) {
warn = FALSE;
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[k], "none");
+ alg->u.cipher.cipher = NULL;
+ alg->u.cipher.warn = warn;
+#endif /* FUZZING */
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) warn = TRUE;
}
/* List MAC algorithms (client->server then server->client). */
for (j = KEXLIST_CSMAC; j <= KEXLIST_SCMAC; j++) {
+#ifdef FUZZING
+ alg = ssh2_kexinit_addalg(s->kexlists[j], "none");
+ alg->u.mac.mac = NULL;
+ alg->u.mac.etm = FALSE;
+#endif /* FUZZING */
for (i = 0; i < s->nmacs; i++) {
alg = ssh2_kexinit_addalg(s->kexlists[j], s->maclist[i]->name);
alg->u.mac.mac = s->maclist[i];
s->scmac_tobe = NULL;
s->cscomp_tobe = NULL;
s->sccomp_tobe = NULL;
- s->warn_kex = s->warn_cscipher = s->warn_sccipher = FALSE;
+ s->warn_kex = s->warn_hk = FALSE;
+ s->warn_cscipher = s->warn_sccipher = FALSE;
pktin->savedpos += 16; /* skip garbage cookie */
bombout(("KEXINIT packet was incomplete"));
crStopV;
}
+
+ /* If we've already selected a cipher which requires a
+ * particular MAC, then just select that, and don't even
+ * bother looking through the server's KEXINIT string for
+ * MACs. */
+ if (i == KEXLIST_CSMAC && s->cscipher_tobe &&
+ s->cscipher_tobe->required_mac) {
+ s->csmac_tobe = s->cscipher_tobe->required_mac;
+ s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
+ goto matched;
+ }
+ if (i == KEXLIST_SCMAC && s->sccipher_tobe &&
+ s->sccipher_tobe->required_mac) {
+ s->scmac_tobe = s->sccipher_tobe->required_mac;
+ s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
+ goto matched;
+ }
+
for (j = 0; j < MAXKEXLIST; j++) {
struct kexinit_algorithm *alg = &s->kexlists[i][j];
if (alg->name == NULL) break;
ssh->kex = alg->u.kex.kex;
s->warn_kex = alg->u.kex.warn;
} else if (i == KEXLIST_HOSTKEY) {
- ssh->hostkey = alg->u.hostkey;
+ ssh->hostkey = alg->u.hk.hostkey;
+ s->warn_hk = alg->u.hk.warn;
} else if (i == KEXLIST_CSCIPHER) {
s->cscipher_tobe = alg->u.cipher.cipher;
s->warn_cscipher = alg->u.cipher.warn;
in_commasep_string(alg->u.comp->delayed_name, str, len))
s->pending_compression = TRUE; /* try this later */
}
- bombout(("Couldn't agree a %s ((available: %.*s)",
+ bombout(("Couldn't agree a %s (available: %.*s)",
kexlist_descr[i], len, str));
crStopV;
matched:;
- }
- /* If the cipher over-rides the mac, then pick it */
- if (s->cscipher_tobe && s->cscipher_tobe->required_mac) {
- s->csmac_tobe = s->cscipher_tobe->required_mac;
- s->csmac_etm_tobe = !!(s->csmac_tobe->etm_name);
- }
- if (s->sccipher_tobe && s->sccipher_tobe->required_mac) {
- s->scmac_tobe = s->sccipher_tobe->required_mac;
- s->scmac_etm_tobe = !!(s->scmac_tobe->etm_name);
- }
+ if (i == KEXLIST_HOSTKEY) {
+ int j;
+
+ /*
+ * In addition to deciding which host key we're
+ * actually going to use, we should make a list of the
+ * host keys offered by the server which we _don't_
+ * have cached. These will be offered as cross-
+ * certification options by ssh_get_specials.
+ *
+ * We also count the key we're currently using for KEX
+ * as one we've already got, because by the time this
+ * menu becomes visible, it will be.
+ */
+ ssh->n_uncert_hostkeys = 0;
+
+ for (j = 0; j < lenof(hostkey_algs); j++) {
+ if (hostkey_algs[j].alg != ssh->hostkey &&
+ in_commasep_string(hostkey_algs[j].alg->name,
+ str, len) &&
+ !have_ssh_host_key(ssh->savedhost, ssh->savedport,
+ hostkey_algs[j].alg->keytype)) {
+ ssh->uncert_hostkeys[ssh->n_uncert_hostkeys++] = j;
+ }
+ }
+ }
+ }
if (s->pending_compression) {
logevent("Server supports delayed compression; "
}
}
+ if (s->warn_hk) {
+ int j, k;
+ char *betteralgs;
+
+ ssh_set_frozen(ssh, 1);
+
+ /*
+ * Change warning box wording depending on why we chose a
+ * warning-level host key algorithm. If it's because
+ * that's all we have *cached*, use the askhk mechanism,
+ * and list the host keys we could usefully cross-certify.
+ * Otherwise, use askalg for the standard wording.
+ */
+ betteralgs = NULL;
+ for (j = 0; j < ssh->n_uncert_hostkeys; j++) {
+ const struct ssh_signkey_with_user_pref_id *hktype =
+ &hostkey_algs[ssh->uncert_hostkeys[j]];
+ int better = FALSE;
+ for (k = 0; k < HK_MAX; k++) {
+ int id = conf_get_int_int(ssh->conf, CONF_ssh_hklist, k);
+ if (id == HK_WARN) {
+ break;
+ } else if (id == hktype->id) {
+ better = TRUE;
+ break;
+ }
+ }
+ if (better) {
+ if (betteralgs) {
+ char *old_ba = betteralgs;
+ betteralgs = dupcat(betteralgs, ",",
+ hktype->alg->name,
+ (const char *)NULL);
+ sfree(old_ba);
+ } else {
+ betteralgs = dupstr(hktype->alg->name);
+ }
+ }
+ }
+ if (betteralgs) {
+ s->dlgret = askhk(ssh->frontend, ssh->hostkey->name,
+ betteralgs, ssh_dialog_callback, ssh);
+ sfree(betteralgs);
+ } else {
+ s->dlgret = askalg(ssh->frontend, "host key type",
+ ssh->hostkey->name,
+ ssh_dialog_callback, ssh);
+ }
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while"
+ " waiting for user response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at host key warning", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ }
+
if (s->warn_cscipher) {
ssh_set_frozen(ssh, 1);
s->dlgret = askalg(ssh->frontend,
{
int csbits, scbits;
- csbits = s->cscipher_tobe->keylen;
- scbits = s->sccipher_tobe->keylen;
+ csbits = s->cscipher_tobe ? s->cscipher_tobe->real_keybits : 0;
+ scbits = s->sccipher_tobe ? s->sccipher_tobe->real_keybits : 0;
s->nbits = (csbits > scbits ? csbits : scbits);
}
/* The keys only have hlen-bit entropy, since they're based on
dmemdump(s->exchange_hash, ssh->kex->hash->hlen);
#endif
- if (!s->hkey ||
- !ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
+ if (!s->hkey) {
+ bombout(("Server's host key is invalid"));
+ crStopV;
+ }
+
+ if (!ssh->hostkey->verifysig(s->hkey, s->sigdata, s->siglen,
(char *)s->exchange_hash,
ssh->kex->hash->hlen)) {
+#ifndef FUZZING
bombout(("Server's host key did not match the signature supplied"));
crStopV;
+#endif
}
s->keystr = ssh->hostkey->fmtkey(s->hkey);
if (!s->got_session_id) {
+ /*
+ * Make a note of any other host key formats that are available.
+ */
+ {
+ int i, j, nkeys = 0;
+ char *list = NULL;
+ for (i = 0; i < lenof(hostkey_algs); i++) {
+ if (hostkey_algs[i].alg == ssh->hostkey)
+ continue;
+
+ for (j = 0; j < ssh->n_uncert_hostkeys; j++)
+ if (ssh->uncert_hostkeys[j] == i)
+ break;
+
+ if (j < ssh->n_uncert_hostkeys) {
+ char *newlist;
+ if (list)
+ newlist = dupprintf("%s/%s", list,
+ hostkey_algs[i].alg->name);
+ else
+ newlist = dupprintf("%s", hostkey_algs[i].alg->name);
+ sfree(list);
+ list = newlist;
+ nkeys++;
+ }
+ }
+ if (list) {
+ logeventf(ssh,
+ "Server also has %s host key%s, but we "
+ "don't know %s", list,
+ nkeys > 1 ? "s" : "",
+ nkeys > 1 ? "any of them" : "it");
+ sfree(list);
+ }
+ }
+
/*
* Authenticate remote host: verify host key. (We've already
* checked the signature of the exchange hash.)
ssh->hostkey->keytype, s->keystr,
s->fingerprint,
ssh_dialog_callback, ssh);
+#ifdef FUZZING
+ s->dlgret = 1;
+#endif
if (s->dlgret < 0) {
do {
crReturnV;
* subsequent rekeys.
*/
ssh->hostkey_str = s->keystr;
+ } else if (ssh->cross_certifying) {
+ s->fingerprint = ssh2_fingerprint(ssh->hostkey, s->hkey);
+ logevent("Storing additional host key for this host:");
+ logevent(s->fingerprint);
+ sfree(s->fingerprint);
+ store_host_key(ssh->savedhost, ssh->savedport,
+ ssh->hostkey->keytype, s->keystr);
+ ssh->cross_certifying = FALSE;
+ /*
+ * Don't forget to store the new key as the one we'll be
+ * re-checking in future normal rekeys.
+ */
+ ssh->hostkey_str = s->keystr;
} else {
/*
* In a rekey, we never present an interactive host key
* the one we saw before.
*/
if (strcmp(ssh->hostkey_str, s->keystr)) {
+#ifndef FUZZING
bombout(("Host key was different in repeat key exchange"));
crStopV;
+#endif
}
sfree(s->keystr);
}
if (ssh->cs_cipher_ctx)
ssh->cscipher->free_context(ssh->cs_cipher_ctx);
ssh->cscipher = s->cscipher_tobe;
- ssh->cs_cipher_ctx = ssh->cscipher->make_context();
+ if (ssh->cscipher) ssh->cs_cipher_ctx = ssh->cscipher->make_context();
if (ssh->cs_mac_ctx)
ssh->csmac->free_context(ssh->cs_mac_ctx);
ssh->csmac = s->csmac_tobe;
ssh->csmac_etm = s->csmac_etm_tobe;
- ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
+ if (ssh->csmac)
+ ssh->cs_mac_ctx = ssh->csmac->make_context(ssh->cs_cipher_ctx);
if (ssh->cs_comp_ctx)
ssh->cscomp->compress_cleanup(ssh->cs_comp_ctx);
* Set IVs on client-to-server keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
- unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
- assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'C',keyspace);
- assert((ssh->cscipher->keylen+7) / 8 <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->cscipher->setkey(ssh->cs_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'A',keyspace);
- assert(ssh->cscipher->blksize <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->cscipher->setiv(ssh->cs_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'E',keyspace);
- assert(ssh->csmac->len <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
- smemclr(keyspace, sizeof(keyspace));
- }
-
- logeventf(ssh, "Initialised %.200s client->server encryption",
- ssh->cscipher->text_name);
- logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
- ssh->csmac->text_name,
- ssh->csmac_etm ? " (in ETM mode)" : "",
- ssh->cscipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->cscipher) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'C',
+ ssh->cscipher->padded_keybytes);
+ ssh->cscipher->setkey(ssh->cs_cipher_ctx, key);
+ smemclr(key, ssh->cscipher->padded_keybytes);
+ sfree(key);
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'A',
+ ssh->cscipher->blksize);
+ ssh->cscipher->setiv(ssh->cs_cipher_ctx, key);
+ smemclr(key, ssh->cscipher->blksize);
+ sfree(key);
+ }
+ if (ssh->csmac) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'E',
+ ssh->csmac->keylen);
+ ssh->csmac->setkey(ssh->cs_mac_ctx, key);
+ smemclr(key, ssh->csmac->keylen);
+ sfree(key);
+ }
+
+ if (ssh->cscipher)
+ logeventf(ssh, "Initialised %.200s client->server encryption",
+ ssh->cscipher->text_name);
+ if (ssh->csmac)
+ logeventf(ssh, "Initialised %.200s client->server MAC algorithm%s%s",
+ ssh->csmac->text_name,
+ ssh->csmac_etm ? " (in ETM mode)" : "",
+ ssh->cscipher->required_mac ? " (required by cipher)" : "");
if (ssh->cscomp->text_name)
logeventf(ssh, "Initialised %s compression",
ssh->cscomp->text_name);
*/
if (ssh->sc_cipher_ctx)
ssh->sccipher->free_context(ssh->sc_cipher_ctx);
- ssh->sccipher = s->sccipher_tobe;
- ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ if (s->sccipher_tobe) {
+ ssh->sccipher = s->sccipher_tobe;
+ ssh->sc_cipher_ctx = ssh->sccipher->make_context();
+ }
if (ssh->sc_mac_ctx)
ssh->scmac->free_context(ssh->sc_mac_ctx);
- ssh->scmac = s->scmac_tobe;
- ssh->scmac_etm = s->scmac_etm_tobe;
- ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ if (s->scmac_tobe) {
+ ssh->scmac = s->scmac_tobe;
+ ssh->scmac_etm = s->scmac_etm_tobe;
+ ssh->sc_mac_ctx = ssh->scmac->make_context(ssh->sc_cipher_ctx);
+ }
if (ssh->sc_comp_ctx)
ssh->sccomp->decompress_cleanup(ssh->sc_comp_ctx);
* Set IVs on server-to-client keys. Here we use the exchange
* hash from the _first_ key exchange.
*/
- {
- unsigned char keyspace[SSH2_KEX_MAX_HASH_LEN * SSH2_MKKEY_ITERS];
- assert(sizeof(keyspace) >= ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'D',keyspace);
- assert((ssh->sccipher->keylen+7) / 8 <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->sccipher->setkey(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'B',keyspace);
- assert(ssh->sccipher->blksize <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->sccipher->setiv(ssh->sc_cipher_ctx, keyspace);
- ssh2_mkkey(ssh,s->K,s->exchange_hash,'F',keyspace);
- assert(ssh->scmac->len <=
- ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
- ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
- smemclr(keyspace, sizeof(keyspace));
- }
- logeventf(ssh, "Initialised %.200s server->client encryption",
- ssh->sccipher->text_name);
- logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
- ssh->scmac->text_name,
- ssh->scmac_etm ? " (in ETM mode)" : "",
- ssh->sccipher->required_mac ? " (required by cipher)" : "");
+ if (ssh->sccipher) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'D',
+ ssh->sccipher->padded_keybytes);
+ ssh->sccipher->setkey(ssh->sc_cipher_ctx, key);
+ smemclr(key, ssh->sccipher->padded_keybytes);
+ sfree(key);
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'B',
+ ssh->sccipher->blksize);
+ ssh->sccipher->setiv(ssh->sc_cipher_ctx, key);
+ smemclr(key, ssh->sccipher->blksize);
+ sfree(key);
+ }
+ if (ssh->scmac) {
+ unsigned char *key;
+
+ key = ssh2_mkkey(ssh, s->K, s->exchange_hash, 'F',
+ ssh->scmac->keylen);
+ ssh->scmac->setkey(ssh->sc_mac_ctx, key);
+ smemclr(key, ssh->scmac->keylen);
+ sfree(key);
+ }
+ if (ssh->sccipher)
+ logeventf(ssh, "Initialised %.200s server->client encryption",
+ ssh->sccipher->text_name);
+ if (ssh->scmac)
+ logeventf(ssh, "Initialised %.200s server->client MAC algorithm%s%s",
+ ssh->scmac->text_name,
+ ssh->scmac_etm ? " (in ETM mode)" : "",
+ ssh->sccipher->required_mac ? " (required by cipher)" : "");
if (ssh->sccomp->text_name)
logeventf(ssh, "Initialised %s decompression",
ssh->sccomp->text_name);
*/
freebn(s->K);
+ /*
+ * Update the specials menu to list the remaining uncertified host
+ * keys.
+ */
+ update_specials_menu(ssh->frontend);
+
/*
* Key exchange is over. Loop straight back round if we have a
* deferred rekey reason.
}
/*
- * Add data to an SSH-2 channel output buffer.
+ * Send data on an SSH channel. In SSH-2, this involves buffering it
+ * first.
*/
-static void ssh2_add_channel_data(struct ssh_channel *c, const char *buf,
- int len)
+static int ssh_send_channel_data(struct ssh_channel *c, const char *buf,
+ int len)
{
- bufchain_add(&c->v.v2.outbuffer, buf, len);
+ if (c->ssh->version == 2) {
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+ return ssh2_try_send(c);
+ } else {
+ send_packet(c->ssh, SSH1_MSG_CHANNEL_DATA,
+ PKT_INT, c->remoteid,
+ PKT_INT, len,
+ PKT_DATA, buf, len,
+ PKT_END);
+ /*
+ * In SSH-1 we can return 0 here - implying that channels are
+ * never individually throttled - because the only
+ * circumstance that can cause throttling will be the whole
+ * SSH connection backing up, in which case _everything_ will
+ * be throttled as a whole.
+ */
+ return 0;
+ }
}
/*
x11_unthrottle(c->u.x11.xconn);
break;
case CHAN_AGENT:
- /* agent sockets are request/response and need no
- * buffer management */
+ /* Now that we've successfully sent all the outgoing
+ * replies we had, try to process more incoming data. */
+ ssh_agentf_try_forward(c);
break;
case CHAN_SOCKDATA:
pfd_unthrottle(c->u.pfd.pf);
}
/*
- * Set up most of a new ssh_channel for SSH-2.
+ * Set up most of a new ssh_channel.
*/
-static void ssh2_channel_init(struct ssh_channel *c)
+static void ssh_channel_init(struct ssh_channel *c)
{
Ssh ssh = c->ssh;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
c->pending_eof = FALSE;
c->throttling_conn = FALSE;
- c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
- ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
- c->v.v2.chanreq_head = NULL;
- c->v.v2.throttle_state = UNTHROTTLED;
- bufchain_init(&c->v.v2.outbuffer);
+ if (ssh->version == 2) {
+ c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+ ssh_is_simple(ssh) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ c->v.v2.chanreq_head = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
+ bufchain_init(&c->v.v2.outbuffer);
+ }
+ add234(ssh->channels, c);
}
/*
return pktout;
}
+static void ssh_channel_unthrottle(struct ssh_channel *c, int bufsize)
+{
+ Ssh ssh = c->ssh;
+ int buflimit;
+
+ if (ssh->version == 1) {
+ buflimit = SSH1_BUFFER_LIMIT;
+ } else {
+ if (ssh_is_simple(ssh))
+ buflimit = 0;
+ else
+ buflimit = c->v.v2.locmaxwin;
+ if (bufsize < buflimit)
+ ssh2_set_window(c, buflimit - bufsize);
+ }
+ if (c->throttling_conn && bufsize <= buflimit) {
+ c->throttling_conn = 0;
+ ssh_throttle_conn(ssh, -1);
+ }
+}
+
/*
* Potentially enlarge the window on an SSH-2 channel.
*/
/*
* Find the channel associated with a message. If there's no channel,
* or it's not properly open, make a noise about it and return NULL.
+ * If the channel is shared, pass the message on to downstream and
+ * also return NULL (meaning the caller should ignore this message).
*/
-static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+static struct ssh_channel *ssh_channel_msg(Ssh ssh, struct Packet *pktin)
{
unsigned localid = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
+ int halfopen_ok;
+ /* Is this message OK on a half-open connection? */
+ if (ssh->version == 1)
+ halfopen_ok = (pktin->type == SSH1_MSG_CHANNEL_OPEN_CONFIRMATION ||
+ pktin->type == SSH1_MSG_CHANNEL_OPEN_FAILURE);
+ else
+ halfopen_ok = (pktin->type == SSH2_MSG_CHANNEL_OPEN_CONFIRMATION ||
+ pktin->type == SSH2_MSG_CHANNEL_OPEN_FAILURE);
c = find234(ssh->channels, &localid, ssh_channelfind);
- if (!c ||
- (c->type != CHAN_SHARING && c->halfopen &&
- pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
- pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+ if (!c || (c->type != CHAN_SHARING && (c->halfopen != halfopen_ok))) {
char *buf = dupprintf("Received %s for %s channel %u",
- ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
- pktin->type),
- c ? "half-open" : "nonexistent", localid);
+ ssh_pkt_type(ssh, pktin->type),
+ !c ? "nonexistent" :
+ c->halfopen ? "half-open" : "open",
+ localid);
ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
sfree(buf);
return NULL;
}
+ if (c->type == CHAN_SHARING) {
+ share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
+ pktin->body, pktin->length);
+ return NULL;
+ }
return c;
}
static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
{
- struct ssh_channel *c = ssh2_channel_msg(ssh, pktin);
+ struct ssh_channel *c = ssh_channel_msg(ssh, pktin);
struct outstanding_channel_request *ocr;
if (!c) return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
ocr = c->v.v2.chanreq_head;
if (!ocr) {
ssh2_msg_unexpected(ssh, pktin);
static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
{
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
if (!(c->closes & CLOSES_SENT_EOF)) {
c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
ssh2_try_send_and_unthrottle(ssh, c);
{
char *data;
int length;
+ unsigned ext_type = 0; /* 0 means not extended */
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
- if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
- ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
- return; /* extended but not stderr */
+ if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA)
+ ext_type = ssh_pkt_getuint32(pktin);
ssh_pkt_getstring(pktin, &data, &length);
if (data) {
- int bufsize = 0;
+ int bufsize;
c->v.v2.locwindow -= length;
c->v.v2.remlocwin -= length;
- switch (c->type) {
- case CHAN_MAINSESSION:
- bufsize =
- from_backend(ssh->frontend, pktin->type ==
- SSH2_MSG_CHANNEL_EXTENDED_DATA,
- data, length);
- break;
- case CHAN_X11:
- bufsize = x11_send(c->u.x11.xconn, data, length);
- break;
- case CHAN_SOCKDATA:
- bufsize = pfd_send(c->u.pfd.pf, data, length);
- break;
- case CHAN_AGENT:
- while (length > 0) {
- if (c->u.a.lensofar < 4) {
- unsigned int l = min(4 - c->u.a.lensofar,
- (unsigned)length);
- memcpy(c->u.a.msglen + c->u.a.lensofar,
- data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == 4) {
- c->u.a.totallen =
- 4 + GET_32BIT(c->u.a.msglen);
- c->u.a.message = snewn(c->u.a.totallen,
- unsigned char);
- memcpy(c->u.a.message, c->u.a.msglen, 4);
- }
- if (c->u.a.lensofar >= 4 && length > 0) {
- unsigned int l =
- min(c->u.a.totallen - c->u.a.lensofar,
- (unsigned)length);
- memcpy(c->u.a.message + c->u.a.lensofar,
- data, l);
- data += l;
- length -= l;
- c->u.a.lensofar += l;
- }
- if (c->u.a.lensofar == c->u.a.totallen) {
- void *reply;
- int replylen;
- c->u.a.outstanding_requests++;
- if (agent_query(c->u.a.message,
- c->u.a.totallen,
- &reply, &replylen,
- ssh_agentf_callback, c))
- ssh_agentf_callback(c, reply, replylen);
- sfree(c->u.a.message);
- c->u.a.message = NULL;
- c->u.a.lensofar = 0;
- }
- }
- bufsize = 0;
- break;
- }
+ if (ext_type != 0 && ext_type != SSH2_EXTENDED_DATA_STDERR)
+ length = 0; /* Don't do anything with unknown extended data. */
+ bufsize = ssh_channel_data(c, ext_type == SSH2_EXTENDED_DATA_STDERR,
+ data, length);
/*
* If it looks like the remote end hit the end of its window,
* and we didn't want it to do that, think about using a
* need to adjust the window if the server's
* sent excess data.
*/
- ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
- c->v.v2.locmaxwin - bufsize : 0);
+ if (bufsize < c->v.v2.locmaxwin)
+ ssh2_set_window(c, c->v.v2.locmaxwin - bufsize);
/*
* If we're either buffering way too much data, or if we're
* buffering anything at all and we're in "simple" mode,
sfree(buf);
}
-static void ssh_channel_destroy(struct ssh_channel *c)
+/*
+ * Close any local socket and free any local resources associated with
+ * a channel. This converts the channel into a CHAN_ZOMBIE.
+ */
+static void ssh_channel_close_local(struct ssh_channel *c, char const *reason)
{
Ssh ssh = c->ssh;
+ char const *msg = NULL;
switch (c->type) {
case CHAN_MAINSESSION:
update_specials_menu(ssh->frontend);
break;
case CHAN_X11:
- if (c->u.x11.xconn != NULL)
- x11_close(c->u.x11.xconn);
- logevent("Forwarded X11 connection terminated");
+ assert(c->u.x11.xconn != NULL);
+ x11_close(c->u.x11.xconn);
+ msg = "Forwarded X11 connection terminated";
break;
case CHAN_AGENT:
- sfree(c->u.a.message);
+ if (c->u.a.pending)
+ agent_cancel_query(c->u.a.pending);
+ bufchain_clear(&c->u.a.inbuffer);
+ msg = "Agent-forwarding connection closed";
break;
case CHAN_SOCKDATA:
- if (c->u.pfd.pf != NULL)
- pfd_close(c->u.pfd.pf);
- logevent("Forwarded port closed");
+ assert(c->u.pfd.pf != NULL);
+ pfd_close(c->u.pfd.pf);
+ msg = "Forwarded port closed";
break;
}
+ c->type = CHAN_ZOMBIE;
+ if (msg != NULL) {
+ if (reason != NULL)
+ logeventf(ssh, "%s %s", msg, reason);
+ else
+ logevent(msg);
+ }
+}
+
+static void ssh_channel_destroy(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ ssh_channel_close_local(c, NULL);
del234(ssh->channels, c);
if (ssh->version == 2) {
Ssh ssh = c->ssh;
struct Packet *pktout;
+ assert(ssh->version == 2);
if (c->halfopen) {
/*
* If we've sent out our own CHANNEL_OPEN but not yet seen
}
}
-static void ssh2_channel_got_eof(struct ssh_channel *c)
+static void ssh_channel_got_eof(struct ssh_channel *c)
{
if (c->closes & CLOSES_RCVD_EOF)
return; /* already seen EOF */
c->closes |= CLOSES_RCVD_EOF;
if (c->type == CHAN_X11) {
+ assert(c->u.x11.xconn != NULL);
x11_send_eof(c->u.x11.xconn);
} else if (c->type == CHAN_AGENT) {
- if (c->u.a.outstanding_requests == 0) {
- /* Manufacture an outgoing EOF in response to the incoming one. */
- sshfwd_write_eof(c);
- }
+ /* Just call try_forward, which will respond to the EOF now if
+ * appropriate, or wait until the queue of outstanding
+ * requests is dealt with if not */
+ ssh_agentf_try_forward(c);
} else if (c->type == CHAN_SOCKDATA) {
+ assert(c->u.pfd.pf != NULL);
pfd_send_eof(c->u.pfd.pf);
} else if (c->type == CHAN_MAINSESSION) {
Ssh ssh = c->ssh;
}
ssh->sent_console_eof = TRUE;
}
-
- ssh2_channel_check_close(c);
}
static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
{
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
- ssh2_channel_got_eof(c);
+ ssh_channel_got_eof(c);
+ ssh2_channel_check_close(c);
}
static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
{
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
/*
* When we receive CLOSE on a channel, we assume it comes with an
* implied EOF if we haven't seen EOF yet.
*/
- ssh2_channel_got_eof(c);
+ ssh_channel_got_eof(c);
if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
/*
{
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
- assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+ assert(c->halfopen); /* ssh_channel_msg will have enforced this */
c->remoteid = ssh_pkt_getuint32(pktin);
c->halfopen = FALSE;
c->v.v2.remwindow = ssh_pkt_getuint32(pktin);
c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- if (c->type == CHAN_SOCKDATA_DORMANT) {
- c->type = CHAN_SOCKDATA;
- if (c->u.pfd.pf)
- pfd_confirm(c->u.pfd.pf);
+ if (c->type == CHAN_SOCKDATA) {
+ assert(c->u.pfd.pf != NULL);
+ pfd_confirm(c->u.pfd.pf);
} else if (c->type == CHAN_ZOMBIE) {
/*
* This case can occur if a local socket error occurred
int reason_length;
struct ssh_channel *c;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
- assert(c->halfopen); /* ssh2_channel_msg will have enforced this */
+ assert(c->halfopen); /* ssh_channel_msg will have enforced this */
- if (c->type == CHAN_SOCKDATA_DORMANT) {
+ if (c->type == CHAN_SOCKDATA) {
reason_code = ssh_pkt_getuint32(pktin);
if (reason_code >= lenof(reasons))
reason_code = 0; /* ensure reasons[reason_code] in range */
ssh_pkt_getstring(pktin, &reason_string, &reason_length);
logeventf(ssh, "Forwarded connection refused by server: %s [%.*s]",
- reasons[reason_code], reason_length, reason_string);
+ reasons[reason_code], reason_length,
+ NULLTOEMPTY(reason_string));
pfd_close(c->u.pfd.pf);
} else if (c->type == CHAN_ZOMBIE) {
struct ssh_channel *c;
struct Packet *pktout;
- c = ssh2_channel_msg(ssh, pktin);
+ c = ssh_channel_msg(ssh, pktin);
if (!c)
return;
- if (c->type == CHAN_SHARING) {
- share_got_pkt_from_server(c->u.sharing.ctx, pktin->type,
- pktin->body, pktin->length);
- return;
- }
ssh_pkt_getstring(pktin, &type, &typelen);
want_reply = ssh2_pkt_getbool(pktin);
char *addrstr;
ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
- addrstr = snewn(peeraddrlen+1, char);
- memcpy(addrstr, peeraddr, peeraddrlen);
- addrstr[peeraddrlen] = '\0';
+ addrstr = dupprintf("%.*s", peeraddrlen, NULLTOEMPTY(peeraddr));
peerport = ssh_pkt_getuint32(pktin);
logeventf(ssh, "Received X11 connect request from %s:%d",
char *shost;
int shostlen;
ssh_pkt_getstring(pktin, &shost, &shostlen);/* skip address */
- pf.shost = dupprintf("%.*s", shostlen, shost);
+ pf.shost = dupprintf("%.*s", shostlen, NULLTOEMPTY(shost));
pf.sport = ssh_pkt_getuint32(pktin);
ssh_pkt_getstring(pktin, &peeraddr, &peeraddrlen);
peerport = ssh_pkt_getuint32(pktin);
realpf = find234(ssh->rportfwds, &pf, NULL);
logeventf(ssh, "Received remote port %s:%d open request "
- "from %s:%d", pf.shost, pf.sport, peeraddr, peerport);
+ "from %.*s:%d", pf.shost, pf.sport,
+ peeraddrlen, NULLTOEMPTY(peeraddr), peerport);
sfree(pf.shost);
if (realpf == NULL) {
error = "Agent forwarding is not enabled";
else {
c->type = CHAN_AGENT; /* identify channel type */
- c->u.a.lensofar = 0;
- c->u.a.message = NULL;
- c->u.a.outstanding_requests = 0;
+ bufchain_init(&c->u.a.inbuffer);
+ c->u.a.pending = NULL;
}
} else {
error = "Unsupported channel type requested";
logeventf(ssh, "Rejected channel open: %s", error);
sfree(c);
} else {
- ssh2_channel_init(c);
+ ssh_channel_init(c);
c->v.v2.remwindow = winsize;
c->v.v2.remmaxpkt = pktsize;
if (our_winsize_override) {
c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
our_winsize_override;
}
- add234(ssh->channels, c);
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
ssh2_pkt_adduint32(pktout, c->remoteid);
ssh2_pkt_adduint32(pktout, c->localid);
}
/* Helper function to deal with sending tty modes for "pty-req" */
-static void ssh2_send_ttymode(void *data, char *mode, char *val)
+static void ssh2_send_ttymode(void *data,
+ const struct ssh_ttymode *mode, char *val)
{
struct Packet *pktout = (struct Packet *)data;
- int i = 0;
unsigned int arg = 0;
- while (strcmp(mode, ssh_ttymodes[i].mode) != 0) i++;
- if (i == lenof(ssh_ttymodes)) return;
- switch (ssh_ttymodes[i].type) {
+
+ switch (mode->type) {
case TTY_OP_CHAR:
arg = ssh_tty_parse_specchar(val);
break;
arg = ssh_tty_parse_boolean(val);
break;
}
- ssh2_pkt_addbyte(pktout, ssh_ttymodes[i].opcode);
+ ssh2_pkt_addbyte(pktout, mode->opcode);
ssh2_pkt_adduint32(pktout, arg);
}
int pklen, alglen, commentlen;
int siglen, retlen, len;
char *q, *agentreq, *ret;
- int try_send;
struct Packet *pktout;
Filename *keyfile;
#ifndef NO_GSSAPI
/* Request the keys held by the agent. */
PUT_32BIT(s->agent_request, 1);
s->agent_request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
- if (!agent_query(s->agent_request, 5, &r, &s->agent_responselen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->agent_request, 5, &r, &s->agent_responselen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturnV;
if (pktin) {
s->q += s->pktout->length - 5;
/* And finally the (zero) flags word. */
PUT_32BIT(s->q, 0);
- if (!agent_query(s->agentreq, s->len + 4,
- &vret, &s->retlen,
- ssh_agent_callback, ssh)) {
+ ssh->auth_agent_query = agent_query(
+ s->agentreq, s->len + 4, &vret, &s->retlen,
+ ssh_agent_callback, ssh);
+ if (ssh->auth_agent_query) {
do {
crReturnV;
if (pktin) {
s->cur_prompt->to_server = TRUE;
s->cur_prompt->name = dupstr("New SSH password");
s->cur_prompt->instruction =
- dupprintf("%.*s", prompt_len, prompt);
+ dupprintf("%.*s", prompt_len, NULLTOEMPTY(prompt));
s->cur_prompt->instr_reqd = TRUE;
/*
* There's no explicit requirement in the protocol
} else {
ssh->mainchan = snew(struct ssh_channel);
ssh->mainchan->ssh = ssh;
- ssh2_channel_init(ssh->mainchan);
+ ssh_channel_init(ssh->mainchan);
if (*conf_get_str(ssh->conf, CONF_ssh_nc_host)) {
/*
ssh->mainchan->type = CHAN_MAINSESSION;
ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- add234(ssh->channels, ssh->mainchan);
update_specials_menu(ssh->frontend);
logevent("Opened main channel");
}
ssh->send_ok = 1;
while (1) {
crReturnV;
- s->try_send = FALSE;
if (pktin) {
/*
/*
* We have spare data. Add it to the channel buffer.
*/
- ssh2_add_channel_data(ssh->mainchan, (char *)in, inlen);
- s->try_send = TRUE;
- }
- if (s->try_send) {
- int i;
- struct ssh_channel *c;
- /*
- * Try to send data on all channels if we can.
- */
- for (i = 0; NULL != (c = index234(ssh->channels, i)); i++)
- if (c->type != CHAN_SHARING)
- ssh2_try_send_and_unthrottle(ssh, c);
+ ssh_send_channel_data(ssh->mainchan, (char *)in, inlen);
}
}
logevent(buf);
sfree(buf);
buf = dupprintf("Disconnection message text: %.*s",
- msglen, msg);
+ msglen, NULLTOEMPTY(msg));
logevent(buf);
bombout(("Server sent disconnect message\ntype %d (%s):\n\"%.*s\"",
reason,
(reason > 0 && reason < lenof(ssh2_disconnect_reasons)) ?
ssh2_disconnect_reasons[reason] : "unknown",
- msglen, msg));
+ msglen, NULLTOEMPTY(msg)));
sfree(buf);
}
ssh2_pkt_getbool(pktin);
ssh_pkt_getstring(pktin, &msg, &msglen);
- logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
+ logeventf(ssh, "Remote debug message: %.*s", msglen, NULLTOEMPTY(msg));
}
static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
ssh->X11_fwd_enabled = FALSE;
ssh->connshare = NULL;
ssh->attempting_connshare = FALSE;
+ ssh->session_started = FALSE;
+ ssh->specials = NULL;
+ ssh->n_uncert_hostkeys = 0;
+ ssh->cross_certifying = FALSE;
*backend_handle = ssh;
CONF_ssh_rekey_data));
ssh->kex_in_progress = FALSE;
+ ssh->auth_agent_query = NULL;
+
#ifndef NO_GSSAPI
ssh->gsslibs = NULL;
#endif
if (ssh->channels) {
while ((c = delpos234(ssh->channels, 0)) != NULL) {
- switch (c->type) {
- case CHAN_X11:
- if (c->u.x11.xconn != NULL)
- x11_close(c->u.x11.xconn);
- break;
- case CHAN_SOCKDATA:
- case CHAN_SOCKDATA_DORMANT:
- if (c->u.pfd.pf != NULL)
- pfd_close(c->u.pfd.pf);
- break;
- }
+ ssh_channel_close_local(c, NULL);
if (ssh->version == 2) {
struct outstanding_channel_request *ocr, *nocr;
ocr = c->v.v2.chanreq_head;
sfree(ssh->v_s);
sfree(ssh->fullhostname);
sfree(ssh->hostkey_str);
+ sfree(ssh->specials);
if (ssh->crcda_ctx) {
crcda_free_context(ssh->crcda_ctx);
ssh->crcda_ctx = NULL;
bufchain_clear(&ssh->queued_incoming_data);
sfree(ssh->username);
conf_free(ssh->conf);
+
+ if (ssh->auth_agent_query)
+ agent_cancel_query(ssh->auth_agent_query);
+
#ifndef NO_GSSAPI
if (ssh->gsslibs)
ssh_gss_cleanup(ssh->gsslibs);
static const struct telnet_special specials_end[] = {
{NULL, TS_EXITMENU}
};
- /* XXX review this length for any changes: */
- static struct telnet_special ssh_specials[lenof(ssh2_ignore_special) +
- lenof(ssh2_rekey_special) +
- lenof(ssh2_session_specials) +
- lenof(specials_end)];
+
+ struct telnet_special *specials = NULL;
+ int nspecials = 0, specialsize = 0;
+
Ssh ssh = (Ssh) handle;
- int i = 0;
-#define ADD_SPECIALS(name) \
- do { \
- assert((i + lenof(name)) <= lenof(ssh_specials)); \
- memcpy(&ssh_specials[i], name, sizeof name); \
- i += lenof(name); \
- } while(0)
+
+ sfree(ssh->specials);
+
+#define ADD_SPECIALS(name) do \
+ { \
+ int len = lenof(name); \
+ if (nspecials + len > specialsize) { \
+ specialsize = (nspecials + len) * 5 / 4 + 32; \
+ specials = sresize(specials, specialsize, struct telnet_special); \
+ } \
+ memcpy(specials+nspecials, name, len*sizeof(struct telnet_special)); \
+ nspecials += len; \
+ } while (0)
if (ssh->version == 1) {
/* Don't bother offering IGNORE if we've decided the remote
ADD_SPECIALS(ssh2_rekey_special);
if (ssh->mainchan)
ADD_SPECIALS(ssh2_session_specials);
+
+ if (ssh->n_uncert_hostkeys) {
+ static const struct telnet_special uncert_start[] = {
+ {NULL, TS_SEP},
+ {"Cache new host key type", TS_SUBMENU},
+ };
+ static const struct telnet_special uncert_end[] = {
+ {NULL, TS_EXITMENU},
+ };
+ int i;
+
+ ADD_SPECIALS(uncert_start);
+ for (i = 0; i < ssh->n_uncert_hostkeys; i++) {
+ struct telnet_special uncert[1];
+ const struct ssh_signkey *alg =
+ hostkey_algs[ssh->uncert_hostkeys[i]].alg;
+ uncert[0].name = alg->name;
+ uncert[0].code = TS_LOCALSTART + ssh->uncert_hostkeys[i];
+ ADD_SPECIALS(uncert);
+ }
+ ADD_SPECIALS(uncert_end);
+ }
} /* else we're not ready yet */
- if (i) {
+ if (nspecials)
ADD_SPECIALS(specials_end);
- return ssh_specials;
+
+ ssh->specials = specials;
+
+ if (nspecials) {
+ return specials;
} else {
return NULL;
}
ssh->version == 2) {
do_ssh2_transport(ssh, "at user request", -1, NULL);
}
+ } else if (code >= TS_LOCALSTART) {
+ ssh->hostkey = hostkey_algs[code - TS_LOCALSTART].alg;
+ ssh->cross_certifying = TRUE;
+ if (!ssh->kex_in_progress && !ssh->bare_connection &&
+ ssh->version == 2) {
+ do_ssh2_transport(ssh, "cross-certifying new host key", -1, NULL);
+ }
} else if (code == TS_BRK) {
if (ssh->state == SSH_STATE_CLOSED
|| ssh->state == SSH_STATE_PREPACKET) return;
c = snew(struct ssh_channel);
c->ssh = ssh;
- ssh2_channel_init(c);
+ ssh_channel_init(c);
c->halfopen = TRUE;
- c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+ c->type = CHAN_SOCKDATA;/* identify channel type */
c->u.pfd.pf = pf;
- add234(ssh->channels, c);
return c;
}
c = snew(struct ssh_channel);
c->ssh = ssh;
- ssh2_channel_init(c);
+ ssh_channel_init(c);
c->type = CHAN_SHARING;
c->u.sharing.ctx = sharing_ctx;
- add234(ssh->channels, c);
return c->localid;
}
static void ssh_unthrottle(void *handle, int bufsize)
{
Ssh ssh = (Ssh) handle;
- int buflimit;
if (ssh->version == 1) {
if (ssh->v1_stdout_throttling && bufsize < SSH1_BUFFER_LIMIT) {
ssh_throttle_conn(ssh, -1);
}
} else {
- if (ssh->mainchan) {
- ssh2_set_window(ssh->mainchan,
- bufsize < ssh->mainchan->v.v2.locmaxwin ?
- ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
- if (ssh_is_simple(ssh))
- buflimit = 0;
- else
- buflimit = ssh->mainchan->v.v2.locmaxwin;
- if (ssh->mainchan->throttling_conn && bufsize <= buflimit) {
- ssh->mainchan->throttling_conn = 0;
- ssh_throttle_conn(ssh, -1);
- }
- }
+ if (ssh->mainchan)
+ ssh_channel_unthrottle(ssh->mainchan, bufsize);
}
/*
ssh_provide_logctx,
ssh_unthrottle,
ssh_cfg_info,
+ ssh_test_for_upstream,
"ssh",
PROT_SSH,
22