#define BUG_SSH2_PK_SESSIONID 128
#define BUG_SSH2_MAXPKT 256
#define BUG_CHOKES_ON_SSH2_IGNORE 512
+#define BUG_CHOKES_ON_WINADJ 1024
/*
* Codes for terminal modes.
* Database for Edit and Continue'.
*/
#define crBegin(v) { int *crLine = &v; switch(v) { case 0:;
-#define crState(t) \
- struct t *s; \
- if (!ssh->t) ssh->t = snew(struct t); \
- s = ssh->t;
+#define crBeginState crBegin(s->crLine)
+#define crStateP(t, v) \
+ struct t *s; \
+ if (!(v)) { s = (v) = snew(struct t); s->crLine = 0; } \
+ s = (v);
+#define crState(t) crStateP(t, ssh->t)
#define crFinish(z) } *crLine = 0; return (z); }
#define crFinishV } *crLine = 0; return; }
+#define crFinishFree(z) } sfree(s); return (z); }
+#define crFinishFreeV } sfree(s); return; }
#define crReturn(z) \
do {\
*crLine =__LINE__; return (z); case __LINE__:;\
struct Packet *pktin);
static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
struct Packet *pktin);
+static void ssh2_channel_check_close(struct ssh_channel *c);
+static void ssh_channel_destroy(struct ssh_channel *c);
/*
* Buffer management constants. There are several of these for
#define OUR_V2_MAXPKT 0x4000UL
#define OUR_V2_PACKETLIMIT 0x9000UL
-/* Maximum length of passwords/passphrases (arbitrary) */
-#define SSH_MAX_PASSWORD_LEN 100
-
const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
const static struct ssh_mac *macs[] = {
CHAN_X11,
CHAN_AGENT,
CHAN_SOCKDATA,
- CHAN_SOCKDATA_DORMANT /* one the remote hasn't confirmed */
+ CHAN_SOCKDATA_DORMANT, /* one the remote hasn't confirmed */
+ /*
+ * CHAN_ZOMBIE is used to indicate a channel for which we've
+ * already destroyed the local data source: for instance, if a
+ * forwarded port experiences a socket error on the local side, we
+ * immediately destroy its local socket and turn the SSH channel
+ * into CHAN_ZOMBIE.
+ */
+ CHAN_ZOMBIE
+};
+
+typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
+typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
+typedef void (*cchandler_fn_t)(struct ssh_channel *, struct Packet *, void *);
+
+/*
+ * Each channel has a queue of outstanding CHANNEL_REQUESTS and their
+ * handlers.
+ */
+struct outstanding_channel_request {
+ cchandler_fn_t handler;
+ void *ctx;
+ struct outstanding_channel_request *next;
};
/*
* 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
*
* A channel is completely finished with when all four bits are set.
+ *
+ * In SSH-2, the four bits mean:
+ *
+ * 1 We have sent SSH2_MSG_CHANNEL_EOF.
+ * 2 We have sent SSH2_MSG_CHANNEL_CLOSE.
+ * 4 We have received SSH2_MSG_CHANNEL_EOF.
+ * 8 We have received SSH2_MSG_CHANNEL_CLOSE.
+ *
+ * A channel is completely finished with when we have both sent
+ * and received CLOSE.
+ *
+ * The symbolic constants below use the SSH-2 terminology, which
+ * is a bit confusing in SSH-1, but we have to use _something_.
*/
+#define CLOSES_SENT_EOF 1
+#define CLOSES_SENT_CLOSE 2
+#define CLOSES_RCVD_EOF 4
+#define CLOSES_RCVD_CLOSE 8
int closes;
/*
- * This flag indicates that a close is pending on the outgoing
- * side of the channel: that is, wherever we're getting the data
- * for this channel has sent us some data followed by EOF. We
- * can't actually close the channel until we've finished sending
- * the data, so we set this flag instead to remind us to
- * initiate the closing process once our buffer is clear.
+ * This flag indicates that an EOF is pending on the outgoing side
+ * of the channel: that is, wherever we're getting the data for
+ * this channel has sent us some data followed by EOF. We can't
+ * actually send the EOF until we've finished sending the data, so
+ * we set this flag instead to remind us to do so once our buffer
+ * is clear.
*/
- int pending_close;
+ int pending_eof;
/*
* True if this channel is causing the underlying connection to be
*/
int remlocwin;
/*
- * These store the list of window adjusts that haven't
+ * These store the list of channel requests that haven't
* been acked.
*/
- struct winadj *winadj_head, *winadj_tail;
+ struct outstanding_channel_request *chanreq_head, *chanreq_tail;
enum { THROTTLED, UNTHROTTLING, UNTHROTTLED } throttle_state;
} v2;
} v;
static int ssh2_pkt_getbool(struct Packet *pkt);
static void ssh_pkt_getstring(struct Packet *pkt, char **p, int *length);
static void ssh2_timer(void *ctx, long now);
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
- struct Packet *pktin);
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+ struct Packet *pktin);
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin);
struct rdpkt1_state_tag {
long len, pad, biglen, to_read;
struct Packet *pktin;
};
-typedef void (*handler_fn_t)(Ssh ssh, struct Packet *pktin);
-typedef void (*chandler_fn_t)(Ssh ssh, struct Packet *pktin, void *ctx);
-
struct queued_handler;
struct queued_handler {
int msg1, msg2;
} state;
int size_needed, eof_needed;
+ int sent_console_eof;
+ int got_pty; /* affects EOF behaviour on main channel */
struct Packet **queue;
int queuelen, queuesize;
int ssh1_rdpkt_crstate;
int ssh2_rdpkt_crstate;
- int do_ssh_init_crstate;
int ssh_gotdata_crstate;
- int do_ssh1_login_crstate;
int do_ssh1_connection_crstate;
- int do_ssh2_transport_crstate;
- int do_ssh2_authconn_crstate;
void *do_ssh_init_state;
void *do_ssh1_login_state;
* indications from a request.
*/
struct queued_handler *qhead, *qtail;
+ handler_fn_t q_saved_handler1, q_saved_handler2;
/*
* This module deals with sending keepalives.
sfree(buf);
}
-#define bombout(msg) \
- do { \
- char *text = dupprintf msg; \
- ssh_do_close(ssh, FALSE); \
- logevent(text); \
- connection_fatal(ssh->frontend, "%s", text); \
- sfree(text); \
- } while (0)
+static void bomb_out(Ssh ssh, char *text)
+{
+ ssh_do_close(ssh, FALSE);
+ logevent(text);
+ connection_fatal(ssh->frontend, "%s", text);
+ sfree(text);
+}
+
+#define bombout(msg) bomb_out(ssh, dupprintf msg)
/* Functions to leave bits out of the SSH packet log file. */
* follows it, or 'A' indicating that we should pass the
* value through from the local environment via get_ttymode.
*/
- if (val[0] == 'A')
+ if (val[0] == 'A') {
val = get_ttymode(ssh->frontend, key);
- else
- val++; /* skip the 'V' */
- if (val)
- do_mode(data, key, val);
+ if (val) {
+ do_mode(data, key, val);
+ sfree(val);
+ }
+ } else
+ do_mode(data, key, val + 1); /* skip the 'V' */
}
}
ssh->remote_bugs |= BUG_CHOKES_ON_SSH2_IGNORE;
logevent("We believe remote version has SSH-2 ignore bug");
}
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_winadj) == FORCE_ON) {
+ /*
+ * Servers that don't support our winadj request for one
+ * reason or another. Currently, none detected automatically.
+ */
+ ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
+ logevent("We believe remote version has winadj bug");
+ }
}
/*
static int do_ssh_init(Ssh ssh, unsigned char c)
{
struct do_ssh_init_state {
+ int crLine;
int vslen;
char version[10];
char *vstring;
int proto1, proto2;
};
crState(do_ssh_init_state);
-
- crBegin(ssh->do_ssh_init_crstate);
+
+ crBeginState;
/* Search for a line beginning with the string "SSH-" in the input. */
for (;;) {
struct RSAKey servkey, hostkey;
struct MD5Context md5c;
struct do_ssh1_login_state {
+ int crLine;
int len;
unsigned char *rsabuf, *keystr1, *keystr2;
unsigned long supported_ciphers_mask, supported_auths_mask;
};
crState(do_ssh1_login_state);
- crBegin(ssh->do_ssh1_login_crstate);
+ crBeginState;
if (!pktin)
crWaitUntil(pktin);
s->cur_prompt = new_prompts(ssh->frontend);
s->cur_prompt->to_server = TRUE;
s->cur_prompt->name = dupstr("SSH login name");
- /* 512 is an arbitrary upper limit on username size */
- add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, 512);
+ add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
ret = get_userpass_input(s->cur_prompt, NULL, 0);
while (ret < 0) {
ssh->send_ok = 1;
* Load the public half of any configured keyfile for later use.
*/
s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- if (!filename_is_null(*s->keyfile)) {
+ if (!filename_is_null(s->keyfile)) {
int keytype;
logeventf(ssh, "Reading private key file \"%.150s\"",
filename_to_str(s->keyfile));
s->cur_prompt->name = dupstr("SSH key passphrase");
add_prompt(s->cur_prompt,
dupprintf("Passphrase for key \"%.100s\": ",
- s->publickey_comment),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ s->publickey_comment), FALSE);
ret = get_userpass_input(s->cur_prompt, NULL, 0);
while (ret < 0) {
ssh->send_ok = 1;
ret = loadrsakey(s->keyfile, &s->key, passphrase,
&error);
if (passphrase) {
- memset(passphrase, 0, strlen(passphrase));
+ smemclr(passphrase, strlen(passphrase));
sfree(passphrase);
}
if (ret == 1) {
(*instr_suf) ? "\n" : "",
instr_suf);
s->cur_prompt->instr_reqd = TRUE;
- add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+ add_prompt(s->cur_prompt, prompt, FALSE);
sfree(instr_suf);
}
}
(*instr_suf) ? "\n" : "",
instr_suf);
s->cur_prompt->instr_reqd = TRUE;
- add_prompt(s->cur_prompt, prompt, FALSE, SSH_MAX_PASSWORD_LEN);
+ add_prompt(s->cur_prompt, prompt, FALSE);
sfree(instr_suf);
}
}
s->cur_prompt->name = dupstr("SSH password");
add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
ssh->username, ssh->savedhost),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
}
/*
crFinish(1);
}
-void sshfwd_close(struct ssh_channel *c)
+static void ssh_channel_try_eof(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ assert(c->pending_eof); /* precondition for calling us */
+ if (c->halfopen)
+ return; /* can't close: not even opened yet */
+ if (ssh->version == 2 && bufchain_size(&c->v.v2.outbuffer) > 0)
+ return; /* can't send EOF: pending outgoing data */
+
+ c->pending_eof = FALSE; /* we're about to send it */
+ if (ssh->version == 1) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ c->closes |= CLOSES_SENT_EOF;
+ } else {
+ struct Packet *pktout;
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ c->closes |= CLOSES_SENT_EOF;
+ ssh2_channel_check_close(c);
+ }
+}
+
+void sshfwd_write_eof(struct ssh_channel *c)
{
Ssh ssh = c->ssh;
if (ssh->state == SSH_STATE_CLOSED)
return;
- if (!c->closes) {
- /*
- * If halfopen is true, we have sent
- * CHANNEL_OPEN for this channel, but it hasn't even been
- * acknowledged by the server. So we must set a close flag
- * on it now, and then when the server acks the channel
- * open, we can close it then.
- */
- if (!c->halfopen) {
- if (ssh->version == 1) {
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- c->closes = 1; /* sent MSG_CLOSE */
- } else {
- int bytes_to_send = bufchain_size(&c->v.v2.outbuffer);
- if (bytes_to_send > 0) {
- /*
- * If we still have unsent data in our outgoing
- * buffer for this channel, we can't actually
- * initiate a close operation yet or that data
- * will be lost. Instead, set the pending_close
- * flag so that when we do clear the buffer
- * we'll start closing the channel.
- */
- char logmsg[160] = {'\0'};
- sprintf(
- logmsg,
- "Forwarded port pending to be closed : "
- "%d bytes remaining",
- bytes_to_send);
- logevent(logmsg);
-
- c->pending_close = TRUE;
- } else {
- /*
- * No locally buffered data, so we can send the
- * close message immediately.
- */
- struct Packet *pktout;
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes = 1; /* sent MSG_CLOSE */
- logevent("Nothing left to send, closing channel");
- }
- }
- }
+ if (c->closes & CLOSES_SENT_EOF)
+ return;
- if (c->type == CHAN_X11) {
- c->u.x11.s = NULL;
- logevent("Forwarded X11 connection terminated");
- } else if (c->type == CHAN_SOCKDATA ||
- c->type == CHAN_SOCKDATA_DORMANT) {
- c->u.pfd.s = NULL;
- logevent("Forwarded port closed");
- }
+ c->pending_eof = TRUE;
+ ssh_channel_try_eof(c);
+}
+
+void sshfwd_unclean_close(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+
+ if (ssh->state == SSH_STATE_CLOSED)
+ return;
+
+ switch (c->type) {
+ case CHAN_X11:
+ x11_close(c->u.x11.s);
+ logevent("Forwarded X11 connection terminated due to local error");
+ break;
+ case CHAN_SOCKDATA:
+ case CHAN_SOCKDATA_DORMANT:
+ pfd_close(c->u.pfd.s);
+ logevent("Forwarded port closed due to local error");
+ break;
}
+ c->type = CHAN_ZOMBIE;
+
+ ssh2_channel_check_close(c);
}
int sshfwd_write(struct ssh_channel *c, char *buf, int len)
if (qh->msg1 > 0) {
assert(ssh->packet_dispatch[qh->msg1] == ssh_queueing_handler);
- ssh->packet_dispatch[qh->msg1] = NULL;
+ ssh->packet_dispatch[qh->msg1] = ssh->q_saved_handler1;
}
if (qh->msg2 > 0) {
assert(ssh->packet_dispatch[qh->msg2] == ssh_queueing_handler);
- ssh->packet_dispatch[qh->msg2] = NULL;
+ ssh->packet_dispatch[qh->msg2] = ssh->q_saved_handler2;
}
if (qh->next) {
ssh->qhead = qh->next;
if (ssh->qhead->msg1 > 0) {
- assert(ssh->packet_dispatch[ssh->qhead->msg1] == NULL);
+ ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
ssh->packet_dispatch[ssh->qhead->msg1] = ssh_queueing_handler;
}
if (ssh->qhead->msg2 > 0) {
- assert(ssh->packet_dispatch[ssh->qhead->msg2] == NULL);
+ ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
ssh->packet_dispatch[ssh->qhead->msg2] = ssh_queueing_handler;
}
} else {
ssh->qhead = ssh->qtail = NULL;
- ssh->packet_dispatch[pktin->type] = NULL;
}
qh->handler(ssh, pktin, qh->ctx);
ssh->qhead = qh;
if (qh->msg1 > 0) {
- assert(ssh->packet_dispatch[qh->msg1] == NULL);
+ ssh->q_saved_handler1 = ssh->packet_dispatch[ssh->qhead->msg1];
ssh->packet_dispatch[qh->msg1] = ssh_queueing_handler;
}
if (qh->msg2 > 0) {
- assert(ssh->packet_dispatch[qh->msg2] == NULL);
+ ssh->q_saved_handler2 = ssh->packet_dispatch[ssh->qhead->msg2];
ssh->packet_dispatch[qh->msg2] = ssh_queueing_handler;
}
} else {
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->pending_close = FALSE;
+ c->pending_eof = FALSE;
c->throttling_conn = 0;
c->type = CHAN_X11; /* identify channel type */
add234(ssh->channels, c);
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->pending_close = FALSE;
+ 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;
add234(ssh->channels, c);
send_packet(ssh, SSH1_MSG_CHANNEL_OPEN_CONFIRMATION,
PKT_INT, c->remoteid, PKT_INT, c->localid,
c->halfopen = FALSE;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->pending_close = FALSE;
+ c->pending_eof = FALSE;
c->throttling_conn = 0;
c->type = CHAN_SOCKDATA; /* identify channel type */
add234(ssh->channels, c);
pfd_confirm(c->u.pfd.s);
}
- if (c && c->closes) {
+ if (c && c->pending_eof) {
/*
* We have a pending close on this channel,
* which we decided on before the server acked
* the channel open. So now we know the
* remoteid, we can close it again.
*/
- send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE,
- PKT_INT, c->remoteid, PKT_END);
+ ssh_channel_try_eof(c);
}
}
struct ssh_channel *c;
c = find234(ssh->channels, &i, ssh_channelfind);
if (c && !c->halfopen) {
- int closetype;
- closetype =
- (pktin->type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
-
- if ((c->closes == 0) && (c->type == CHAN_X11)) {
- logevent("Forwarded X11 connection terminated");
- assert(c->u.x11.s != NULL);
- x11_close(c->u.x11.s);
- c->u.x11.s = NULL;
- }
- if ((c->closes == 0) && (c->type == CHAN_SOCKDATA)) {
- logevent("Forwarded port closed");
- assert(c->u.pfd.s != NULL);
- pfd_close(c->u.pfd.s);
- c->u.pfd.s = NULL;
- }
- c->closes |= (closetype << 2); /* seen this message */
- if (!(c->closes & closetype)) {
- send_packet(ssh, pktin->type, PKT_INT, c->remoteid,
- PKT_END);
- c->closes |= closetype; /* sent it too */
- }
+ if (pktin->type == SSH1_MSG_CHANNEL_CLOSE &&
+ !(c->closes & CLOSES_RCVD_EOF)) {
+ /*
+ * Received CHANNEL_CLOSE, which we translate into
+ * outgoing EOF.
+ */
+ int send_close = FALSE;
- if (c->closes == 15) {
- del234(ssh->channels, c);
- sfree(c);
- }
+ c->closes |= CLOSES_RCVD_EOF;
+
+ switch (c->type) {
+ case CHAN_X11:
+ if (c->u.x11.s)
+ x11_send_eof(c->u.x11.s);
+ else
+ send_close = TRUE;
+ break;
+ case CHAN_SOCKDATA:
+ if (c->u.pfd.s)
+ pfd_send_eof(c->u.pfd.s);
+ 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;
+ }
+ }
+
+ 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));
+ }
+
+ c->closes |= CLOSES_RCVD_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) &&
+ !(c->closes & CLOSES_SENT_CLOSE)) {
+ send_packet(ssh, SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION,
+ PKT_INT, c->remoteid, PKT_END);
+ c->closes |= CLOSES_SENT_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 ? "" :
} else if (pktin->type == SSH1_SMSG_FAILURE) {
c_write_str(ssh, "Server refused to allocate pty\r\n");
ssh->editing = ssh->echoing = 1;
- }
- logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
- ssh->ospeed, ssh->ispeed);
+ } else {
+ logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+ ssh->ospeed, ssh->ispeed);
+ ssh->got_pty = TRUE;
+ }
} else {
ssh->editing = ssh->echoing = 1;
}
/*
* Handle the SSH-2 transport layer.
*/
-static int do_ssh2_transport(Ssh ssh, void *vin, int inlen,
+static void do_ssh2_transport(Ssh ssh, void *vin, int inlen,
struct Packet *pktin)
{
unsigned char *in = (unsigned char *)vin;
struct do_ssh2_transport_state {
+ int crLine;
int nbits, pbits, warn_kex, warn_cscipher, warn_sccipher;
Bignum p, g, e, f, K;
void *our_kexinit;
};
crState(do_ssh2_transport_state);
- crBegin(ssh->do_ssh2_transport_crstate);
+ crBeginState;
s->cscipher_tobe = s->sccipher_tobe = NULL;
s->csmac_tobe = s->scmac_tobe = NULL;
begin_key_exchange:
ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
{
- int i, j, commalist_started;
+ int i, j, k, commalist_started;
/*
* Set up the preferred key exchange. (NULL => warn below here)
if (i < lenof(hostkey_algs) - 1)
ssh2_pkt_addstring_str(s->pktout, ",");
}
- /* List client->server encryption algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
- for (i = 0; i < s->n_preferred_ciphers; i++) {
- const struct ssh2_ciphers *c = s->preferred_ciphers[i];
- if (!c) continue; /* warning flag */
- for (j = 0; j < c->nciphers; j++) {
- if (commalist_started)
- ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
- commalist_started = 1;
+ /* List encryption algorithms (client->server then server->client). */
+ for (k = 0; k < 2; k++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ commalist_started = 0;
+ for (i = 0; i < s->n_preferred_ciphers; i++) {
+ const struct ssh2_ciphers *c = s->preferred_ciphers[i];
+ if (!c) continue; /* warning flag */
+ for (j = 0; j < c->nciphers; j++) {
+ if (commalist_started)
+ ssh2_pkt_addstring_str(s->pktout, ",");
+ ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
+ commalist_started = 1;
+ }
}
}
- /* List server->client encryption algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
- for (i = 0; i < s->n_preferred_ciphers; i++) {
- const struct ssh2_ciphers *c = s->preferred_ciphers[i];
- if (!c) continue; /* warning flag */
- for (j = 0; j < c->nciphers; j++) {
- if (commalist_started)
+ /* List MAC algorithms (client->server then server->client). */
+ for (j = 0; j < 2; j++) {
+ ssh2_pkt_addstring_start(s->pktout);
+ for (i = 0; i < s->nmacs; i++) {
+ ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
+ if (i < s->nmacs - 1)
ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout, c->list[j]->name);
- commalist_started = 1;
}
}
- /* List client->server MAC algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- for (i = 0; i < s->nmacs; i++) {
- ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
- if (i < s->nmacs - 1)
- ssh2_pkt_addstring_str(s->pktout, ",");
- }
- /* List server->client MAC algorithms. */
- ssh2_pkt_addstring_start(s->pktout);
- for (i = 0; i < s->nmacs; i++) {
- ssh2_pkt_addstring_str(s->pktout, s->maclist[i]->name);
- if (i < s->nmacs - 1)
- ssh2_pkt_addstring_str(s->pktout, ",");
- }
/* List client->server compression algorithms,
* then server->client compression algorithms. (We use the
* same set twice.) */
ssh2_pkt_send_noqueue(ssh, s->pktout);
if (!pktin)
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
/*
* Now examine the other side's KEXINIT to see what we're up
if (pktin->type != SSH2_MSG_KEXINIT) {
bombout(("expected key exchange packet from server"));
- crStop(0);
+ crStopV;
}
ssh->kex = NULL;
ssh->hostkey = NULL;
if (!ssh->kex) {
bombout(("Couldn't agree a key exchange algorithm (available: %s)",
str ? str : "(null)"));
- crStop(0);
+ crStopV;
}
/*
* Note that the server's guess is considered wrong if it doesn't match
break;
}
}
+ if (!ssh->hostkey) {
+ bombout(("Couldn't agree a host key algorithm (available: %s)",
+ str ? str : "(null)"));
+ crStopV;
+ }
+
s->guessok = s->guessok &&
first_in_commasep_string(hostkey_algs[0]->name, str, len);
ssh_pkt_getstring(pktin, &str, &len); /* client->server cipher */
if (!s->cscipher_tobe) {
bombout(("Couldn't agree a client-to-server cipher (available: %s)",
str ? str : "(null)"));
- crStop(0);
+ crStopV;
}
ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */
if (!s->sccipher_tobe) {
bombout(("Couldn't agree a server-to-client cipher (available: %s)",
str ? str : "(null)"));
- crStop(0);
+ crStopV;
}
ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */
ssh_dialog_callback, ssh);
if (s->dlgret < 0) {
do {
- crReturn(0);
+ crReturnV;
if (pktin) {
bombout(("Unexpected data from server while"
" waiting for user response"));
- crStop(0);
+ crStopV;
}
} while (pktin || inlen > 0);
s->dlgret = ssh->user_response;
if (s->dlgret == 0) {
ssh_disconnect(ssh, "User aborted at kex warning", NULL,
0, TRUE);
- crStop(0);
+ crStopV;
}
}
ssh_dialog_callback, ssh);
if (s->dlgret < 0) {
do {
- crReturn(0);
+ crReturnV;
if (pktin) {
bombout(("Unexpected data from server while"
" waiting for user response"));
- crStop(0);
+ crStopV;
}
} while (pktin || inlen > 0);
s->dlgret = ssh->user_response;
if (s->dlgret == 0) {
ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
0, TRUE);
- crStop(0);
+ crStopV;
}
}
ssh_dialog_callback, ssh);
if (s->dlgret < 0) {
do {
- crReturn(0);
+ crReturnV;
if (pktin) {
bombout(("Unexpected data from server while"
" waiting for user response"));
- crStop(0);
+ crStopV;
}
} while (pktin || inlen > 0);
s->dlgret = ssh->user_response;
if (s->dlgret == 0) {
ssh_disconnect(ssh, "User aborted at cipher warning", NULL,
0, TRUE);
- crStop(0);
+ crStopV;
}
}
pktin->data + 5, pktin->length - 5);
if (s->ignorepkt) /* first_kex_packet_follows */
- crWaitUntil(pktin); /* Ignore packet */
+ crWaitUntilV(pktin); /* Ignore packet */
}
if (ssh->kex->main_type == KEXTYPE_DH) {
ssh2_pkt_adduint32(s->pktout, s->pbits);
ssh2_pkt_send_noqueue(ssh, s->pktout);
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
if (pktin->type != SSH2_MSG_KEX_DH_GEX_GROUP) {
bombout(("expected key exchange group packet from server"));
- crStop(0);
+ crStopV;
}
s->p = ssh2_pkt_getmp(pktin);
s->g = ssh2_pkt_getmp(pktin);
if (!s->p || !s->g) {
bombout(("unable to read mp-ints from incoming group packet"));
- crStop(0);
+ crStopV;
}
ssh->kex_ctx = dh_setup_gex(s->p, s->g);
s->kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
ssh2_pkt_send_noqueue(ssh, s->pktout);
set_busy_status(ssh->frontend, BUSY_WAITING); /* wait for server */
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
if (pktin->type != s->kex_reply_value) {
bombout(("expected key exchange reply packet from server"));
- crStop(0);
+ crStopV;
}
set_busy_status(ssh->frontend, BUSY_CPU); /* cogitate */
ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
s->f = ssh2_pkt_getmp(pktin);
if (!s->f) {
bombout(("unable to parse key exchange reply packet"));
- crStop(0);
+ crStopV;
}
ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
* RSA key exchange. First expect a KEXRSA_PUBKEY packet
* from the server.
*/
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
if (pktin->type != SSH2_MSG_KEXRSA_PUBKEY) {
bombout(("expected RSA public key packet from server"));
- crStop(0);
+ crStopV;
}
ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
if (!s->rsakey) {
sfree(s->rsakeydata);
bombout(("unable to parse RSA public key from server"));
- crStop(0);
+ crStopV;
}
hash_string(ssh->kex->hash, ssh->exhash, s->rsakeydata, s->rsakeylen);
ssh_rsakex_freekey(s->rsakey);
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
if (pktin->type != SSH2_MSG_KEXRSA_DONE) {
sfree(s->rsakeydata);
bombout(("expected signature packet from server"));
- crStop(0);
+ crStopV;
}
ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
(char *)s->exchange_hash,
ssh->kex->hash->hlen)) {
bombout(("Server's host key did not match the signature supplied"));
- crStop(0);
+ crStopV;
}
/*
ssh_dialog_callback, ssh);
if (s->dlgret < 0) {
do {
- crReturn(0);
+ crReturnV;
if (pktin) {
bombout(("Unexpected data from server while waiting"
" for user host key response"));
- crStop(0);
+ crStopV;
}
} while (pktin || inlen > 0);
s->dlgret = ssh->user_response;
if (s->dlgret == 0) {
ssh_disconnect(ssh, "User aborted at host key verification", NULL,
0, TRUE);
- crStop(0);
+ crStopV;
}
if (!s->got_session_id) { /* don't bother logging this in rekeys */
logevent("Host key fingerprint is:");
assert(ssh->csmac->len <=
ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
ssh->csmac->setkey(ssh->cs_mac_ctx, keyspace);
- memset(keyspace, 0, sizeof(keyspace));
+ smemclr(keyspace, sizeof(keyspace));
}
logeventf(ssh, "Initialised %.200s client->server encryption",
/*
* Expect SSH2_MSG_NEWKEYS from server.
*/
- crWaitUntil(pktin);
+ crWaitUntilV(pktin);
if (pktin->type != SSH2_MSG_NEWKEYS) {
bombout(("expected new-keys packet from server"));
- crStop(0);
+ crStopV;
}
ssh->incoming_data_size = 0; /* start counting from here */
assert(ssh->scmac->len <=
ssh->kex->hash->hlen * SSH2_MKKEY_ITERS);
ssh->scmac->setkey(ssh->sc_mac_ctx, keyspace);
- memset(keyspace, 0, sizeof(keyspace));
+ smemclr(keyspace, sizeof(keyspace));
}
logeventf(ssh, "Initialised %.200s server->client encryption",
ssh->sccipher->text_name);
ssh->next_rekey = schedule_timer(conf_get_int(ssh->conf, CONF_ssh_rekey_time)*60*TICKSPERSEC,
ssh2_timer, ssh);
- /*
- * If this is the first key exchange phase, we must pass the
- * SSH2_MSG_NEWKEYS packet to the next layer, not because it
- * wants to see it but because it will need time to initialise
- * itself before it sees an actual packet. In subsequent key
- * exchange phases, we don't pass SSH2_MSG_NEWKEYS on, because
- * it would only confuse the layer above.
- */
- if (s->activated_authconn) {
- crReturn(0);
- }
- s->activated_authconn = TRUE;
-
/*
* Now we're encrypting. Begin returning 1 to the protocol main
* function so that other things can run on top of the
while (!((pktin && pktin->type == SSH2_MSG_KEXINIT) ||
(!pktin && inlen < 0))) {
wait_for_rekey:
- crReturn(1);
+ if (!ssh->protocol_initial_phase_done) {
+ ssh->protocol_initial_phase_done = TRUE;
+ /*
+ * Allow authconn to initialise itself.
+ */
+ do_ssh2_authconn(ssh, NULL, 0, NULL);
+ }
+ crReturnV;
}
if (pktin) {
logevent("Server initiated key re-exchange");
}
goto begin_key_exchange;
- crFinish(1);
+ crFinishV;
}
/*
{
Ssh ssh = c->ssh;
struct Packet *pktout;
+ int ret;
while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
int len;
* After having sent as much data as we can, return the amount
* still buffered.
*/
- return bufchain_size(&c->v.v2.outbuffer);
+ ret = bufchain_size(&c->v.v2.outbuffer);
+
+ /*
+ * And if there's no data pending but we need to send an EOF, send
+ * it.
+ */
+ if (!ret && c->pending_eof)
+ ssh_channel_try_eof(c);
+
+ return ret;
}
static void ssh2_try_send_and_unthrottle(Ssh ssh, struct ssh_channel *c)
{
int bufsize;
- if (c->closes)
- return; /* don't send on closing channels */
+ if (c->closes & CLOSES_SENT_EOF)
+ return; /* don't send on channels we've EOFed */
bufsize = ssh2_try_send(c);
if (bufsize == 0) {
switch (c->type) {
break;
}
}
-
- /*
- * If we've emptied the channel's output buffer and there's a
- * pending close event, start the channel-closing procedure.
- */
- if (c->pending_close && bufchain_size(&c->v.v2.outbuffer) == 0) {
- struct Packet *pktout;
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- c->closes = 1;
- c->pending_close = FALSE;
- }
}
/*
Ssh ssh = c->ssh;
c->localid = alloc_channel_id(ssh);
c->closes = 0;
- c->pending_close = FALSE;
+ c->pending_eof = FALSE;
c->throttling_conn = FALSE;
c->v.v2.locwindow = c->v.v2.locmaxwin = c->v.v2.remlocwin =
conf_get_int(ssh->conf, CONF_ssh_simple) ? OUR_V2_BIGWIN : OUR_V2_WINSIZE;
- c->v.v2.winadj_head = c->v.v2.winadj_tail = NULL;
+ c->v.v2.chanreq_head = NULL;
c->v.v2.throttle_state = UNTHROTTLED;
bufchain_init(&c->v.v2.outbuffer);
}
+/*
+ * 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.
+ */
+static void ssh2_queue_chanreq_handler(struct ssh_channel *c,
+ cchandler_fn_t handler, void *ctx)
+{
+ struct outstanding_channel_request *ocr =
+ snew(struct outstanding_channel_request);
+
+ assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+ ocr->handler = handler;
+ ocr->ctx = ctx;
+ ocr->next = NULL;
+ if (!c->v.v2.chanreq_head)
+ c->v.v2.chanreq_head = ocr;
+ else
+ c->v.v2.chanreq_tail->next = ocr;
+ c->v.v2.chanreq_tail = ocr;
+}
+
+/*
+ * Construct the common parts of a CHANNEL_REQUEST. If handler is not
+ * NULL then a reply will be requested and the handler will be called
+ * when it arrives. The returned packet is ready to have any
+ * request-specific data added and be sent. Note that if a handler is
+ * provided, it's essential that the request actually be sent.
+ *
+ * The handler will usually be passed the response packet in pktin.
+ * If pktin is NULL, this means that no reply will ever be forthcoming
+ * (e.g. because the entire connection is being destroyed) and the
+ * handler should free any storage it's holding.
+ */
+static struct Packet *ssh2_chanreq_init(struct ssh_channel *c, char *type,
+ cchandler_fn_t handler, void *ctx)
+{
+ struct Packet *pktout;
+
+ assert(!(c->closes & (CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE)));
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_addstring(pktout, type);
+ ssh2_pkt_addbool(pktout, handler != NULL);
+ if (handler != NULL)
+ ssh2_queue_chanreq_handler(c, handler, ctx);
+ return pktout;
+}
+
/*
* Potentially enlarge the window on an SSH-2 channel.
*/
+static void ssh2_handle_winadj_response(struct ssh_channel *, struct Packet *,
+ void *);
static void ssh2_set_window(struct ssh_channel *c, int newwin)
{
Ssh ssh = c->ssh;
/*
- * Never send WINDOW_ADJUST for a channel that the remote side
- * already thinks it's closed; there's no point, since it won't
- * be sending any more data anyway.
+ * Never send WINDOW_ADJUST for a channel that the remote side has
+ * already sent EOF on; there's no point, since it won't be
+ * sending any more data anyway. Ditto if _we've_ already sent
+ * CLOSE.
*/
- if (c->closes != 0)
+ if (c->closes & (CLOSES_RCVD_EOF | CLOSES_SENT_CLOSE))
return;
/*
*/
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
*/
if (newwin / 2 >= c->v.v2.locwindow) {
struct Packet *pktout;
- struct winadj *wa;
+ unsigned *up;
/*
* In order to keep track of how much window the client
* 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);
+ !(ssh->remote_bugs & BUG_CHOKES_ON_WINADJ)) {
+ up = snew(unsigned);
+ *up = newwin - c->v.v2.locwindow;
+ pktout = ssh2_chanreq_init(c, "winadj@putty.projects.tartarus.org",
+ ssh2_handle_winadj_response, up);
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 {
return c;
}
-static int ssh2_handle_winadj_response(struct ssh_channel *c)
+static void ssh2_handle_winadj_response(struct ssh_channel *c,
+ struct Packet *pktin, void *ctx)
{
- struct winadj *wa = c->v.v2.winadj_head;
- if (!wa)
- return FALSE;
- c->v.v2.winadj_head = wa->next;
- c->v.v2.remlocwin += wa->size;
- sfree(wa);
+ unsigned *sizep = ctx;
+
+ /*
+ * Winadj responses should always be failures. However, at least
+ * one server ("boks_sshd") is known to return SUCCESS for channel
+ * requests it's never heard of, such as "winadj@putty". Raised
+ * with foxt.com as bug 090916-090424, but for the sake of a quiet
+ * life, we don't worry about what kind of response we got.
+ */
+
+ c->v.v2.remlocwin += *sizep;
+ sfree(sizep);
/*
* 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
*/
if (c->v.v2.throttle_state == UNTHROTTLING)
c->v.v2.throttle_state = UNTHROTTLED;
- return TRUE;
}
-static void ssh2_msg_channel_success(Ssh ssh, struct Packet *pktin)
+static void ssh2_msg_channel_response(Ssh ssh, struct Packet *pktin)
{
- /*
- * This should never get called. All channel requests are either
- * sent with want_reply false, are sent before this handler gets
- * installed, or are "winadj@putty" requests, which servers should
- * never respond to with success.
- *
- * However, at least one server ("boks_sshd") is known to return
- * SUCCESS for channel requests it's never heard of, such as
- * "winadj@putty". Raised with foxt.com as bug 090916-090424, but
- * for the sake of a quiet life, we handle it just the same as the
- * expected FAILURE.
- */
- struct ssh_channel *c;
+ struct ssh_channel *c = ssh2_channel_msg(ssh, pktin);
+ struct outstanding_channel_request *ocr;
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
+ if (!c) return;
+ ocr = c->v.v2.chanreq_head;
+ if (!ocr) {
+ ssh2_msg_unexpected(ssh, pktin);
return;
- if (!ssh2_handle_winadj_response(c))
- 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)
-{
+ }
+ ocr->handler(c, pktin, ocr->ctx);
+ c->v.v2.chanreq_head = ocr->next;
+ sfree(ocr);
/*
- * 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.
+ * We may now initiate channel-closing procedures, if that
+ * CHANNEL_REQUEST was the last thing outstanding before we send
+ * CHANNEL_CLOSE.
*/
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- if (!ssh2_handle_winadj_response(c))
- ssh_disconnect(ssh, NULL,
- "Received unsolicited SSH_MSG_CHANNEL_FAILURE",
- SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ ssh2_channel_check_close(c);
}
static void ssh2_msg_channel_window_adjust(Ssh ssh, struct Packet *pktin)
c = ssh2_channel_msg(ssh, pktin);
if (!c)
return;
- if (!c->closes) {
+ if (!(c->closes & CLOSES_SENT_EOF)) {
c->v.v2.remwindow += ssh_pkt_getuint32(pktin);
ssh2_try_send_and_unthrottle(ssh, c);
}
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;
}
}
}
}
-static void ssh2_msg_channel_eof(Ssh ssh, struct Packet *pktin)
-{
- struct ssh_channel *c;
-
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
-
- if (c->type == CHAN_X11) {
- /*
- * Remote EOF on an X11 channel means we should
- * wrap up and close the channel ourselves.
- */
- x11_close(c->u.x11.s);
- c->u.x11.s = NULL;
- sshfwd_close(c);
- } else if (c->type == CHAN_AGENT) {
- sshfwd_close(c);
- } else if (c->type == CHAN_SOCKDATA) {
- pfd_close(c->u.pfd.s);
- c->u.pfd.s = NULL;
- sshfwd_close(c);
- }
-}
-
-static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
+static void ssh_channel_destroy(struct ssh_channel *c)
{
- struct ssh_channel *c;
- struct Packet *pktout;
+ Ssh ssh = c->ssh;
- c = ssh2_channel_msg(ssh, pktin);
- if (!c)
- return;
- /* Do pre-close processing on the channel. */
switch (c->type) {
case CHAN_MAINSESSION:
- ssh->mainchan = NULL;
- update_specials_menu(ssh->frontend);
- break;
+ ssh->mainchan = NULL;
+ update_specials_menu(ssh->frontend);
+ break;
case CHAN_X11:
- if (c->u.x11.s != NULL)
- x11_close(c->u.x11.s);
- sshfwd_close(c);
- break;
+ if (c->u.x11.s != NULL)
+ x11_close(c->u.x11.s);
+ logevent("Forwarded X11 connection terminated");
+ break;
case CHAN_AGENT:
- sshfwd_close(c);
- break;
+ sfree(c->u.a.message);
+ break;
case CHAN_SOCKDATA:
- if (c->u.pfd.s != NULL)
- pfd_close(c->u.pfd.s);
- sshfwd_close(c);
- break;
- }
- if (c->closes == 0) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
+ if (c->u.pfd.s != NULL)
+ pfd_close(c->u.pfd.s);
+ logevent("Forwarded port closed");
+ break;
}
+
del234(ssh->channels, c);
- bufchain_clear(&c->v.v2.outbuffer);
+ if (ssh->version == 2) {
+ bufchain_clear(&c->v.v2.outbuffer);
+ assert(c->v.v2.chanreq_head == NULL);
+ }
sfree(c);
/*
* (This is only our termination condition if we're
* not running in -N mode.)
*/
- if (!conf_get_int(ssh->conf, CONF_ssh_no_shell) && count234(ssh->channels) == 0) {
- /*
- * We used to send SSH_MSG_DISCONNECT here,
- * because I'd believed that _every_ conforming
- * SSH-2 connection had to end with a disconnect
- * being sent by at least one side; apparently
- * I was wrong and it's perfectly OK to
- * unceremoniously slam the connection shut
- * when you're done, and indeed OpenSSH feels
- * this is more polite than sending a
- * DISCONNECT. So now we don't.
- */
- ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
+ if (ssh->version == 2 &&
+ !conf_get_int(ssh->conf, CONF_ssh_no_shell) &&
+ count234(ssh->channels) == 0) {
+ /*
+ * We used to send SSH_MSG_DISCONNECT here,
+ * because I'd believed that _every_ conforming
+ * SSH-2 connection had to end with a disconnect
+ * being sent by at least one side; apparently
+ * I was wrong and it's perfectly OK to
+ * unceremoniously slam the connection shut
+ * when you're done, and indeed OpenSSH feels
+ * this is more polite than sending a
+ * DISCONNECT. So now we don't.
+ */
+ ssh_disconnect(ssh, "All channels closed", NULL, 0, TRUE);
+ }
+}
+
+static void ssh2_channel_check_close(struct ssh_channel *c)
+{
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+
+ if ((!((CLOSES_SENT_EOF | CLOSES_RCVD_EOF) & ~c->closes) ||
+ c->type == CHAN_ZOMBIE) &&
+ !c->v.v2.chanreq_head &&
+ !(c->closes & CLOSES_SENT_CLOSE)) {
+ /*
+ * We have both sent and received EOF (or the channel is a
+ * zombie), and we have no outstanding channel requests, which
+ * means the channel is in final wind-up. But we haven't sent
+ * CLOSE, so let's do so now.
+ */
+ pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(pktout, c->remoteid);
+ ssh2_pkt_send(ssh, pktout);
+ c->closes |= CLOSES_SENT_EOF | CLOSES_SENT_CLOSE;
+ }
+
+ if (!((CLOSES_SENT_CLOSE | CLOSES_RCVD_CLOSE) & ~c->closes)) {
+ assert(c->v.v2.chanreq_head == NULL);
+ /*
+ * We have both sent and received CLOSE, which means we're
+ * completely done with the channel.
+ */
+ ssh_channel_destroy(c);
+ }
+}
+
+static void ssh2_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) {
+ x11_send_eof(c->u.x11.s);
+ } else if (c->type == CHAN_AGENT) {
+ /* Manufacture an outgoing EOF in response to the incoming one. */
+ sshfwd_write_eof(c);
+ } else if (c->type == CHAN_SOCKDATA) {
+ pfd_send_eof(c->u.pfd.s);
+ } else if (c->type == CHAN_MAINSESSION) {
+ Ssh ssh = c->ssh;
+
+ if (!ssh->sent_console_eof &&
+ (from_backend_eof(ssh->frontend) || ssh->got_pty)) {
+ /*
+ * Either from_backend_eof told us that the front end
+ * wants us to close the outgoing side of the connection
+ * as soon as we see EOF from the far end, or else we've
+ * unilaterally decided to do that because we've allocated
+ * a remote pty and hence EOF isn't a particularly
+ * meaningful concept.
+ */
+ sshfwd_write_eof(c);
+ }
+ 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);
+ if (!c)
+ return;
+ ssh2_channel_got_eof(c);
+}
+
+static void ssh2_msg_channel_close(Ssh ssh, struct Packet *pktin)
+{
+ struct ssh_channel *c;
+
+ c = ssh2_channel_msg(ssh, pktin);
+ if (!c)
+ 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);
+
+ /*
+ * And we also send an outgoing EOF, if we haven't already, on the
+ * assumption that CLOSE is a pretty forceful announcement that
+ * the remote side is doing away with the entire channel. (If it
+ * had wanted to send us EOF and continue receiving data from us,
+ * it would have just sent CHANNEL_EOF.)
+ */
+ if (!(c->closes & CLOSES_SENT_EOF)) {
+ /*
+ * Make sure we don't read any more from whatever our local
+ * data source is for this channel.
+ */
+ switch (c->type) {
+ case CHAN_MAINSESSION:
+ ssh->send_ok = 0; /* stop trying to read from stdin */
+ break;
+ case CHAN_X11:
+ x11_override_throttle(c->u.x11.s, 1);
+ break;
+ case CHAN_SOCKDATA:
+ pfd_override_throttle(c->u.pfd.s, 1);
+ break;
+ }
+
+ /*
+ * Send outgoing EOF.
+ */
+ sshfwd_write_eof(c);
+ }
+
+ /*
+ * Now process the actual close.
+ */
+ if (!(c->closes & CLOSES_RCVD_CLOSE)) {
+ c->closes |= CLOSES_RCVD_CLOSE;
+ ssh2_channel_check_close(c);
}
}
static void ssh2_msg_channel_open_confirmation(Ssh ssh, struct Packet *pktin)
{
struct ssh_channel *c;
- struct Packet *pktout;
c = ssh2_channel_msg(ssh, pktin);
if (!c)
c->v.v2.remmaxpkt = ssh_pkt_getuint32(pktin);
if (c->u.pfd.s)
pfd_confirm(c->u.pfd.s);
- if (c->closes) {
- /*
- * We have a pending close on this channel,
- * which we decided on before the server acked
- * the channel open. So now we know the
- * remoteid, we can close it again.
- */
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(pktout, c->remoteid);
- ssh2_pkt_send(ssh, pktout);
- }
+ if (c->pending_eof)
+ ssh_channel_try_eof(c);
}
static void ssh2_msg_channel_open_failure(Ssh ssh, struct Packet *pktin)
ssh2_pkt_adduint32(pktout, arg);
}
+static void ssh2_setup_x11(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_x11_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_x11_state, ctx);
+
+ crBeginState;
+
+ logevent("Requesting X11 forwarding");
+ pktout = ssh2_chanreq_init(ssh->mainchan, "x11-req",
+ ssh2_setup_x11, s);
+ ssh2_pkt_addbool(pktout, 0); /* many connections */
+ ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthprotoname);
+ /*
+ * Note that while we blank the X authentication data here, we don't
+ * take any special action to blank the start of an X11 channel,
+ * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
+ * without having session blanking enabled is likely to leak your
+ * cookie into the log.
+ */
+ dont_log_password(ssh, pktout, PKTLOG_BLANK);
+ ssh2_pkt_addstring(pktout, ssh->x11disp->remoteauthdatastring);
+ end_log_omission(ssh, pktout);
+ ssh2_pkt_adduint32(pktout, ssh->x11disp->screennum);
+ ssh2_pkt_send(ssh, pktout);
+
+ crWaitUntilV(pktin);
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logevent("X11 forwarding enabled");
+ ssh->X11_fwd_enabled = TRUE;
+ } else
+ logevent("X11 forwarding refused");
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_agent(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_agent_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_agent_state, ctx);
+
+ crBeginState;
+
+ logevent("Requesting OpenSSH-style agent forwarding");
+ pktout = ssh2_chanreq_init(ssh->mainchan, "auth-agent-req@openssh.com",
+ ssh2_setup_agent, s);
+ ssh2_pkt_send(ssh, pktout);
+
+ crWaitUntilV(pktin);
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logevent("Agent forwarding enabled");
+ ssh->agentfwd_enabled = TRUE;
+ } else
+ logevent("Agent forwarding refused");
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_pty(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_pty_state {
+ int crLine;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_pty_state, ctx);
+
+ crBeginState;
+
+ /* Unpick the terminal-speed string. */
+ /* XXX perhaps we should allow no speeds to be sent. */
+ ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
+ sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
+ /* Build the pty request. */
+ pktout = ssh2_chanreq_init(ssh->mainchan, "pty-req",
+ ssh2_setup_pty, s);
+ ssh2_pkt_addstring(pktout, conf_get_str(ssh->conf, CONF_termtype));
+ ssh2_pkt_adduint32(pktout, ssh->term_width);
+ ssh2_pkt_adduint32(pktout, ssh->term_height);
+ ssh2_pkt_adduint32(pktout, 0); /* pixel width */
+ ssh2_pkt_adduint32(pktout, 0); /* pixel height */
+ ssh2_pkt_addstring_start(pktout);
+ parse_ttymodes(ssh, ssh2_send_ttymode, (void *)pktout);
+ ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_ISPEED);
+ ssh2_pkt_adduint32(pktout, ssh->ispeed);
+ ssh2_pkt_addbyte(pktout, SSH2_TTY_OP_OSPEED);
+ ssh2_pkt_adduint32(pktout, ssh->ospeed);
+ ssh2_pkt_addstring_data(pktout, "\0", 1); /* TTY_OP_END */
+ ssh2_pkt_send(ssh, pktout);
+ ssh->state = SSH_STATE_INTERMED;
+
+ crWaitUntilV(pktin);
+
+ if (pktin) {
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS) {
+ logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
+ ssh->ospeed, ssh->ispeed);
+ ssh->got_pty = TRUE;
+ } else {
+ c_write_str(ssh, "Server refused to allocate pty\r\n");
+ ssh->editing = ssh->echoing = 1;
+ }
+ }
+
+ crFinishFreeV;
+}
+
+static void ssh2_setup_env(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ struct ssh2_setup_env_state {
+ int crLine;
+ int num_env, env_left, env_ok;
+ };
+ Ssh ssh = c->ssh;
+ struct Packet *pktout;
+ crStateP(ssh2_setup_env_state, ctx);
+
+ crBeginState;
+
+ /*
+ * Send environment variables.
+ *
+ * Simplest thing here is to send all the requests at once, and
+ * then wait for a whole bunch of successes or failures.
+ */
+ s->num_env = 0;
+ {
+ char *key, *val;
+
+ for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
+ val != NULL;
+ val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
+ pktout = ssh2_chanreq_init(ssh->mainchan, "env", ssh2_setup_env, s);
+ ssh2_pkt_addstring(pktout, key);
+ ssh2_pkt_addstring(pktout, val);
+ ssh2_pkt_send(ssh, pktout);
+
+ s->num_env++;
+ }
+ if (s->num_env)
+ logeventf(ssh, "Sent %d environment variables", s->num_env);
+ }
+
+ if (s->num_env) {
+ s->env_ok = 0;
+ s->env_left = s->num_env;
+
+ while (s->env_left > 0) {
+ crWaitUntilV(pktin);
+ if (!pktin) goto out;
+ if (pktin->type == SSH2_MSG_CHANNEL_SUCCESS)
+ s->env_ok++;
+ s->env_left--;
+ }
+
+ if (s->env_ok == s->num_env) {
+ logevent("All environment variables successfully set");
+ } else if (s->env_ok == 0) {
+ logevent("All environment variables refused");
+ c_write_str(ssh, "Server refused to set environment variables\r\n");
+ } else {
+ logeventf(ssh, "%d environment variables refused",
+ s->num_env - s->env_ok);
+ c_write_str(ssh, "Server refused to set all environment variables\r\n");
+ }
+ }
+ out:;
+ crFinishFreeV;
+}
+
/*
* Handle the SSH-2 userauth and connection layers.
*/
+static void ssh2_msg_authconn(Ssh ssh, struct Packet *pktin)
+{
+ do_ssh2_authconn(ssh, NULL, 0, pktin);
+}
+
+static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
+ void *ctx)
+{
+ do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+}
+
static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
struct Packet *pktin)
{
struct do_ssh2_authconn_state {
+ int crLine;
enum {
AUTH_TYPE_NONE,
AUTH_TYPE_PUBLICKEY,
int siglen, retlen, len;
char *q, *agentreq, *ret;
int try_send;
- int num_env, env_left, env_ok;
struct Packet *pktout;
Filename *keyfile;
#ifndef NO_GSSAPI
};
crState(do_ssh2_authconn_state);
- crBegin(ssh->do_ssh2_authconn_crstate);
-
+ crBeginState;
+
+ /* Register as a handler for all the messages this coroutine handles. */
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_authconn;
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_authconn; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_authconn; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_authconn;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_authconn;
+
s->done_service_req = FALSE;
s->we_are_in = s->userauth_success = FALSE;
#ifndef NO_GSSAPI
* for later use.
*/
s->keyfile = conf_get_filename(ssh->conf, CONF_keyfile);
- if (!filename_is_null(*s->keyfile)) {
+ if (!filename_is_null(s->keyfile)) {
int keytype;
logeventf(ssh, "Reading private key file \"%.150s\"",
filename_to_str(s->keyfile));
s->cur_prompt = new_prompts(ssh->frontend);
s->cur_prompt->to_server = TRUE;
s->cur_prompt->name = dupstr("SSH login name");
- /* 512 is an arbitrary limit :-( */
- add_prompt(s->cur_prompt, dupstr("login as: "), TRUE, 512);
+ add_prompt(s->cur_prompt, dupstr("login as: "), TRUE);
ret = get_userpass_input(s->cur_prompt, NULL, 0);
while (ret < 0) {
ssh->send_ok = 1;
logevent("Server refused keyboard-interactive authentication");
} else if (s->type==AUTH_TYPE_GSSAPI) {
/* always quiet, so no c_write */
- logevent("GSSAPI authentication failed");
+ /* also, the code down in the GSSAPI block has
+ * already logged this in the Event Log */
} else if (s->type == AUTH_TYPE_KEYBOARD_INTERACTIVE) {
logevent("Keyboard-interactive authentication failed");
c_write_str(ssh, "Access denied\r\n");
add_prompt(s->cur_prompt,
dupprintf("Passphrase for key \"%.100s\": ",
s->publickey_comment),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
ret = get_userpass_input(s->cur_prompt, NULL, 0);
while (ret < 0) {
ssh->send_ok = 1;
key = ssh2_load_userkey(s->keyfile, passphrase, &error);
if (passphrase) {
/* burn the evidence */
- memset(passphrase, 0, strlen(passphrase));
+ smemclr(passphrase, strlen(passphrase));
sfree(passphrase);
}
if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
}
add_prompt(s->cur_prompt,
dupprintf("%.*s", prompt_len, prompt),
- echo, SSH_MAX_PASSWORD_LEN);
+ echo);
}
if (name_len) {
}
ssh2_pkt_send_with_padding(ssh, s->pktout, 256);
+ /*
+ * Free the prompts structure from this iteration.
+ * If there's another, a new one will be allocated
+ * when we return to the top of this while loop.
+ */
+ free_prompts(s->cur_prompt);
+
/*
* Get the next packet in case it's another
* INFO_REQUEST.
add_prompt(s->cur_prompt, dupprintf("%s@%s's password: ",
ssh->username,
ssh->savedhost),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
ret = get_userpass_input(s->cur_prompt, NULL, 0);
while (ret < 0) {
*/
add_prompt(s->cur_prompt,
dupstr("Current password (blank for previously entered password): "),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
add_prompt(s->cur_prompt, dupstr("Enter new password: "),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
add_prompt(s->cur_prompt, dupstr("Confirm new password: "),
- FALSE, SSH_MAX_PASSWORD_LEN);
+ FALSE);
/*
* Loop until the user manages to enter the same
*/
/* burn the evidence */
free_prompts(s->cur_prompt);
- memset(s->password, 0, strlen(s->password));
+ smemclr(s->password, strlen(s->password));
sfree(s->password);
ssh_disconnect(ssh, NULL, "Unable to authenticate",
SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER,
* re-enter it if they louse up the new password.)
*/
if (s->cur_prompt->prompts[0]->result[0]) {
- memset(s->password, 0, strlen(s->password));
+ smemclr(s->password, strlen(s->password));
/* burn the evidence */
sfree(s->password);
s->password =
* We don't need the old password any more, in any
* case. Burn the evidence.
*/
- memset(s->password, 0, strlen(s->password));
+ smemclr(s->password, strlen(s->password));
sfree(s->password);
} else {
ssh2_msg_channel_request;
ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] =
ssh2_msg_channel_open;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_channel_response;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_channel_response;
+
if (ssh->mainchan && conf_get_int(ssh->conf, CONF_ssh_simple)) {
/*
* 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 */
+ s->pktout = ssh2_chanreq_init(ssh->mainchan,
+ "simple@putty.projects.tartarus.org",
+ NULL, NULL);
ssh2_pkt_send(ssh, s->pktout);
}
/*
- * Potentially enable X11 forwarding.
+ * Enable port forwardings.
*/
- if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
- (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
- conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf))) {
- logevent("Requesting X11 forwarding");
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "x11-req");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addbool(s->pktout, 0); /* many connections */
- ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthprotoname);
- /*
- * Note that while we blank the X authentication data here, we don't
- * take any special action to blank the start of an X11 channel,
- * so using MIT-MAGIC-COOKIE-1 and actually opening an X connection
- * without having session blanking enabled is likely to leak your
- * cookie into the log.
- */
- dont_log_password(ssh, s->pktout, PKTLOG_BLANK);
- ssh2_pkt_addstring(s->pktout, ssh->x11disp->remoteauthdatastring);
- end_log_omission(ssh, s->pktout);
- ssh2_pkt_adduint32(s->pktout, ssh->x11disp->screennum);
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
+ ssh_setup_portfwd(ssh, ssh->conf);
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to X11 forwarding request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- logevent("X11 forwarding refused");
- } else {
- logevent("X11 forwarding enabled");
- ssh->X11_fwd_enabled = TRUE;
- }
- }
+ /*
+ * Send the CHANNEL_REQUESTS for the main channel. Each one is
+ * handled by its own little asynchronous co-routine.
+ */
/*
- * Enable port forwardings.
+ * Potentially enable X11 forwarding.
*/
- ssh_setup_portfwd(ssh, ssh->conf);
+ /*
+ * Potentially enable X11 forwarding.
+ */
+ if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_x11_forward) &&
+ (ssh->x11disp = x11_setup_display(conf_get_str(ssh->conf, CONF_x11_display),
+ conf_get_int(ssh->conf, CONF_x11_auth), ssh->conf)))
+ ssh2_setup_x11(ssh->mainchan, NULL, NULL);
/*
* Potentially enable agent forwarding.
*/
- if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists()) {
- logevent("Requesting OpenSSH-style agent forwarding");
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "auth-agent-req@openssh.com");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_send(ssh, s->pktout);
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to agent forwarding request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- logevent("Agent forwarding refused");
- } else {
- logevent("Agent forwarding enabled");
- ssh->agentfwd_enabled = TRUE;
- }
- }
+ if (ssh->mainchan && !ssh->ncmode && conf_get_int(ssh->conf, CONF_agentfwd) && agent_exists())
+ ssh2_setup_agent(ssh->mainchan, NULL, NULL);
/*
* Now allocate a pty for the session.
*/
if (ssh->mainchan && !ssh->ncmode && !conf_get_int(ssh->conf, CONF_nopty)) {
- /* Unpick the terminal-speed string. */
- /* XXX perhaps we should allow no speeds to be sent. */
- ssh->ospeed = 38400; ssh->ispeed = 38400; /* last-resort defaults */
- sscanf(conf_get_str(ssh->conf, CONF_termspeed), "%d,%d", &ssh->ospeed, &ssh->ispeed);
- /* Build the pty request. */
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
- ssh2_pkt_addstring(s->pktout, "pty-req");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, conf_get_str(ssh->conf, CONF_termtype));
- ssh2_pkt_adduint32(s->pktout, ssh->term_width);
- ssh2_pkt_adduint32(s->pktout, ssh->term_height);
- ssh2_pkt_adduint32(s->pktout, 0); /* pixel width */
- ssh2_pkt_adduint32(s->pktout, 0); /* pixel height */
- ssh2_pkt_addstring_start(s->pktout);
- parse_ttymodes(ssh, ssh2_send_ttymode, (void *)s->pktout);
- ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_ISPEED);
- ssh2_pkt_adduint32(s->pktout, ssh->ispeed);
- ssh2_pkt_addbyte(s->pktout, SSH2_TTY_OP_OSPEED);
- ssh2_pkt_adduint32(s->pktout, ssh->ospeed);
- ssh2_pkt_addstring_data(s->pktout, "\0", 1); /* TTY_OP_END */
- ssh2_pkt_send(ssh, s->pktout);
- ssh->state = SSH_STATE_INTERMED;
-
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to pty request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- c_write_str(ssh, "Server refused to allocate pty\r\n");
- ssh->editing = ssh->echoing = 1;
- } else {
- logeventf(ssh, "Allocated pty (ospeed %dbps, ispeed %dbps)",
- ssh->ospeed, ssh->ispeed);
- }
+ ssh2_setup_pty(ssh->mainchan, NULL, NULL);
} else {
ssh->editing = ssh->echoing = 1;
}
/*
* Send environment variables.
- *
- * Simplest thing here is to send all the requests at once, and
- * then wait for a whole bunch of successes or failures.
*/
- if (ssh->mainchan && !ssh->ncmode) {
- char *key, *val;
-
- s->num_env = 0;
-
- for (val = conf_get_str_strs(ssh->conf, CONF_environmt, NULL, &key);
- val != NULL;
- val = conf_get_str_strs(ssh->conf, CONF_environmt, key, &key)) {
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(s->pktout, "env");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
- ssh2_pkt_addstring(s->pktout, key);
- ssh2_pkt_addstring(s->pktout, val);
- ssh2_pkt_send(ssh, s->pktout);
-
- s->num_env++;
- }
-
- if (s->num_env) {
- logeventf(ssh, "Sent %d environment variables", s->num_env);
-
- s->env_ok = 0;
- s->env_left = s->num_env;
-
- while (s->env_left > 0) {
- crWaitUntilV(pktin);
-
- if (pktin->type != SSH2_MSG_CHANNEL_SUCCESS) {
- if (pktin->type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Unexpected response to environment request:"
- " packet type %d", pktin->type));
- crStopV;
- }
- } else {
- s->env_ok++;
- }
-
- s->env_left--;
- }
-
- if (s->env_ok == s->num_env) {
- logevent("All environment variables successfully set");
- } else if (s->env_ok == 0) {
- logevent("All environment variables refused");
- c_write_str(ssh, "Server refused to set environment variables\r\n");
- } else {
- logeventf(ssh, "%d environment variables refused",
- s->num_env - s->env_ok);
- c_write_str(ssh, "Server refused to set all environment variables\r\n");
- }
- }
- }
+ if (ssh->mainchan && !ssh->ncmode)
+ ssh2_setup_env(ssh->mainchan, NULL, NULL);
/*
* Start a shell or a remote command. We may have to attempt
cmd = conf_get_str(ssh->conf, CONF_remote_cmd);
}
- s->pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(s->pktout, ssh->mainchan->remoteid); /* recipient channel */
if (subsys) {
- ssh2_pkt_addstring(s->pktout, "subsystem");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "subsystem",
+ ssh2_response_authconn, NULL);
ssh2_pkt_addstring(s->pktout, cmd);
} else if (*cmd) {
- ssh2_pkt_addstring(s->pktout, "exec");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "exec",
+ ssh2_response_authconn, NULL);
ssh2_pkt_addstring(s->pktout, cmd);
} else {
- ssh2_pkt_addstring(s->pktout, "shell");
- ssh2_pkt_addbool(s->pktout, 1); /* want reply */
+ s->pktout = ssh2_chanreq_init(ssh->mainchan, "shell",
+ ssh2_response_authconn, NULL);
}
ssh2_pkt_send(ssh, s->pktout);
if (ssh->eof_needed)
ssh_special(ssh, TS_EOF);
- /*
- * 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;
-
/*
* Transfer data!
*/
logeventf(ssh, "Remote debug message: %.*s", msglen, msg);
}
+static void ssh2_msg_transport(Ssh ssh, struct Packet *pktin)
+{
+ do_ssh2_transport(ssh, NULL, 0, pktin);
+}
+
+/*
+ * Called if we receive a packet that isn't allowed by the protocol.
+ * This only applies to packets whose meaning PuTTY understands.
+ * Entirely unknown packets are handled below.
+ */
+static void ssh2_msg_unexpected(Ssh ssh, struct Packet *pktin)
+{
+ char *buf = dupprintf("Server protocol violation: unexpected %s packet",
+ ssh2_pkt_type(ssh->pkt_kctx, ssh->pkt_actx,
+ pktin->type));
+ ssh_disconnect(ssh, NULL, buf, SSH2_DISCONNECT_PROTOCOL_ERROR, FALSE);
+ sfree(buf);
+}
+
static void ssh2_msg_something_unimplemented(Ssh ssh, struct Packet *pktin)
{
struct Packet *pktout;
ssh->packet_dispatch[i] = ssh2_msg_something_unimplemented;
/*
- * Any message we actually understand, we set to NULL so that
- * the coroutines will get it.
+ * Initially, we only accept transport messages (and a few generic
+ * ones). do_ssh2_authconn will add more when it starts.
+ * Messages that are understood but not currently acceptable go to
+ * ssh2_msg_unexpected.
*/
- ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = NULL;
- ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXINIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = NULL;
- /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = NULL; duplicate case value */
- /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = NULL; duplicate case value */
- ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = NULL;
- ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = NULL;
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = NULL;
- /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = NULL; duplicate case value */
- /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = NULL; duplicate case value */
- ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = NULL;
- ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = NULL;
+ ssh->packet_dispatch[SSH2_MSG_UNIMPLEMENTED] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_SERVICE_ACCEPT] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_KEXINIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_NEWKEYS] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEXDH_INIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEXDH_REPLY] = ssh2_msg_transport;
+ /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REQUEST] = ssh2_msg_transport; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_GROUP] = ssh2_msg_transport; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_INIT] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_KEX_DH_GEX_REPLY] = ssh2_msg_transport;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_BANNER] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_PK_OK] = ssh2_msg_unexpected;
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ] = ssh2_msg_unexpected; duplicate case value */
+ /* ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_REQUEST] = ssh2_msg_unexpected; duplicate case value */
+ ssh->packet_dispatch[SSH2_MSG_USERAUTH_INFO_RESPONSE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_GLOBAL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_REQUEST_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_CONFIRMATION] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_OPEN_FAILURE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_WINDOW_ADJUST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EXTENDED_DATA] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_EOF] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_CLOSE] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_REQUEST] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_SUCCESS] = ssh2_msg_unexpected;
+ ssh->packet_dispatch[SSH2_MSG_CHANNEL_FAILURE] = ssh2_msg_unexpected;
/*
- * These special message types we install handlers for.
+ * These messages have a special handler from the start.
*/
ssh->packet_dispatch[SSH2_MSG_DISCONNECT] = ssh2_msg_disconnect;
ssh->packet_dispatch[SSH2_MSG_IGNORE] = ssh_msg_ignore; /* shared with SSH-1 */
do_ssh2_transport(ssh, "too much data received", -1, NULL);
}
- if (pktin && ssh->packet_dispatch[pktin->type]) {
+ if (pktin)
ssh->packet_dispatch[pktin->type](ssh, pktin);
- return;
- }
-
- if (!ssh->protocol_initial_phase_done ||
- (pktin && pktin->type >= 20 && pktin->type < 50)) {
- if (do_ssh2_transport(ssh, in, inlen, pktin) &&
- !ssh->protocol_initial_phase_done) {
- ssh->protocol_initial_phase_done = TRUE;
- /*
- * Allow authconn to initialise itself.
- */
- do_ssh2_authconn(ssh, NULL, 0, NULL);
- }
- } else {
+ else if (!ssh->protocol_initial_phase_done)
+ do_ssh2_transport(ssh, in, inlen, pktin);
+ else
do_ssh2_authconn(ssh, in, inlen, pktin);
- }
}
static void ssh_cache_conf_values(Ssh ssh)
ssh->v2_outgoing_sequence = 0;
ssh->ssh1_rdpkt_crstate = 0;
ssh->ssh2_rdpkt_crstate = 0;
- ssh->do_ssh_init_crstate = 0;
ssh->ssh_gotdata_crstate = 0;
ssh->do_ssh1_connection_crstate = 0;
- ssh->do_ssh1_login_crstate = 0;
- ssh->do_ssh2_transport_crstate = 0;
- ssh->do_ssh2_authconn_crstate = 0;
ssh->do_ssh_init_state = NULL;
ssh->do_ssh1_login_state = NULL;
ssh->do_ssh2_transport_state = NULL;
bufchain_init(&ssh->queued_incoming_data);
ssh->frozen = FALSE;
ssh->username = NULL;
+ ssh->sent_console_eof = FALSE;
+ ssh->got_pty = FALSE;
*backend_handle = ssh;
pfd_close(c->u.pfd.s);
break;
}
+ if (ssh->version == 2) {
+ struct outstanding_channel_request *ocr, *nocr;
+ ocr = c->v.v2.chanreq_head;
+ while (ocr) {
+ ocr->handler(c, NULL, ocr->ctx);
+ nocr = ocr->next;
+ sfree(ocr);
+ ocr = nocr;
+ }
+ bufchain_clear(&c->v.v2.outbuffer);
+ }
sfree(c);
}
freetree234(ssh->channels);
if (ssh->version == 1) {
return override_value;
} else if (ssh->version == 2) {
- if (!ssh->mainchan || ssh->mainchan->closes > 0)
+ if (!ssh->mainchan)
return override_value;
else
return (override_value +
PKT_INT, ssh->term_width,
PKT_INT, 0, PKT_INT, 0, PKT_END);
} else if (ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "window-change");
- ssh2_pkt_addbool(pktout, 0);
+ pktout = ssh2_chanreq_init(ssh->mainchan, "window-change",
+ NULL, NULL);
ssh2_pkt_adduint32(pktout, ssh->term_width);
ssh2_pkt_adduint32(pktout, ssh->term_height);
ssh2_pkt_adduint32(pktout, 0);
if (ssh->version == 1) {
send_packet(ssh, SSH1_CMSG_EOF, PKT_END);
} else if (ssh->mainchan) {
- struct Packet *pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_EOF);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_send(ssh, pktout);
+ sshfwd_write_eof(ssh->mainchan);
ssh->send_ok = 0; /* now stop trying to read from stdin */
}
logevent("Sent EOF message");
if (ssh->version == 1) {
logevent("Unable to send BREAK signal in SSH-1");
} else if (ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "break");
- ssh2_pkt_addbool(pktout, 0);
+ pktout = ssh2_chanreq_init(ssh->mainchan, "break", NULL, NULL);
ssh2_pkt_adduint32(pktout, 0); /* default break length */
ssh2_pkt_send(ssh, pktout);
}
if (signame) {
/* It's a signal. */
if (ssh->version == 2 && ssh->mainchan) {
- pktout = ssh2_pkt_init(SSH2_MSG_CHANNEL_REQUEST);
- ssh2_pkt_adduint32(pktout, ssh->mainchan->remoteid);
- ssh2_pkt_addstring(pktout, "signal");
- ssh2_pkt_addbool(pktout, 0);
+ pktout = ssh2_chanreq_init(ssh->mainchan, "signal", NULL, NULL);
ssh2_pkt_addstring(pktout, signame);
ssh2_pkt_send(ssh, pktout);
logeventf(ssh, "Sent signal SIG%s", signame);
}
}
}
+
+ /*
+ * Now process any SSH connection data that was stashed in our
+ * queue while we were frozen.
+ */
+ ssh_process_queued_incoming_data(ssh);
}
void ssh_send_port_open(void *channel, char *hostname, int port, char *org)