#define SSH1_SMSG_AUTH_CCARD_CHALLENGE 71 /* 0x47 */
#define SSH1_CMSG_AUTH_CCARD_RESPONSE 72 /* 0x48 */
+#define SSH1_AUTH_RHOSTS 1 /* 0x1 */
+#define SSH1_AUTH_RSA 2 /* 0x2 */
+#define SSH1_AUTH_PASSWORD 3 /* 0x3 */
+#define SSH1_AUTH_RHOSTS_RSA 4 /* 0x4 */
#define SSH1_AUTH_TIS 5 /* 0x5 */
#define SSH1_AUTH_CCARD 16 /* 0x10 */
/*
* Codes for terminal modes.
* Most of these are the same in SSH-1 and SSH-2.
- * This list is derived from draft-ietf-secsh-connect-25 and
+ * This list is derived from RFC 4254 and
* SSH-1 RFC-1.2.31.
*/
static const struct {
* channels.
*
* - OUR_V2_BIGWIN is the window size we advertise for the only
- * channel in a simple connection.
+ * channel in a simple connection. It must be <= INT_MAX.
*/
#define SSH1_BUFFER_LIMIT 32768
#define SSH_MAX_BACKLOG 32768
#define OUR_V2_WINSIZE 16384
-#define OUR_V2_BIGWIN 0x10000000
+#define OUR_V2_BIGWIN 0x7fffffff
#define OUR_V2_MAXPKT 0x4000UL
/* Maximum length of passwords/passphrases (arbitrary) */
};
/*
+ * little structure to keep track of outstanding WINDOW_ADJUSTs
+ */
+struct winadj {
+ struct winadj *next;
+ unsigned size;
+};
+
+/*
* 2-3-4 tree storing channels.
*/
struct ssh_channel {
unsigned remwindow, remmaxpkt;
/* locwindow is signed so we can cope with excess data. */
int locwindow, locmaxwin;
+ /*
+ * remlocwin is the amount of local window that we think
+ * the remote end had available to it after it sent the
+ * last data packet or window adjust ack.
+ */
+ int remlocwin;
+ /*
+ * These store the list of window adjusts that haven't
+ * been acked.
+ */
+ struct winadj *winadj_head, *winadj_tail;
+ enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
} v2;
} v;
union {
static int ssh2_try_send(struct ssh_channel *c);
static void ssh2_add_channel_data(struct ssh_channel *c, char *buf, int len);
static void ssh_throttle_all(Ssh ssh, int enable, int bufsize);
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin);
+static void ssh2_set_window(struct ssh_channel *c, int newwin);
static int ssh_sendbuffer(void *handle);
static int ssh_do_close(Ssh ssh, int notify_exit);
static unsigned long ssh_pkt_getuint32(struct Packet *pkt);
ssh->v1_remote_protoflags = ssh_pkt_getuint32(pktin);
s->supported_ciphers_mask = ssh_pkt_getuint32(pktin);
s->supported_auths_mask = ssh_pkt_getuint32(pktin);
+ if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA))
+ s->supported_auths_mask &= ~(1 << SSH1_AUTH_RSA);
ssh->v1_local_protoflags =
ssh->v1_remote_protoflags & SSH1_PROTOFLAGS_SUPPORTED;
crWaitUntil(pktin);
- if ((ssh->remote_bugs & BUG_CHOKES_ON_RSA)) {
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_RSA)) == 0) {
/* We must not attempt PK auth. Pretend we've already tried it. */
s->tried_publickey = s->tried_agent = 1;
} else {
}
}
if (s->pwpkt_type == SSH1_CMSG_AUTH_PASSWORD) {
+ if ((s->supported_auths_mask & (1 << SSH1_AUTH_PASSWORD)) == 0) {
+ bombout(("No supported authentication methods available"));
+ crStop(0);
+ }
s->cur_prompt->to_server = TRUE;
s->cur_prompt->name = dupstr("SSH password");
add_prompt(s->cur_prompt, dupprintf("%.90s@%.90s's password: ",
/*
* Potentially enlarge the window on an SSH-2 channel.
*/
-static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+static void ssh2_set_window(struct ssh_channel *c, int newwin)
{
Ssh ssh = c->ssh;
*
* "Significant" is arbitrarily defined as half the window size.
*/
- if (newwin >= c->v.v2.locwindow * 2) {
+ if (newwin / 2 >= c->v.v2.locwindow) {
struct Packet *pktout;
+ struct winadj *wa;
+
+ /*
+ * In order to keep track of how much window the client
+ * actually has available, we'd like it to acknowledge each
+ * WINDOW_ADJUST. We can't do that directly, so we accompany
+ * it with a CHANNEL_REQUEST that has to be acknowledged.
+ *
+ * This is only necessary if we're opening the window wide.
+ * If we're not, then throughput is being constrained by
+ * something other than the maximum window size anyway.
+ *
+ * We also only send this if the main channel has finished its
+ * initial CHANNEL_REQUESTs and installed the default
+ * CHANNEL_FAILURE handler, so as not to risk giving it
+ * unexpected CHANNEL_FAILUREs.
+ */
+ if (newwin == c->v.v2.locmaxwin &&
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE]) {
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring(pktout, "winadj@putty.projects.tartarus.org");
+ ssh2_pkt_addbool(pktout, TRUE);
+ ssh2_pkt_send(ssh, pktout);
+ /*
+ * CHANNEL_FAILURE doesn't come with any indication of
+ * what message caused it, so we have to keep track of the
+ * outstanding CHANNEL_REQUESTs ourselves.
+ */
+ wa = snew(struct winadj);
+ wa->size = newwin - c->v.v2.locwindow;
+ wa->next = NULL;
+ if (!c->v.v2.winadj_head)
+ c->v.v2.winadj_head = wa;
+ else
+ c->v.v2.winadj_tail->next = wa;
+ c->v.v2.winadj_tail = wa;
+ if (c->v.v2.throttle_state != UNTHROTTLED)
+ c->v.v2.throttle_state = UNTHROTTLING;
+ } else {
+ /* Pretend the WINDOW_ADJUST was acked immediately. */
+ c->v.v2.remlocwin = newwin;
+ c->v.v2.throttle_state = THROTTLED;
+ }
pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
ssh2_pkt_adduint32(pktout, c->remoteid);
ssh2_pkt_adduint32(pktout, newwin - c->v.v2.locwindow);
}
}
+static void ssh2_msg_channel_failure(Ssh ssh, struct Packet *pktin)
+{
+ /*
+ * The only time this should get called is for "winadj@putty"
+ * messages sent above. All other channel requests are either
+ * 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);
+ if (!c)
+ return; /* nonexistent channel */
+ 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;
+ }
+}
+
static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
{
unsigned i = ssh_pkt_getuint32(pktin);
if (data) {
int bufsize = 0;
c->v.v2.locwindow -= length;
+ c->v.v2.remlocwin -= length;
switch (c->type) {
case CHAN_MAINSESSION:
bufsize =
break;
}
/*
+ * 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
+ * larger window.
+ */
+ if (c->v.v2.remlocwin <= 0 && c->v.v2.throttle_state == UNTHROTTLED &&
+ c->v.v2.locmaxwin < 0x40000000)
+ c->v.v2.locmaxwin += OUR_V2_WINSIZE;
+ /*
* If we are not buffering too much data,
* enlarge the window again at the remote side.
* If we are buffering too much, we may still
c->v.v2.locwindow = c->v.v2.locmaxwin = OUR_V2_WINSIZE;
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_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);
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->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
ssh2_msg_channel_open;
+ if (ssh->cfg.ssh_simple) {
+ /*
+ * This message indicates to the server that we promise
+ * not to try to run any other channel in parallel with
+ * this one, so it's safe for it to advertise a very large
+ * window and leave the flow control to TCP.
+ */
+ s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
+ ssh2_pkt_addstring(s->pktout, "simple@putty.projects.tartarus.org");
+ ssh2_pkt_addbool(s->pktout, 0); /* no reply */
+ ssh2_pkt_send(ssh, s->pktout);
+ }
+
/*
* Potentially enable X11 forwarding.
*/
ssh_special(ssh, TS_EOF);
/*
+ * All the initial channel requests are done, so install the default
+ * failure handler.
+ */
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_failure;
+
+ /*
* Transfer data!
*/
if (ssh->ldisc)
ssh1_throttle(ssh, -1);
}
} else {
- ssh2_set_window(ssh->mainchan,
- ssh->mainchan->v.v2.locmaxwin - bufsize);
+ if (ssh->mainchan)
+ ssh2_set_window(ssh->mainchan,
+ ssh->mainchan->v.v2.locmaxwin - bufsize);
}
}
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);