#define BUG_NEEDS_SSH1_PLAIN_PASSWORD 4
#define BUG_CHOKES_ON_RSA 8
#define BUG_SSH2_RSA_PADDING 16
+#define BUG_SSH2_DERIVEKEY 32
+#define BUG_SSH2_DH_GEX 64
static int ssh_pkt_ctx = 0;
struct ssh_channel {
unsigned remoteid, localid;
int type;
+ /*
+ * In SSH1, this value contains four bits:
+ *
+ * 1 We have sent SSH1_MSG_CHANNEL_CLOSE.
+ * 2 We have sent SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+ * 4 We have received SSH1_MSG_CHANNEL_CLOSE.
+ * 8 We have received SSH1_MSG_CHANNEL_CLOSE_CONFIRMATION.
+ *
+ * A channel is completely finished with when all four bits are set.
+ */
int closes;
union {
struct ssh1_data_channel {
if (pktin.type == SSH1_MSG_DEBUG) {
/* log debug message */
- char buf[80];
+ char buf[512];
int stringlen = GET_32BIT(pktin.body);
- strcpy(buf, "Remote: ");
- if (stringlen > 70)
- stringlen = 70;
+ strcpy(buf, "Remote debug message: ");
+ if (stringlen > 480)
+ stringlen = 480;
memcpy(buf + 8, pktin.body + 4, stringlen);
buf[8 + stringlen] = '\0';
logevent(buf);
st->pad = pktin.data[4];
/*
+ * _Completely_ silly lengths should be stomped on before they
+ * do us any more damage.
+ */
+ if (st->len < 0 || st->pad < 0 || st->len + st->pad < 0) {
+ bombout(("Incoming packet was garbled on decryption"));
+ crReturn(0);
+ }
+
+ /*
* This enables us to deduce the payload length.
*/
st->payload = st->len - st->pad - 1;
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, "Cisco-1.25")) {
+ if (cfg.sshbug_ignore1 == BUG_ON ||
+ (cfg.sshbug_ignore1 == BUG_AUTO &&
+ (!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, "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")) {
+ if (cfg.sshbug_plainpw1 == BUG_ON ||
+ (cfg.sshbug_plainpw1 == BUG_AUTO &&
+ (!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
logevent("We believe remote version needs a plain SSH1 password");
}
- if (!strcmp(imp, "Cisco-1.25")) {
+ if (cfg.sshbug_rsa1 == BUG_ON ||
+ (cfg.sshbug_rsa1 == BUG_AUTO &&
+ (!strcmp(imp, "Cisco-1.25")))) {
/*
* These versions apparently have no clue whatever about
* RSA authentication and will panic and die if they see
logevent("We believe remote version can't handle RSA authentication");
}
- 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)) {
+ if (cfg.sshbug_hmac2 == BUG_ON ||
+ (cfg.sshbug_hmac2 == BUG_AUTO &&
+ (!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)))) {
/*
* These versions have the HMAC bug.
*/
logevent("We believe remote version has SSH2 HMAC bug");
}
- if ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') ||
- (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')) {
+ if (cfg.sshbug_derivekey2 == BUG_ON ||
+ (cfg.sshbug_derivekey2 == BUG_AUTO &&
+ (!strncmp(imp, "2.0.", 4)))) {
+ /*
+ * These versions have the key-derivation bug (failing to
+ * include the literal shared secret in the hashes that
+ * generate the keys).
+ */
+ ssh_remote_bugs |= BUG_SSH2_DERIVEKEY;
+ logevent("We believe remote version has SSH2 key-derivation bug");
+ }
+
+ if (cfg.sshbug_rsapad2 == BUG_ON ||
+ (cfg.sshbug_rsapad2 == BUG_AUTO &&
+ ((!strncmp(imp, "OpenSSH_2.", 10) && imp[10]>='5' && imp[10]<='9') ||
+ (!strncmp(imp, "OpenSSH_3.", 10) && imp[10]>='0' && imp[10]<='2')))){
/*
* These versions have the SSH2 RSA padding bug.
*/
ssh_remote_bugs |= BUG_SSH2_RSA_PADDING;
logevent("We believe remote version has SSH2 RSA padding bug");
}
+
+ if (cfg.sshbug_dhgex2 == BUG_ON) {
+ /*
+ * These versions have the SSH2 DH GEX bug.
+ */
+ ssh_remote_bugs |= BUG_SSH2_DH_GEX;
+ logevent("We believe remote version has SSH2 DH group exchange bug");
+ }
}
static int do_ssh_init(unsigned char c)
static int vstrsize;
static char *vlog;
static int i;
+ static int proto1, proto2;
crBegin;
sfree(vlog);
/*
- * Server version "1.99" means we can choose whether we use v1
- * or v2 protocol. Choice is based on cfg.sshprot.
+ * Decide which SSH protocol version to support.
*/
- if (ssh_versioncmp(version, cfg.sshprot == 1 ? "2.0" : "1.99") >= 0) {
+
+ /* Anything strictly below "2.0" means protocol 1 is supported. */
+ proto1 = ssh_versioncmp(version, "2.0") < 0;
+ /* Anything greater or equal to "1.99" means protocol 2 is supported. */
+ proto2 = ssh_versioncmp(version, "1.99") >= 0;
+
+ if (cfg.sshprot == 0 && !proto1) {
+ bombout(("SSH protocol version 1 required by user but not provided by server"));
+ crReturn(0);
+ }
+ if (cfg.sshprot == 3 && !proto2) {
+ bombout(("SSH protocol version 2 required by user but not provided by server"));
+ crReturn(0);
+ }
+
+ if (proto2 && (cfg.sshprot >= 2 || !proto1)) {
/*
- * This is a v2 server. Begin v2 protocol.
+ * Use v2 protocol.
*/
char verstring[80], vlog[100];
sprintf(verstring, "SSH-2.0-%s", sshver);
s_rdpkt = ssh2_rdpkt;
} else {
/*
- * This is a v1 server. Begin v1 protocol.
+ * Use v1 protocol.
*/
char verstring[80], vlog[100];
sprintf(verstring, "SSH-%s-%s",
sprintf(vlog, "We claim version: %s", verstring);
logevent(vlog);
strcat(verstring, "\n");
-
- if (cfg.sshprot == 3) {
- bombout(("SSH protocol version 2 required by user but not provided by server"));
- crReturn(0);
- }
logevent("Using SSH protocol version 1");
sk_write(s, verstring, strlen(verstring));
}
/*
+ * Username and password input, abstracted off into reusable
+ * routines (hopefully even reusable between SSH1 and SSH2!).
+ */
+static char *ssh_userpass_input_buffer;
+static int ssh_userpass_input_buflen;
+static int ssh_userpass_input_bufpos;
+static int ssh_userpass_input_echo;
+
+/* Set up a username or password input loop on a given buffer. */
+void setup_userpass_input(char *buffer, int buflen, int echo)
+{
+ ssh_userpass_input_buffer = buffer;
+ ssh_userpass_input_buflen = buflen;
+ ssh_userpass_input_bufpos = 0;
+ ssh_userpass_input_echo = echo;
+}
+
+/*
+ * Process some terminal data in the course of username/password
+ * input. Returns >0 for success (line of input returned in
+ * buffer), <0 for failure (user hit ^C/^D, bomb out and exit), 0
+ * for inconclusive (keep waiting for more input please).
+ */
+int process_userpass_input(unsigned char *in, int inlen)
+{
+ char c;
+
+ while (inlen--) {
+ switch (c = *in++) {
+ case 10:
+ case 13:
+ ssh_userpass_input_buffer[ssh_userpass_input_bufpos] = 0;
+ ssh_userpass_input_buffer[ssh_userpass_input_buflen-1] = 0;
+ return +1;
+ break;
+ case 8:
+ case 127:
+ if (ssh_userpass_input_bufpos > 0) {
+ if (ssh_userpass_input_echo)
+ c_write_str("\b \b");
+ ssh_userpass_input_bufpos--;
+ }
+ break;
+ case 21:
+ case 27:
+ while (ssh_userpass_input_bufpos > 0) {
+ if (ssh_userpass_input_echo)
+ c_write_str("\b \b");
+ ssh_userpass_input_bufpos--;
+ }
+ break;
+ case 3:
+ case 4:
+ return -1;
+ break;
+ default:
+ if (((c >= ' ' && c <= '~') ||
+ ((unsigned char) c >= 160))
+ && ssh_userpass_input_bufpos < ssh_userpass_input_buflen-1) {
+ ssh_userpass_input_buffer[ssh_userpass_input_bufpos++] = c;
+ if (ssh_userpass_input_echo)
+ c_write(&c, 1);
+ }
+ break;
+ }
+ }
+ return 0;
+}
+
+/*
* Handle the key exchange and user authentication phases.
*/
static int do_ssh1_login(unsigned char *in, int inlen, int ispkt)
fflush(stdout);
{
- static int pos = 0;
- static char c;
if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
if (ssh_get_line && !ssh_getline_pw_only) {
if (!ssh_get_line("login as: ",
crReturn(1);
}
} else {
+ static int ret;
c_write_str("login as: ");
ssh_send_ok = 1;
- while (pos >= 0) {
+
+ setup_userpass_input(username, sizeof(username), 1);
+ do {
crWaitUntil(!ispkt);
- while (inlen--)
- switch (c = *in++) {
- case 10:
- case 13:
- username[pos] = 0;
- pos = -1;
- break;
- case 8:
- case 127:
- if (pos > 0) {
- c_write_str("\b \b");
- pos--;
- }
- break;
- case 21:
- case 27:
- while (pos > 0) {
- c_write_str("\b \b");
- pos--;
- }
- break;
- case 3:
- case 4:
- cleanup_exit(0);
- break;
- default:
- if (((c >= ' ' && c <= '~') ||
- ((unsigned char) c >= 160))
- && pos < sizeof(username)-1) {
- username[pos++] = c;
- c_write(&c, 1);
- }
- break;
- }
- }
+ ret = process_userpass_input(in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
c_write_str("\r\n");
- username[strcspn(username, "\n\r")] = '\0';
}
} else {
- strncpy(username, cfg.username, 99);
- username[99] = '\0';
+ strncpy(username, cfg.username, sizeof(username));
+ username[sizeof(username)-1] = '\0';
}
send_packet(SSH1_CMSG_USER, PKT_STR, username, PKT_END);
} else {
/* Prompt may have come from server. We've munged it a bit, so
* we know it to be zero-terminated at least once. */
+ static int ret;
c_write_untrusted(prompt, strlen(prompt));
pos = 0;
- ssh_send_ok = 1;
- while (pos >= 0) {
+
+ setup_userpass_input(password, sizeof(password), 0);
+ do {
crWaitUntil(!ispkt);
- while (inlen--)
- switch (c = *in++) {
- case 10:
- case 13:
- password[pos] = 0;
- pos = -1;
- break;
- case 8:
- case 127:
- if (pos > 0)
- pos--;
- break;
- case 21:
- case 27:
- pos = 0;
- break;
- case 3:
- case 4:
- cleanup_exit(0);
- break;
- default:
- if (pos < sizeof(password)-1)
- password[pos++] = c;
- break;
- }
- }
+ ret = process_userpass_input(in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
c_write_str("\r\n");
}
ssh2_pkt_send();
}
}
- c->closes = 1;
+ c->closes = 1; /* sent MSG_CLOSE */
if (c->type == CHAN_X11) {
c->u.x11.s = NULL;
logevent("Forwarded X11 connection terminated");
unsigned i = GET_32BIT(pktin.body);
struct ssh_channel *c;
c = find234(ssh_channels, &i, ssh_channelfind);
- if (c) {
+ if (c && ((int)c->remoteid) != -1) {
int closetype;
closetype =
(pktin.type == SSH1_MSG_CHANNEL_CLOSE ? 1 : 2);
- if (!(c->closes & closetype))
- send_packet(pktin.type, PKT_INT, c->remoteid,
- PKT_END);
+
if ((c->closes == 0) && (c->type == CHAN_X11)) {
logevent("Forwarded X11 connection terminated");
assert(c->u.x11.s != NULL);
pfd_close(c->u.pfd.s);
c->u.pfd.s = NULL;
}
- c->closes |= closetype;
- if (c->closes == 3) {
+
+ c->closes |= (closetype << 2); /* seen this message */
+ if (!(c->closes & closetype)) {
+ send_packet(pktin.type, PKT_INT, c->remoteid,
+ PKT_END);
+ c->closes |= closetype; /* sent it too */
+ }
+
+ if (c->closes == 15) {
del234(ssh_channels, c);
sfree(c);
}
+ } else {
+ bombout(("Received CHANNEL_CLOSE%s for %s channel %d\n",
+ pktin.type == SSH1_MSG_CHANNEL_CLOSE ? "" :
+ "_CONFIRMATION", c ? "half-open" : "nonexistent",
+ i));
}
} else if (pktin.type == SSH1_MSG_CHANNEL_DATA) {
/* Data sent down one of our channels. */
SHA_State s;
/* First 20 bytes. */
SHA_Init(&s);
- sha_mpint(&s, K);
+ if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+ sha_mpint(&s, K);
SHA_Bytes(&s, H, 20);
SHA_Bytes(&s, &chr, 1);
SHA_Bytes(&s, sessid, 20);
SHA_Final(&s, keyspace);
/* Next 20 bytes. */
SHA_Init(&s);
- sha_mpint(&s, K);
+ if (!(ssh_remote_bugs & BUG_SSH2_DERIVEKEY))
+ sha_mpint(&s, K);
SHA_Bytes(&s, H, 20);
SHA_Bytes(&s, keyspace, 20);
SHA_Final(&s, keyspace + 20);
/*
* Be prepared to work around the buggy MAC problem.
*/
- if (cfg.buggymac || (ssh_remote_bugs & BUG_SSH2_HMAC))
+ if (ssh_remote_bugs & BUG_SSH2_HMAC)
maclist = buggymacs, nmacs = lenof(buggymacs);
else
maclist = macs, nmacs = lenof(macs);
/* List key exchange algorithms. */
ssh2_pkt_addstring_start();
for (i = 0; i < lenof(kex_algs); i++) {
+ if (kex_algs[i] == &ssh_diffiehellman_gex &&
+ (ssh_remote_bugs & BUG_SSH2_DH_GEX))
+ continue;
ssh2_pkt_addstring_str(kex_algs[i]->name);
if (i < lenof(kex_algs) - 1)
ssh2_pkt_addstring_str(",");
pktin.savedpos += 16; /* skip garbage cookie */
ssh2_pkt_getstring(&str, &len); /* key exchange algorithms */
for (i = 0; i < lenof(kex_algs); i++) {
+ if (kex_algs[i] == &ssh_diffiehellman_gex &&
+ (ssh_remote_bugs & BUG_SSH2_DH_GEX))
+ continue;
if (in_commasep_string(kex_algs[i]->name, str, len)) {
kex = kex_algs[i];
break;
username[0] = '\0';
got_username = FALSE;
do {
- static int pos;
- static char c;
-
/*
* Get a username.
*/
- pos = 0;
if (got_username && !cfg.change_username) {
/*
* We got a username last time round this loop, and
crReturnV;
}
} else {
+ static int ret;
c_write_str("login as: ");
ssh_send_ok = 1;
- while (pos >= 0) {
+ setup_userpass_input(username, sizeof(username), 1);
+ do {
crWaitUntilV(!ispkt);
- while (inlen--)
- switch (c = *in++) {
- case 10:
- case 13:
- username[pos] = 0;
- pos = -1;
- break;
- case 8:
- case 127:
- if (pos > 0) {
- c_write_str("\b \b");
- pos--;
- }
- break;
- case 21:
- case 27:
- while (pos > 0) {
- c_write_str("\b \b");
- pos--;
- }
- break;
- case 3:
- case 4:
- cleanup_exit(0);
- break;
- default:
- if (((c >= ' ' && c <= '~') ||
- ((unsigned char) c >= 160))
- && pos < sizeof(username)-1) {
- username[pos++] = c;
- c_write(&c, 1);
- }
- break;
- }
- }
+ ret = process_userpass_input(in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
}
c_write_str("\r\n");
username[strcspn(username, "\n\r")] = '\0';
} else {
char stuff[200];
- strncpy(username, cfg.username, 99);
- username[99] = '\0';
+ strncpy(username, cfg.username, sizeof(username));
+ username[sizeof(username)-1] = '\0';
if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
sprintf(stuff, "Using username \"%s\".\r\n", username);
c_write_str(stuff);
method = 0;
ssh_pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ /*
+ * Most password/passphrase prompts will be
+ * non-echoing, so we set this to 0 by default.
+ * Exception is that some keyboard-interactive prompts
+ * can be echoing, in which case we'll set this to 1.
+ */
+ echo = 0;
+
if (!method && can_pubkey && agent_exists() && !tried_agent) {
/*
* Attempt public-key authentication using Pageant.
ssh2_pkt_getstring(&lang, &lang_len);
if (name_len > 0) {
c_write_untrusted(name, name_len);
- c_write_str("\n");
+ c_write_str("\r\n");
}
if (inst_len > 0) {
c_write_untrusted(inst, inst_len);
- c_write_str("\n");
+ c_write_str("\r\n");
}
num_prompts = ssh2_pkt_getuint32();
}
crReturnV;
}
} else {
- static int pos = 0;
- static char c;
-
+ static int ret;
c_write_untrusted(pwprompt, strlen(pwprompt));
ssh_send_ok = 1;
- pos = 0;
- while (pos >= 0) {
+ setup_userpass_input(password, sizeof(password), echo);
+ do {
crWaitUntilV(!ispkt);
- while (inlen--)
- switch (c = *in++) {
- case 10:
- case 13:
- password[pos] = 0;
- pos = -1;
- break;
- case 8:
- case 127:
- if (pos > 0)
- pos--;
- break;
- case 21:
- case 27:
- pos = 0;
- break;
- case 3:
- case 4:
- cleanup_exit(0);
- break;
- default:
- if (pos < sizeof(password)-1)
- password[pos++] = c;
- break;
- }
- }
+ ret = process_userpass_input(in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
c_write_str("\r\n");
}
}
struct ssh_channel *c;
c = find234(ssh_channels, &i, ssh_channelfind);
- if (!c)
- continue; /* nonexistent channel */
+ if (!c || ((int)c->remoteid) == -1) {
+ bombout(("Received CHANNEL_CLOSE for %s channel %d\n",
+ c ? "half-open" : "nonexistent", i));
+ }
/* Do pre-close processing on the channel. */
switch (c->type) {
case CHAN_MAINSESSION:
if (ssh_state == SSH_STATE_CLOSED
|| ssh_state == SSH_STATE_PREPACKET) return;
if (ssh_version == 1) {
- send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
+ if (!(ssh_remote_bugs & BUG_CHOKES_ON_SSH1_IGNORE))
+ send_packet(SSH1_MSG_IGNORE, PKT_STR, "", PKT_END);
} else {
ssh2_pkt_init(SSH2_MSG_IGNORE);
ssh2_pkt_addstring_start();