*/
#define BUG_CHOKES_ON_SSH1_IGNORE 1
#define BUG_SSH2_HMAC 2
+#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
+
#define GET_32BIT(cp) \
(((unsigned long)(unsigned char)(cp)[0] << 24) | \
#define SSH_MAX_BACKLOG 32768
#define OUR_V2_WINSIZE 16384
-/*
- * Ciphers for SSH2. We miss out single-DES because it isn't
- * supported; also 3DES and Blowfish are both done differently from
- * SSH1. (3DES uses outer chaining; Blowfish has the opposite
- * endianness and different-sized keys.)
- */
-const static struct ssh2_ciphers *ciphers[] = {
- &ssh2_aes,
- &ssh2_blowfish,
- &ssh2_3des,
-};
-
const static struct ssh_kex *kex_algs[] = {
&ssh_diffiehellman_gex,
&ssh_diffiehellman
(*data)++, (*datalen)--;
}
-#ifdef FWHACK
- if (st->len == 0x52656d6f) { /* "Remo"te server has closed ... */
- st->len = 0x300; /* big enough to carry to end */
- }
-#endif
-
st->pad = 8 - (st->len % 8);
st->biglen = st->len + st->pad;
pktin.length = st->len - 5;
pktin.data[st->i] = *(*data)++;
(*datalen)--;
}
-#ifdef FWHACK
- if (!memcmp(pktin.data, "Remo", 4)) { /* "Remo"te server has closed ... */
- /* FIXME */
- }
-#endif
+
if (sccipher)
sccipher->decrypt(pktin.data, st->cipherblk);
pktout.body[-1] = pktout.type;
-#ifdef DUMP_PACKETS
- debug(("Packet payload pre-compression:\n"));
- dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
-
if (ssh1_compressing) {
unsigned char *compblk;
int complen;
+#ifdef DUMP_PACKETS
+ debug(("Packet payload pre-compression:\n"));
+ dmemdump(pktout.body - 1, pktout.length + 1);
+#endif
zlib_compress_block(pktout.body - 1, pktout.length + 1,
&compblk, &complen);
ssh1_pktout_size(complen - 1);
memcpy(pktout.body - 1, compblk, complen);
sfree(compblk);
-#ifdef DUMP_PACKETS
- debug(("Packet payload post-compression:\n"));
- dmemdump(pktout.body - 1, pktout.length + 1);
-#endif
}
len = pktout.length + 5; /* type and CRC */
/*
* Compress packet payload.
*/
-#ifdef DUMP_PACKETS
- debug(("Pre-compression payload:\n"));
- dmemdump(pktout.data + 5, pktout.length - 5);
-#endif
{
unsigned char *newpayload;
int newlen;
+#ifdef DUMP_PACKETS
+ if (cscomp && cscomp != &ssh_comp_none) {
+ debug(("Pre-compression payload:\n"));
+ dmemdump(pktout.data + 5, pktout.length - 5);
+ }
+#endif
if (cscomp && cscomp->compress(pktout.data + 5, pktout.length - 5,
&newpayload, &newlen)) {
pktout.length = 5;
char *imp; /* pointer to implementation part */
imp = vstring;
imp += strcspn(imp, "-");
- if (*imp)
- imp++;
+ if (*imp) imp++;
imp += strcspn(imp, "-");
- if (*imp)
- imp++;
+ if (*imp) imp++;
ssh_remote_bugs = 0;
if (!strcmp(imp, "1.2.18") || !strcmp(imp, "1.2.19") ||
!strcmp(imp, "1.2.20") || !strcmp(imp, "1.2.21") ||
- !strcmp(imp, "1.2.22")) {
+ !strcmp(imp, "1.2.22") || !strcmp(imp, "Cisco-1.25")) {
/*
* These versions don't support SSH1_MSG_IGNORE, so we have
* to use a different defence against password length
logevent("We believe remote version has SSH1 ignore bug");
}
+ if (!strcmp(imp, "Cisco-1.25")) {
+ /*
+ * These versions need a plain password sent; they can't
+ * handle having a null and a random length of data after
+ * the password.
+ */
+ ssh_remote_bugs |= BUG_NEEDS_SSH1_PLAIN_PASSWORD;
+ logevent("We believe remote version needs a plain SSH1 password");
+ }
+
if (!strncmp(imp, "2.1.0", 5) || !strncmp(imp, "2.0.", 4) ||
!strncmp(imp, "2.2.0", 5) || !strncmp(imp, "2.3.0", 5) ||
!strncmp(imp, "2.1 ", 4)) {
crReturn(1); /* get another character */
}
- vstring = smalloc(16);
vstrsize = 16;
+ vstring = smalloc(vstrsize);
strcpy(vstring, "SSH-");
vslen = 4;
i = 0;
vstring[vslen] = 0;
vlog = smalloc(20 + vslen);
+ vstring[strcspn (vstring, "\r\n")] = '\0'; /* remove end-of-line chars */
sprintf(vlog, "Server version: %s", vstring);
- ssh_detect_bugs(vstring);
- vlog[strcspn(vlog, "\r\n")] = '\0';
logevent(vlog);
+ ssh_detect_bugs(vstring);
sfree(vlog);
/*
* Also places the canonical host name into `realhost'. It must be
* freed by the caller.
*/
-static char *connect_to_host(char *host, int port, char **realhost)
+static char *connect_to_host(char *host, int port, char **realhost, int nodelay)
{
static struct plug_function_table fn_table = {
ssh_closing,
sprintf(buf, "Connecting to %.100s port %d", addrbuf, port);
logevent(buf);
}
- s = sk_new(addr, port, 0, 1, &fn_table_ptr);
+ s = sk_new(addr, port, 0, 1, nodelay, &fn_table_ptr);
if ((err = sk_socket_error(s)))
return err;
* use of the fact that the password is interpreted
* as a C string: so we can append a NUL, then some
* random data.
+ *
+ * One server (a Cisco one) can deal with neither
+ * SSH1_MSG_IGNORE _nor_ a padded password string.
+ * For this server we are left with no defences
+ * against password length sniffing.
*/
- if (ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE) {
- char string[64];
- char *s;
- int len;
-
- len = strlen(password);
- if (len < sizeof(string)) {
- s = string;
- strcpy(string, password);
- len++; /* cover the zero byte */
- while (len < sizeof(string)) {
- string[len++] = (char) random_byte();
- }
- } else {
- s = password;
- }
- send_packet(pwpkt_type, PKT_INT, len,
- PKT_DATA, s, len, PKT_END);
- } else {
+ if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE)) {
+ /*
+ * The server can deal with SSH1_MSG_IGNORE, so
+ * we can use the primary defence.
+ */
int bottom, top, pwlen, i;
char *randomstr;
PKT_STR, randomstr, PKT_END);
}
}
+ logevent("Sending password with camouflage packets");
ssh_pkt_defersend();
+ }
+ else if (!(ssh_remote_bugs & BUG_NEEDS_SSH1_PLAIN_PASSWORD)) {
+ /*
+ * The server can't deal with SSH1_MSG_IGNORE
+ * but can deal with padded passwords, so we
+ * can use the secondary defence.
+ */
+ char string[64];
+ char *s;
+ int len;
+
+ len = strlen(password);
+ if (len < sizeof(string)) {
+ s = string;
+ strcpy(string, password);
+ len++; /* cover the zero byte */
+ while (len < sizeof(string)) {
+ string[len++] = (char) random_byte();
+ }
+ } else {
+ s = password;
+ }
+ logevent("Sending length-padded password");
+ send_packet(pwpkt_type, PKT_INT, len,
+ PKT_DATA, s, len, PKT_END);
+ } else {
+ /*
+ * The server has _both_
+ * BUG_CHOKES_ON_SSH1_IGNORE and
+ * BUG_NEEDS_SSH1_PLAIN_PASSWORD. There is
+ * therefore nothing we can do.
+ */
+ int len;
+ len = strlen(password);
+ logevent("Sending unpadded password");
+ send_packet(pwpkt_type, PKT_INT, len,
+ PKT_DATA, password, len, PKT_END);
}
} else {
send_packet(pwpkt_type, PKT_STR, password, PKT_END);
void sshfwd_close(struct ssh_channel *c)
{
if (c && !c->closes) {
- if (ssh_version == 1) {
- send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
- PKT_END);
- } else {
- ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
- ssh2_pkt_adduint32(c->remoteid);
- ssh2_pkt_send();
+ /*
+ * If the channel's remoteid is -1, 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->remoteid != -1) {
+ if (ssh_version == 1) {
+ send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ } else {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
}
c->closes = 1;
if (c->type == CHAN_X11) {
c->u.x11.s = NULL;
logevent("Forwarded X11 connection terminated");
- } else if (c->type == CHAN_SOCKDATA) {
+ } else if (c->type == CHAN_SOCKDATA ||
+ c->type == CHAN_SOCKDATA_DORMANT) {
c->u.pfd.s = NULL;
logevent("Forwarded port closed");
}
if (eof_needed)
ssh_special(TS_EOF);
- ldisc_send(NULL, 0); /* cause ldisc to notice changes */
+ ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */
ssh_send_ok = 1;
ssh_channels = newtree234(ssh_channelcmp);
while (1) {
if (c && c->type == CHAN_SOCKDATA_DORMANT) {
c->remoteid = localid;
c->type = CHAN_SOCKDATA;
+ c->v.v1.throttling = 0;
pfd_confirm(c->u.pfd.s);
}
+ if (c && 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.
+ */
+ send_packet(SSH1_MSG_CHANNEL_CLOSE, PKT_INT, c->remoteid,
+ PKT_END);
+ }
+
} else if (pktin.type == SSH1_MSG_CHANNEL_OPEN_FAILURE) {
unsigned int remoteid = GET_32BIT(pktin.body);
unsigned int localid = GET_32BIT(pktin.body+4);
* if no pty is available or in other odd cases. Ignore */
} else if (pktin.type == SSH1_SMSG_EXIT_STATUS) {
send_packet(SSH1_CMSG_EXIT_CONFIRMATION, PKT_END);
+ /*
+ * In case `helpful' firewalls or proxies tack
+ * extra human-readable text on the end of the
+ * session which we might mistake for another
+ * encrypted packet, we close the session once
+ * we've sent EXIT_CONFIRMATION.
+ */
+ ssh_state = SSH_STATE_CLOSED;
+ crReturnV;
} else {
bombout(("Strange packet received: type %d", pktin.type));
crReturnV;
static int n_preferred_ciphers;
static const struct ssh2_ciphers *preferred_ciphers[CIPHER_MAX];
static const struct ssh_compress *preferred_comp;
+ static int cipherstr_started;
static int first_kex;
crBegin;
n_preferred_ciphers++;
break;
case CIPHER_DES:
- /* Not supported in SSH2; silently drop */
+ if (cfg.ssh2_des_cbc) {
+ preferred_ciphers[n_preferred_ciphers] = &ssh2_des;
+ n_preferred_ciphers++;
+ }
break;
case CIPHER_3DES:
preferred_ciphers[n_preferred_ciphers] = &ssh2_3des;
}
/* List client->server encryption algorithms. */
ssh2_pkt_addstring_start();
+ cipherstr_started = 0;
for (i = 0; i < n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = preferred_ciphers[i];
if (!c) continue; /* warning flag */
for (j = 0; j < c->nciphers; j++) {
- ssh2_pkt_addstring_str(c->list[j]->name);
- if (i < n_preferred_ciphers || j < c->nciphers - 1)
+ if (cipherstr_started)
ssh2_pkt_addstring_str(",");
+ ssh2_pkt_addstring_str(c->list[j]->name);
+ cipherstr_started = 1;
}
}
/* List server->client encryption algorithms. */
ssh2_pkt_addstring_start();
+ cipherstr_started = 0;
for (i = 0; i < n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = preferred_ciphers[i];
if (!c) continue; /* warning flag */
for (j = 0; j < c->nciphers; j++) {
- ssh2_pkt_addstring_str(c->list[j]->name);
- if (i < n_preferred_ciphers || j < c->nciphers - 1)
+ if (cipherstr_started)
ssh2_pkt_addstring_str(",");
+ ssh2_pkt_addstring_str(c->list[j]->name);
+ cipherstr_started = 1;
}
}
/* List client->server MAC algorithms. */
break;
}
}
+ if (!cscipher_tobe) {
+ bombout(("Couldn't agree a client-to-server cipher (available: %s)", str));
+ crReturn(0);
+ }
+
ssh2_pkt_getstring(&str, &len); /* server->client cipher */
warn = 0;
for (i = 0; i < n_preferred_ciphers; i++) {
break;
}
}
+ if (!sccipher_tobe) {
+ bombout(("Couldn't agree a server-to-client cipher (available: %s)", str));
+ crReturn(0);
+ }
+
ssh2_pkt_getstring(&str, &len); /* client->server mac */
for (i = 0; i < nmacs; i++) {
if (in_commasep_string(maclist[i]->name, str, len)) {
crReturn(0);
}
- /*
- * Expect SSH2_MSG_NEWKEYS from server.
- */
- crWaitUntil(ispkt);
- if (pktin.type != SSH2_MSG_NEWKEYS) {
- bombout(("expected new-keys packet from server"));
- crReturn(0);
- }
-
/*
* Authenticate remote host: verify host key. (We've already
* checked the signature of the exchange hash.)
ssh2_pkt_init(SSH2_MSG_NEWKEYS);
ssh2_pkt_send();
+ /*
+ * Expect SSH2_MSG_NEWKEYS from server.
+ */
+ crWaitUntil(ispkt);
+ if (pktin.type != SSH2_MSG_NEWKEYS) {
+ bombout(("expected new-keys packet from server"));
+ crReturn(0);
+ }
+
/*
* Create and initialise session keys.
*/
in_commasep_string("publickey", methods, methlen);
can_passwd =
in_commasep_string("password", methods, methlen);
- can_keyb_inter =
+ can_keyb_inter = cfg.try_ki_auth &&
in_commasep_string("keyboard-interactive", methods, methlen);
}
ssh2_pkt_getstring(&prompt, &prompt_len);
strncpy(pwprompt, prompt, sizeof(pwprompt));
+ pwprompt[prompt_len < sizeof(pwprompt) ?
+ prompt_len : sizeof(pwprompt)-1] = '\0';
need_pw = TRUE;
echo = ssh2_pkt_getbool();
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Server got confused by X11 forwarding request"));
+ bombout(("Unexpected response to X11 forwarding request:"
+ " packet type %d", pktin.type));
crReturnV;
}
logevent("X11 forwarding refused");
if (pktin.type != SSH2_MSG_REQUEST_SUCCESS) {
if (pktin.type != SSH2_MSG_REQUEST_FAILURE) {
- bombout(("Server got confused by port forwarding request"));
+ bombout(("Unexpected response to port "
+ "forwarding request: packet type %d",
+ pktin.type));
crReturnV;
}
logevent("Server refused this port forwarding");
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(
- ("Server got confused by agent forwarding request"));
+ bombout(("Unexpected response to agent forwarding request:"
+ " packet type %d", pktin.type));
crReturnV;
}
logevent("Agent forwarding refused");
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Server got confused by pty request"));
+ bombout(("Unexpected response to pty request:"
+ " packet type %d", pktin.type));
crReturnV;
}
c_write_str("Server refused to allocate pty\r\n");
} while (pktin.type == SSH2_MSG_CHANNEL_WINDOW_ADJUST);
if (pktin.type != SSH2_MSG_CHANNEL_SUCCESS) {
if (pktin.type != SSH2_MSG_CHANNEL_FAILURE) {
- bombout(("Server got confused by shell/command request"));
+ bombout(("Unexpected response to shell/command request:"
+ " packet type %d", pktin.type));
crReturnV;
}
/*
/*
* Transfer data!
*/
- ldisc_send(NULL, 0); /* cause ldisc to notice changes */
+ ldisc_send(NULL, 0, 0); /* cause ldisc to notice changes */
ssh_send_ok = 1;
while (1) {
static int try_send;
ssh_state = SSH_STATE_CLOSED;
logevent("Received disconnect message");
crReturnV;
- } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
- continue; /* exit status et al; ignore (FIXME?) */
} else if (pktin.type == SSH2_MSG_CHANNEL_EOF) {
unsigned i = ssh2_pkt_getuint32();
struct ssh_channel *c;
* See if that was the last channel left open.
*/
if (count234(ssh_channels) == 0) {
+#if 0
+ /*
+ * We used to send SSH_MSG_DISCONNECT here,
+ * because I'd believed that _every_ conforming
+ * SSH2 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.
+ */
logevent("All channels closed. Disconnecting");
ssh2_pkt_init(SSH2_MSG_DISCONNECT);
ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
ssh2_pkt_addstring("All open channels closed");
ssh2_pkt_addstring("en"); /* language tag */
ssh2_pkt_send();
+#endif
ssh_state = SSH_STATE_CLOSED;
crReturnV;
}
continue; /* dunno why they're confirming this */
c->remoteid = ssh2_pkt_getuint32();
c->type = CHAN_SOCKDATA;
- c->closes = 0;
c->v.v2.remwindow = ssh2_pkt_getuint32();
c->v.v2.remmaxpkt = ssh2_pkt_getuint32();
bufchain_init(&c->v.v2.outbuffer);
- pfd_confirm(c->u.pfd.s);
+ 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.
+ */
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_CLOSE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
} else if (pktin.type == SSH2_MSG_CHANNEL_OPEN_FAILURE) {
unsigned i = ssh2_pkt_getuint32();
struct ssh_channel *c;
del234(ssh_channels, c);
sfree(c);
+ } else if (pktin.type == SSH2_MSG_CHANNEL_REQUEST) {
+ unsigned localid;
+ char *type;
+ int typelen, want_reply;
+ struct ssh_channel *c;
+
+ localid = ssh2_pkt_getuint32();
+ ssh2_pkt_getstring(&type, &typelen);
+ want_reply = ssh2_pkt_getbool();
+
+ /*
+ * 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[80];
+ sprintf(buf, "Received channel request for nonexistent"
+ " channel %d", localid);
+ logevent(buf);
+ ssh2_pkt_init(SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring(buf);
+ ssh2_pkt_addstring("en"); /* language tag */
+ ssh2_pkt_send();
+ connection_fatal(buf);
+ ssh_state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+
+ /*
+ * We don't recognise any form of channel request,
+ * so we now either ignore the request or respond
+ * with CHANNEL_FAILURE, depending on want_reply.
+ */
+ if (want_reply) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_FAILURE);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_send();
+ }
} else if (pktin.type == SSH2_MSG_CHANNEL_OPEN) {
char *type;
int typelen;
*
* Returns an error message, or NULL on success.
*/
-static char *ssh_init(char *host, int port, char **realhost)
+static char *ssh_init(char *host, int port, char **realhost, int nodelay)
{
char *p;
ssh_overall_bufsize = 0;
ssh_fallback_cmd = 0;
- p = connect_to_host(host, port, realhost);
+ p = connect_to_host(host, port, realhost, nodelay);
if (p != NULL)
return p;