#define BUG_SSH2_DERIVEKEY 32
#define BUG_SSH2_REKEY 64
#define BUG_SSH2_PK_SESSIONID 128
+#define BUG_SSH2_MAXPKT 256
/*
* Codes for terminal modes.
* A channel is completely finished with when all four bits are set.
*/
int closes;
+ /*
+ * True if this channel is causing the underlying connection to be
+ * throttled.
+ */
+ int throttling_conn;
union {
- struct ssh1_data_channel {
- int throttling;
- } v1;
struct ssh2_data_channel {
bufchain outbuffer;
unsigned remwindow, remmaxpkt;
void *x11auth;
int version;
- int v1_throttle_count;
+ int conn_throttle_count;
int overall_bufsize;
int throttled_all;
int v1_stdout_throttling;
ssh->remote_bugs |= BUG_SSH2_REKEY;
logevent("We believe remote version has SSH-2 rekey bug");
}
+
+ if (ssh->cfg.sshbug_maxpkt2 == FORCE_ON ||
+ (ssh->cfg.sshbug_maxpkt2 == AUTO &&
+ (wc_match("1.36_sshlib GlobalSCAPE", imp)))) {
+ /*
+ * This version ignores our makpkt and needs to be throttled.
+ */
+ ssh->remote_bugs |= BUG_SSH2_MAXPKT;
+ logevent("We believe remote version ignores SSH-2 maximum packet size");
+ }
}
/*
/*
* Throttle or unthrottle the SSH connection.
*/
-static void ssh1_throttle(Ssh ssh, int adjust)
+static void ssh_throttle_conn(Ssh ssh, int adjust)
{
- int old_count = ssh->v1_throttle_count;
- ssh->v1_throttle_count += adjust;
- assert(ssh->v1_throttle_count >= 0);
- if (ssh->v1_throttle_count && !old_count) {
+ int old_count = ssh->conn_throttle_count;
+ ssh->conn_throttle_count += adjust;
+ assert(ssh->conn_throttle_count >= 0);
+ if (ssh->conn_throttle_count && !old_count) {
ssh_set_frozen(ssh, 1);
- } else if (!ssh->v1_throttle_count && old_count) {
+ } else if (!ssh->conn_throttle_count && old_count) {
ssh_set_frozen(ssh, 0);
}
}
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) {
- if (c->v.v1.throttling && bufsize < SSH1_BUFFER_LIMIT) {
- c->v.v1.throttling = 0;
- ssh1_throttle(ssh, -1);
- }
+ buflimit = SSH1_BUFFER_LIMIT;
} else {
- ssh2_set_window(c, c->v.v2.locmaxwin - bufsize);
+ 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);
}
}
string, stringlen);
if (!ssh->v1_stdout_throttling && bufsize > SSH1_BUFFER_LIMIT) {
ssh->v1_stdout_throttling = 1;
- ssh1_throttle(ssh, +1);
+ ssh_throttle_conn(ssh, +1);
}
}
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->v.v1.throttling = 0;
+ c->throttling_conn = 0;
c->type = CHAN_X11; /* identify channel type */
add234(ssh->channels, c);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->v.v1.throttling = 0;
+ c->throttling_conn = 0;
c->type = CHAN_AGENT; /* identify channel type */
c->u.a.lensofar = 0;
add234(ssh->channels, c);
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->v.v1.throttling = 0;
+ c->throttling_conn = 0;
c->type = CHAN_SOCKDATA; /* identify channel type */
add234(ssh->channels, c);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
c->remoteid = localid;
c->halfopen = FALSE;
c->type = CHAN_SOCKDATA;
- c->v.v1.throttling = 0;
+ c->throttling_conn = 0;
pfd_confirm(c->u.pfd.s);
}
bufsize = 0; /* agent channels never back up */
break;
}
- if (!c->v.v1.throttling && bufsize > SSH1_BUFFER_LIMIT) {
- c->v.v1.throttling = 1;
- ssh1_throttle(ssh, +1);
+ if (!c->throttling_conn && bufsize > SSH1_BUFFER_LIMIT) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
}
}
}
}
/*
+ * Set up most of a new ssh_channel for SSH-2.
+ */
+static void ssh2_channel_init(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ c->localid = alloc_channel_id(ssh);
+ c->closes = 0;
+ c->throttling_conn = FALSE;
+ c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
+ ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
+ c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+ c->v.v2.throttle_state = UNTHROTTLED;
+ bufchain_init(&c->v.v2.outbuffer);
+}
+
+/*
* Potentially enlarge the window on an SSH-2 channel.
*/
static void ssh2_set_window(struct ssh_channel *c, int newwin)
return;
/*
+ * If the remote end has a habit of ignoring maxpkt, limit the
+ * window so that it has no choice (assuming it doesn't ignore the
+ * window as well).
+ */
+ if ((ssh->remote_bugs & BUG_SSH2_MAXPKT) && newwin > OUR_V2_MAXPKT)
+ newwin = OUR_V2_MAXPKT;
+
+
+ /*
* Only send a WINDOW_ADJUST if there's significantly more window
* available than the other end thinks there is. This saves us
* sending a WINDOW_ADJUST for every character in a shell session.
}
}
+/*
+ * 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.
+ */
+static struct ssh_channel *ssh2_channel_msg(Ssh ssh, struct Packet *pktin)
+{
+ unsigned localid = ssh_pkt_getuint32(pktin);
+ struct ssh_channel *c;
+
+ c = find234(ssh->channels, &localid, ssh_channelfind);
+ if (!c ||
+ (c->halfopen && pktin->type != SSH2_MSG_CHANNEL_OPEN_CONFIRMATION &&
+ pktin->type != SSH2_MSG_CHANNEL_OPEN_FAILURE)) {
+ 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_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+ return NULL;
+ }
+ return c;
+}
+
+static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
+{
+ /*
+ * This should never get called. All channel requests are either
+ * sent with want_reply false or are sent before this handler gets
+ * installed.
+ */
+ struct ssh_channel *c;
+ struct winadj *wa;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ wa = c->v.v2.winadj_head;
+ if (wa)
+ ssh_disconnect(ssh, NULL, "Received SSH_MSG_CHANNEL_SUCCESS for "
+ "\"winadj@putty.projects.tartarus.org\"",
+ SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ else
+ ssh_disconnect(ssh, NULL,
+ "Received unsolicited SSH_MSG_CHANNEL_SUCCESS",
+ SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+}
+
static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
{
/*
* sent with want_reply false or are sent before this handler gets
* installed.
*/
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
struct winadj *wa;
- c = find234(ssh->channels, &i, ssh_channelfind);
+ c = ssh2_channel_msg(ssh, pktin);
if (!c)
- return; /* nonexistent channel */
+ return;
wa = c->v.v2.winadj_head;
- if (!wa)
- logevent("excess SSH_MSG_CHANNEL_FAILURE");
- else {
- c->v.v2.winadj_head = wa->next;
- c->v.v2.remlocwin += wa->size;
- sfree(wa);
- /*
- * winadj messages are only sent when the window is fully open,
- * so if we get an ack of one, we know any pending unthrottle
- * is complete.
- */
- if (c->v.v2.throttle_state == UNTHROTTLING)
- c->v.v2.throttle_state = UNTHROTTLED;
+ if (!wa) {
+ ssh_disconnect(ssh, NULL,
+ "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
+ SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ return;
}
+ c->v.v2.winadj_head = wa->next;
+ c->v.v2.remlocwin += wa->size;
+ sfree(wa);
+ /*
+ * winadj messages are only sent when the window is fully open, so
+ * if we get an ack of one, we know any pending unthrottle is
+ * complete.
+ */
+ if (c->v.v2.throttle_state == UNTHROTTLING)
+ c->v.v2.throttle_state = UNTHROTTLED;
}
static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
{
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
- if (c && !c->closes) {
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
+ if (!c->closes) {
c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
ssh2_try_send_and_unthrottle(c);
}
{
char *data;
int length;
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
+ c = ssh2_channel_msg(ssh, pktin);
if (!c)
- return; /* nonexistent channel */
+ return;
if (pktin->type == SSH2_MSG_CHANNEL_EXTENDED_DATA &&
ssh_pkt_getuint32(pktin) != SSH2_EXTENDED_DATA_STDERR)
return; /* extended but not stderr */
*/
ssh2_set_window(c, bufsize < c->v.v2.locmaxwin ?
c->v.v2.locmaxwin - bufsize : 0);
+ /*
+ * If we're either buffering way too much data, or if we're
+ * buffering anything at all and we're in "simple" mode,
+ * throttle the whole channel.
+ */
+ if ((bufsize > c->v.v2.locmaxwin ||
+ (ssh->cfg.ssh_simple && bufsize > 0)) &&
+ !c->throttling_conn) {
+ c->throttling_conn = 1;
+ ssh_throttle_conn(ssh, +1);
+ }
}
}
static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
{
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
+ c = ssh2_channel_msg(ssh, pktin);
if (!c)
- return; /* nonexistent channel */
+ return;
if (c->type == CHAN_X11) {
/*
static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
{
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
struct Packet *pktout;
- c = find234(ssh->channels, &i, ssh_channelfind);
- if (!c || c->halfopen) {
- bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
- c ? "half-open" : "nonexistent", i));
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
return;
- }
/* Do pre-close processing on the channel. */
switch (c->type) {
case CHAN_MAINSESSION:
static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
{
- unsigned i = ssh_pkt_getuint32(pktin);
struct ssh_channel *c;
struct Packet *pktout;
- c = find234(ssh->channels, &i, ssh_channelfind);
+ c = ssh2_channel_msg(ssh, pktin);
if (!c)
- return; /* nonexistent channel */
+ return;
if (c->type != CHAN_SOCKDATA_DORMANT)
return; /* dunno why they're confirming this */
c->remoteid = ssh_pkt_getuint32(pktin);
"Unknown channel type",
"Resource shortage",
};
- unsigned i = ssh_pkt_getuint32(pktin);
unsigned reason_code;
char *reason_string;
int reason_length;
struct ssh_channel *c;
- c = find234(ssh->channels, &i, ssh_channelfind);
+ c = ssh2_channel_msg(ssh, pktin);
if (!c)
- return; /* nonexistent channel */
+ return;
if (c->type != CHAN_SOCKDATA_DORMANT)
return; /* dunno why they're failing this */
static void ssh2_msg_channel_request(Ssh ssh, struct Packet *pktin)
{
- unsigned localid;
char *type;
int typelen, want_reply;
int reply = SSH2_MSG_CHANNEL_FAILURE; /* default */
struct ssh_channel *c;
struct Packet *pktout;
- localid = ssh_pkt_getuint32(pktin);
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ return;
ssh_pkt_getstring(pktin, &type, &typelen);
want_reply = ssh2_pkt_getbool(pktin);
/*
- * First, check that the channel exists. Otherwise,
- * we can instantly disconnect with a rude message.
- */
- c = find234(ssh->channels, &localid, ssh_channelfind);
- if (!c) {
- char *buf = dupprintf("Received channel request for nonexistent"
- " channel %d", localid);
- ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
- sfree(buf);
- return;
- }
-
- /*
* Having got the channel number, we now look at
* the request type string to see if it's something
* we recognise.
int msglen = 0, core = FALSE;
/* ICK: older versions of OpenSSH (e.g. 3.4p1)
* provide an `int' for the signal, despite its
- * having been a `string' in the drafts since at
+ * having been a `string' in the drafts of RFC 4254 since at
* least 2001. (Fixed in session.c 1.147.) Try to
* infer which we can safely parse it as. */
{
fmt_sig = dupprintf(" %d", signum);
ssh->exitcode = 128 + signum;
} else {
- /* As per the drafts. */
+ /* As per RFC 4254. */
char *sig;
int siglen;
ssh_pkt_getstring(pktin, &sig, &siglen);
logeventf(ssh, "Rejected channel open: %s", error);
sfree(c);
} else {
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
+ ssh2_channel_init(c);
c->v.v2.remwindow = winsize;
c->v.v2.remmaxpkt = pktsize;
- c->v.v2.remlocwin = OUR_V2_WINSIZE;
- c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
- c->v.v2.throttle_state = UNTHROTTLED;
- bufchain_init(&c->v.v2.outbuffer);
add234(ssh->channels, c);
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN_CONFIRMATION);
ssh2_pkt_adduint32(pktout, c->remoteid);
*/
ssh->mainchan = snew(struct ssh_channel);
ssh->mainchan->ssh = ssh;
- ssh->mainchan->localid = alloc_channel_id(ssh);
+ ssh2_channel_init(ssh->mainchan);
logeventf(ssh,
"Opening direct-tcpip channel to %s:%d in place of session",
ssh->cfg.ssh_nc_host, ssh->cfg.ssh_nc_port);
s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
ssh2_pkt_addstring(s->pktout, "direct-tcpip");
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
- ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
- ssh->mainchan->v.v2.remlocwin =
- ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
- ssh->mainchan->v.v2.winadj_head = NULL;
- ssh->mainchan->v.v2.winadj_tail = NULL;
- ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_addstring(s->pktout, ssh->cfg.ssh_nc_host);
ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
ssh->mainchan->halfopen = FALSE;
ssh->mainchan->type = CHAN_MAINSESSION;
- ssh->mainchan->closes = 0;
ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- bufchain_init(&ssh->mainchan->v.v2.outbuffer);
add234(ssh->channels, ssh->mainchan);
update_specials_menu(ssh->frontend);
logevent("Opened direct-tcpip channel");
} else {
ssh->mainchan = snew(struct ssh_channel);
ssh->mainchan->ssh = ssh;
- ssh->mainchan->localid = alloc_channel_id(ssh);
+ ssh2_channel_init(ssh->mainchan);
s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
ssh2_pkt_addstring(s->pktout, "session");
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->localid);
- ssh->mainchan->v.v2.locwindow = ssh->mainchan->v.v2.locmaxwin =
- ssh->mainchan->v.v2.remlocwin =
- ssh->cfg.ssh_simple ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
- ssh->mainchan->v.v2.winadj_head = NULL;
- ssh->mainchan->v.v2.winadj_tail = NULL;
- ssh->mainchan->v.v2.throttle_state = UNTHROTTLED;
ssh2_pkt_adduint32(s->pktout, ssh->mainchan->v.v2.locwindow);/* our window size */
ssh2_pkt_adduint32(s->pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_send(ssh, s->pktout);
ssh->mainchan->remoteid = ssh_pkt_getuint32(pktin);
ssh->mainchan->halfopen = FALSE;
ssh->mainchan->type = CHAN_MAINSESSION;
- ssh->mainchan->closes = 0;
ssh->mainchan->v.v2.remwindow = ssh_pkt_getuint32(pktin);
ssh->mainchan->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
- bufchain_init(&ssh->mainchan->v.v2.outbuffer);
add234(ssh->channels, ssh->mainchan);
update_specials_menu(ssh->frontend);
logevent("Opened channel for session");
* All the initial channel requests are done, so install the default
* failure handler.
*/
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_success;
ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
/*
ssh->send_ok = 0;
ssh->editing = 0;
ssh->echoing = 0;
- ssh->v1_throttle_count = 0;
+ ssh->conn_throttle_count = 0;
ssh->overall_bufsize = 0;
ssh->fallback_cmd = 0;
static const struct telnet_special ssh2_session_specials[] = {
{NULL, TS_SEP},
{"Break", TS_BRK},
- /* These are the signal names defined by draft-ietf-secsh-connect-23.
+ /* These are the signal names defined by RFC 4254.
* They include all the ISO C signals, but are a subset of the POSIX
* required signals. */
{"SIGINT (Interrupt)", TS_SIGINT},
Ssh ssh = (Ssh) handle;
struct ssh_channel *c;
c = snew(struct ssh_channel);
- c->ssh = ssh;
- if (c) {
- c->halfopen = TRUE;
- c->localid = alloc_channel_id(ssh);
- c->closes = 0;
- c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
- c->u.pfd.s = s;
- bufchain_init(&c->v.v2.outbuffer);
- add234(ssh->channels, c);
- }
+ c->ssh = ssh;
+ ssh2_channel_init(c);
+ c->halfopen = TRUE;
+ c->type = CHAN_SOCKDATA_DORMANT;/* identify channel type */
+ c->u.pfd.s = s;
+ add234(ssh->channels, c);
return c;
}
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->v1_stdout_throttling = 0;
- ssh1_throttle(ssh, -1);
+ ssh_throttle_conn(ssh, -1);
}
} else {
- if (ssh->mainchan)
+ if (ssh->mainchan) {
ssh2_set_window(ssh->mainchan,
- ssh->mainchan->v.v2.locmaxwin - bufsize);
+ bufsize < ssh->mainchan->v.v2.locmaxwin ?
+ ssh->mainchan->v.v2.locmaxwin - bufsize : 0);
+ if (ssh->cfg.ssh_simple)
+ 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);
+ }
+ }
}
}
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_OPEN);
ssh2_pkt_addstring(pktout, "direct-tcpip");
ssh2_pkt_adduint32(pktout, c->localid);
- c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
- c->v.v2.remlocwin = OUR_V2_WINSIZE;
- c->v.v2.winadj_head = c->v.v2.winadj_head = NULL;
- c->v.v2.throttle_state = UNTHROTTLED;
ssh2_pkt_adduint32(pktout, c->v.v2.locwindow);/* our window size */
ssh2_pkt_adduint32(pktout, OUR_V2_MAXPKT); /* our max pkt size */
ssh2_pkt_addstring(pktout, hostname);