SSH2_PKTCTX_NOKEX,
SSH2_PKTCTX_DHGROUP,
SSH2_PKTCTX_DHGEX,
+ SSH2_PKTCTX_ECDHKEX,
SSH2_PKTCTX_RSAKEX
} Pkt_KCtx;
typedef enum {
#define BUG_SSH2_MAXPKT 256
#define BUG_CHOKES_ON_SSH2_IGNORE 512
#define BUG_CHOKES_ON_WINADJ 1024
+#define BUG_SENDS_LATE_REQUEST_REPLY 2048
/*
* Codes for terminal modes.
translatek(SSH2_MSG_KEXRSA_PUBKEY, SSH2_PKTCTX_RSAKEX);
translatek(SSH2_MSG_KEXRSA_SECRET, SSH2_PKTCTX_RSAKEX);
translatek(SSH2_MSG_KEXRSA_DONE, SSH2_PKTCTX_RSAKEX);
+ translatek(SSH2_MSG_KEX_ECDH_INIT, SSH2_PKTCTX_ECDHKEX);
+ translatek(SSH2_MSG_KEX_ECDH_REPLY, SSH2_PKTCTX_ECDHKEX);
translate(SSH2_MSG_USERAUTH_REQUEST);
translate(SSH2_MSG_USERAUTH_FAILURE);
translate(SSH2_MSG_USERAUTH_SUCCESS);
#define OUR_V2_MAXPKT 0x4000UL
#define OUR_V2_PACKETLIMIT 0x9000UL
-const static struct ssh_signkey *hostkey_algs[] = { &ssh_rsa, &ssh_dss };
+const static struct ssh_signkey *hostkey_algs[] = {
+ &ssh_ecdsa_nistp256, &ssh_ecdsa_nistp384, &ssh_ecdsa_nistp521,
+ &ssh_rsa, &ssh_dss
+};
const static struct ssh_mac *macs[] = {
&ssh_hmac_sha256, &ssh_hmac_sha1, &ssh_hmac_sha1_96, &ssh_hmac_md5
if (conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == FORCE_ON ||
(conf_get_int(ssh->conf, CONF_sshbug_rsapad2) == AUTO &&
(wc_match("OpenSSH_2.[5-9]*", imp) ||
- wc_match("OpenSSH_3.[0-2]*", imp)))) {
+ wc_match("OpenSSH_3.[0-2]*", imp) ||
+ wc_match("mod_sftp/0.[0-8]*", imp) ||
+ wc_match("mod_sftp/0.9.[0-8]", imp)))) {
/*
* These versions have the SSH-2 RSA padding bug.
*/
ssh->remote_bugs |= BUG_CHOKES_ON_WINADJ;
logevent("We believe remote version has winadj bug");
}
+
+ if (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == FORCE_ON ||
+ (conf_get_int(ssh->conf, CONF_sshbug_chanreq) == AUTO &&
+ (wc_match("OpenSSH_[2-5].*", imp) ||
+ wc_match("OpenSSH_6.[0-6]*", imp)))) {
+ /*
+ * These versions have the SSH-2 channel request bug. 6.7 and
+ * above do not:
+ * https://bugzilla.mindrot.org/show_bug.cgi?id=1818
+ */
+ ssh->remote_bugs |= BUG_SENDS_LATE_REQUEST_REPLY;
+ logevent("We believe remote version has SSH-2 channel request bug");
+ }
}
/*
s->proto2 = ssh_versioncmp(s->version, "1.99") >= 0;
if (conf_get_int(ssh->conf, CONF_sshprot) == 0 && !s->proto1) {
- bombout(("SSH protocol version 1 required by user but not provided by server"));
+ bombout(("SSH protocol version 1 required by configuration but "
+ "not provided by server"));
crStop(0);
}
if (conf_get_int(ssh->conf, CONF_sshprot) == 3 && !s->proto2) {
- bombout(("SSH protocol version 2 required by user but not provided by server"));
+ bombout(("SSH protocol version 2 required by configuration but "
+ "not provided by server"));
crStop(0);
}
ssh->portfwds = NULL;
}
+ /*
+ * Also stop attempting to connection-share.
+ */
+ if (ssh->connshare) {
+ sharestate_free(ssh->connshare);
+ ssh->connshare = NULL;
+ }
+
return ret;
}
loghost = conf_get_str(ssh->conf, CONF_loghost);
if (*loghost) {
- char *colon;
+ char *tmphost;
+ char *colon;
- ssh->savedhost = dupstr(loghost);
+ tmphost = dupstr(loghost);
ssh->savedport = 22; /* default ssh port */
/*
- * A colon suffix on savedhost also lets us affect
- * savedport.
- *
- * (FIXME: do something about IPv6 address literals here.)
+ * A colon suffix on the hostname string also lets us affect
+ * savedport. (Unless there are multiple colons, in which case
+ * we assume this is an unbracketed IPv6 literal.)
*/
- colon = strrchr(ssh->savedhost, ':');
- if (colon) {
+ colon = host_strrchr(tmphost, ':');
+ if (colon && colon == host_strchr(tmphost, ':')) {
*colon++ = '\0';
if (*colon)
ssh->savedport = atoi(colon);
}
+
+ ssh->savedhost = host_strduptrim(tmphost);
+ sfree(tmphost);
} else {
- ssh->savedhost = dupstr(host);
+ ssh->savedhost = host_strduptrim(host);
if (port < 0)
port = 22; /* default ssh port */
ssh->savedport = port;
sfree(error);
}
+int verify_ssh_manual_host_key(Ssh ssh, const char *fingerprint,
+ const struct ssh_signkey *ssh2keytype,
+ void *ssh2keydata)
+{
+ if (!conf_get_str_nthstrkey(ssh->conf, CONF_ssh_manual_hostkeys, 0)) {
+ return -1; /* no manual keys configured */
+ }
+
+ if (fingerprint) {
+ /*
+ * The fingerprint string we've been given will have things
+ * like 'ssh-rsa 2048' at the front of it. Strip those off and
+ * narrow down to just the colon-separated hex block at the
+ * end of the string.
+ */
+ const char *p = strrchr(fingerprint, ' ');
+ fingerprint = p ? p+1 : fingerprint;
+ /* Quick sanity checks, including making sure it's in lowercase */
+ assert(strlen(fingerprint) == 16*3 - 1);
+ assert(fingerprint[2] == ':');
+ assert(fingerprint[strspn(fingerprint, "0123456789abcdef:")] == 0);
+
+ if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+ fingerprint))
+ return 1; /* success */
+ }
+
+ if (ssh2keydata) {
+ /*
+ * Construct the base64-encoded public key blob and see if
+ * that's listed.
+ */
+ unsigned char *binblob;
+ char *base64blob;
+ int binlen, atoms, i;
+ binblob = ssh2keytype->public_blob(ssh2keydata, &binlen);
+ atoms = (binlen + 2) / 3;
+ base64blob = snewn(atoms * 4 + 1, char);
+ for (i = 0; i < atoms; i++)
+ base64_encode_atom(binblob + 3*i, binlen - 3*i, base64blob + 4*i);
+ base64blob[atoms * 4] = '\0';
+ sfree(binblob);
+ if (conf_get_str_str_opt(ssh->conf, CONF_ssh_manual_hostkeys,
+ base64blob)) {
+ sfree(base64blob);
+ return 1; /* success */
+ }
+ sfree(base64blob);
+ }
+
+ return 0;
+}
+
/*
* Handle the key exchange and user authentication phases.
*/
rsastr_fmt(keystr, &s->hostkey);
rsa_fingerprint(fingerprint, sizeof(fingerprint), &s->hostkey);
- ssh_set_frozen(ssh, 1);
- s->dlgret = verify_ssh_host_key(ssh->frontend,
- ssh->savedhost, ssh->savedport,
- "rsa", keystr, fingerprint,
- ssh_dialog_callback, ssh);
- sfree(keystr);
- if (s->dlgret < 0) {
- do {
- crReturn(0);
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for user host key response"));
- crStop(0);
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
+ /* First check against manually configured host keys. */
+ s->dlgret = verify_ssh_manual_host_key(ssh, fingerprint, NULL, NULL);
+ if (s->dlgret == 0) { /* did not match */
+ bombout(("Host key did not appear in manually configured list"));
+ sfree(keystr);
+ crStop(0);
+ } else if (s->dlgret < 0) { /* none configured; use standard handling */
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ "rsa", keystr, fingerprint,
+ ssh_dialog_callback, ssh);
+ sfree(keystr);
+ if (s->dlgret < 0) {
+ do {
+ crReturn(0);
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user host key response"));
+ crStop(0);
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at host key verification",
- NULL, 0, TRUE);
- crStop(0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "User aborted at host key verification",
+ NULL, 0, TRUE);
+ crStop(0);
+ }
+ } else {
+ sfree(keystr);
}
}
if (*kp == 'L' || *kp == 'R')
type = *kp++;
- if ((kp2 = strchr(kp, ':')) != NULL) {
+ if ((kp2 = host_strchr(kp, ':')) != NULL) {
/*
* There's a colon in the middle of the source port
* string, which means that the part before it is
* actually a source address.
*/
- saddr = dupprintf("%.*s", (int)(kp2 - kp), kp);
+ char *saddr_tmp = dupprintf("%.*s", (int)(kp2 - kp), kp);
+ saddr = host_strduptrim(saddr_tmp);
+ sfree(saddr_tmp);
sports = kp2+1;
} else {
saddr = NULL;
} else {
/* ordinary forwarding */
vp = val;
- vp2 = vp + strcspn(vp, ":");
+ vp2 = vp + host_strcspn(vp, ":");
host = dupprintf("%.*s", (int)(vp2 - vp), vp);
- if (vp2)
+ if (*vp2)
vp2++;
dports = vp2;
dport = atoi(dports);
ssh_special(ssh, TS_EOF);
if (ssh->ldisc)
- ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+ ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */
ssh->send_ok = 1;
ssh->channels = newtree234(ssh_channelcmp);
while (1) {
/*
* Utility routine for decoding comma-separated strings in KEXINIT.
*/
-static int in_commasep_string(char *needle, char *haystack, int haylen)
+static int in_commasep_string(char const *needle, char const *haystack,
+ int haylen)
{
int needlen;
if (!needle || !haystack) /* protect against null pointers */
/*
* Similar routine for checking whether we have the first string in a list.
*/
-static int first_in_commasep_string(char *needle, char *haystack, int haylen)
+static int first_in_commasep_string(char const *needle, char const *haystack,
+ int haylen)
{
int needlen;
if (!needle || !haystack) /* protect against null pointers */
return 0;
}
+/*
+ * Add a value to the comma-separated string at the end of the packet.
+ * If the value is already in the string, don't bother adding it again.
+ */
+static void ssh2_pkt_addstring_commasep(struct Packet *pkt, const char *data)
+{
+ if (in_commasep_string(data, (char *)pkt->data + pkt->savedpos,
+ pkt->length - pkt->savedpos)) return;
+ if (pkt->length - pkt->savedpos > 0)
+ ssh_pkt_addstring_str(pkt, ",");
+ ssh_pkt_addstring_str(pkt, data);
+}
+
/*
* SSH-2 key creation method.
int hostkeylen, siglen, rsakeylen;
void *hkey; /* actual host key */
void *rsakey; /* for RSA kex */
+ void *eckey; /* for ECDH kex */
unsigned char exchange_hash[SSH2_KEX_MAX_HASH_LEN];
int n_preferred_kex;
const struct ssh_kexes *preferred_kex[KEX_MAX];
begin_key_exchange:
ssh->pkt_kctx = SSH2_PKTCTX_NOKEX;
{
- int i, j, k, commalist_started;
+ int i, j, k;
/*
* Set up the preferred key exchange. (NULL => warn below here)
s->preferred_kex[s->n_preferred_kex++] =
&ssh_rsa_kex;
break;
+ case KEX_ECDH:
+ s->preferred_kex[s->n_preferred_kex++] =
+ &ssh_ecdh_kex;
+ break;
case KEX_WARN:
/* Flag for later. Don't bother if it's the last in
* the list. */
ssh2_pkt_addbyte(s->pktout, (unsigned char) random_byte());
/* List key exchange algorithms. */
ssh2_pkt_addstring_start(s->pktout);
- commalist_started = 0;
for (i = 0; i < s->n_preferred_kex; i++) {
const struct ssh_kexes *k = s->preferred_kex[i];
if (!k) continue; /* warning flag */
- for (j = 0; j < k->nkexes; j++) {
- if (commalist_started)
- ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout, k->list[j]->name);
- commalist_started = 1;
- }
+ for (j = 0; j < k->nkexes; j++)
+ ssh2_pkt_addstring_commasep(s->pktout, k->list[j]->name);
}
/* List server host key algorithms. */
if (!s->got_session_id) {
* we're prepared to cope with.
*/
ssh2_pkt_addstring_start(s->pktout);
- for (i = 0; i < lenof(hostkey_algs); i++) {
- ssh2_pkt_addstring_str(s->pktout, hostkey_algs[i]->name);
- if (i < lenof(hostkey_algs) - 1)
- ssh2_pkt_addstring_str(s->pktout, ",");
- }
+ for (i = 0; i < lenof(hostkey_algs); i++)
+ ssh2_pkt_addstring_commasep(s->pktout, hostkey_algs[i]->name);
} else {
/*
* In subsequent key exchanges, we list only the kex
/* 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;
- }
+ for (j = 0; j < c->nciphers; j++)
+ ssh2_pkt_addstring_commasep(s->pktout, c->list[j]->name);
}
}
/* 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, ",");
- }
+ for (i = 0; i < s->nmacs; i++)
+ ssh2_pkt_addstring_commasep(s->pktout, s->maclist[i]->name);
}
/* List client->server compression algorithms,
* then server->client compression algorithms. (We use the
ssh2_pkt_addstring_start(s->pktout);
assert(lenof(compressions) > 1);
/* Prefer non-delayed versions */
- ssh2_pkt_addstring_str(s->pktout, s->preferred_comp->name);
+ ssh2_pkt_addstring_commasep(s->pktout, s->preferred_comp->name);
/* We don't even list delayed versions of algorithms until
* they're allowed to be used, to avoid a race. See the end of
* this function. */
- if (s->userauth_succeeded && s->preferred_comp->delayed_name) {
- ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout,
- s->preferred_comp->delayed_name);
- }
+ if (s->userauth_succeeded && s->preferred_comp->delayed_name)
+ ssh2_pkt_addstring_commasep(s->pktout,
+ s->preferred_comp->delayed_name);
for (i = 0; i < lenof(compressions); i++) {
const struct ssh_compress *c = compressions[i];
- if (c != s->preferred_comp) {
- ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout, c->name);
- if (s->userauth_succeeded && c->delayed_name) {
- ssh2_pkt_addstring_str(s->pktout, ",");
- ssh2_pkt_addstring_str(s->pktout, c->delayed_name);
- }
- }
+ ssh2_pkt_addstring_commasep(s->pktout, c->name);
+ if (s->userauth_succeeded && c->delayed_name)
+ ssh2_pkt_addstring_commasep(s->pktout, c->delayed_name);
}
}
/* List client->server languages. Empty list. */
pktin->savedpos += 16; /* skip garbage cookie */
ssh_pkt_getstring(pktin, &str, &len); /* key exchange algorithms */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
preferred = NULL;
for (i = 0; i < s->n_preferred_kex; i++) {
break;
}
if (!ssh->kex) {
- bombout(("Couldn't agree a key exchange algorithm (available: %s)",
- str ? str : "(null)"));
+ bombout(("Couldn't agree a key exchange algorithm"
+ " (available: %.*s)", len, str));
crStopV;
}
/*
*/
s->guessok = first_in_commasep_string(preferred, str, len);
ssh_pkt_getstring(pktin, &str, &len); /* host key algorithms */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < lenof(hostkey_algs); i++) {
if (in_commasep_string(hostkey_algs[i]->name, str, len)) {
ssh->hostkey = hostkey_algs[i];
}
}
if (!ssh->hostkey) {
- bombout(("Couldn't agree a host key algorithm (available: %s)",
- str ? str : "(null)"));
+ bombout(("Couldn't agree a host key algorithm"
+ " (available: %.*s)", len, str));
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 (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) {
break;
}
if (!s->cscipher_tobe) {
- bombout(("Couldn't agree a client-to-server cipher (available: %s)",
- str ? str : "(null)"));
+ bombout(("Couldn't agree a client-to-server cipher"
+ " (available: %.*s)", len, str));
crStopV;
}
ssh_pkt_getstring(pktin, &str, &len); /* server->client cipher */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < s->n_preferred_ciphers; i++) {
const struct ssh2_ciphers *c = s->preferred_ciphers[i];
if (!c) {
break;
}
if (!s->sccipher_tobe) {
- bombout(("Couldn't agree a server-to-client cipher (available: %s)",
- str ? str : "(null)"));
+ bombout(("Couldn't agree a server-to-client cipher"
+ " (available: %.*s)", len, str));
crStopV;
}
ssh_pkt_getstring(pktin, &str, &len); /* client->server mac */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < s->nmacs; i++) {
if (in_commasep_string(s->maclist[i]->name, str, len)) {
s->csmac_tobe = s->maclist[i];
}
}
ssh_pkt_getstring(pktin, &str, &len); /* server->client mac */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < s->nmacs; i++) {
if (in_commasep_string(s->maclist[i]->name, str, len)) {
s->scmac_tobe = s->maclist[i];
}
}
ssh_pkt_getstring(pktin, &str, &len); /* client->server compression */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < lenof(compressions) + 1; i++) {
const struct ssh_compress *c =
i == 0 ? s->preferred_comp : compressions[i - 1];
}
}
ssh_pkt_getstring(pktin, &str, &len); /* server->client compression */
+ if (!str) {
+ bombout(("KEXINIT packet was incomplete"));
+ crStopV;
+ }
for (i = 0; i < lenof(compressions) + 1; i++) {
const struct ssh_compress *c =
i == 0 ? s->preferred_comp : compressions[i - 1];
freebn(s->g);
freebn(s->p);
}
+ } else if (ssh->kex->main_type == KEXTYPE_ECDH) {
+
+ logeventf(ssh, "Doing ECDH key exchange with hash %s",
+ ssh->kex->hash->text_name);
+ ssh->pkt_kctx = SSH2_PKTCTX_ECDHKEX;
+
+ s->eckey = NULL;
+ if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp256")) {
+ s->eckey = ssh_ecdhkex_newkey(ec_p256());
+ } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp384")) {
+ s->eckey = ssh_ecdhkex_newkey(ec_p384());
+ } else if (!strcmp(ssh->kex->name, "ecdh-sha2-nistp521")) {
+ s->eckey = ssh_ecdhkex_newkey(ec_p521());
+ }
+ if (!s->eckey) {
+ bombout(("Unable to generate key for ECDH"));
+ crStopV;
+ }
+
+ {
+ char *publicPoint;
+ int publicPointLength;
+ publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+ if (!publicPoint) {
+ ssh_ecdhkex_freekey(s->eckey);
+ bombout(("Unable to encode public key for ECDH"));
+ crStopV;
+ }
+ s->pktout = ssh2_pkt_init(SSH2_MSG_KEX_ECDH_INIT);
+ ssh2_pkt_addstring_start(s->pktout);
+ ssh2_pkt_addstring_data(s->pktout, publicPoint, publicPointLength);
+ sfree(publicPoint);
+ }
+
+ ssh2_pkt_send_noqueue(ssh, s->pktout);
+
+ crWaitUntilV(pktin);
+ if (pktin->type != SSH2_MSG_KEX_ECDH_REPLY) {
+ ssh_ecdhkex_freekey(s->eckey);
+ bombout(("expected ECDH reply packet from server"));
+ crStopV;
+ }
+
+ ssh_pkt_getstring(pktin, &s->hostkeydata, &s->hostkeylen);
+ hash_string(ssh->kex->hash, ssh->exhash, s->hostkeydata, s->hostkeylen);
+ s->hkey = ssh->hostkey->newkey(s->hostkeydata, s->hostkeylen);
+
+ {
+ char *publicPoint;
+ int publicPointLength;
+ publicPoint = ssh_ecdhkex_getpublic(s->eckey, &publicPointLength);
+ if (!publicPoint) {
+ ssh_ecdhkex_freekey(s->eckey);
+ bombout(("Unable to encode public key for ECDH hash"));
+ crStopV;
+ }
+ hash_string(ssh->kex->hash, ssh->exhash,
+ publicPoint, publicPointLength);
+ sfree(publicPoint);
+ }
+
+ {
+ char *keydata;
+ int keylen;
+ ssh_pkt_getstring(pktin, &keydata, &keylen);
+ hash_string(ssh->kex->hash, ssh->exhash, keydata, keylen);
+ s->K = ssh_ecdhkex_getkey(s->eckey, keydata, keylen);
+ if (!s->K) {
+ ssh_ecdhkex_freekey(s->eckey);
+ bombout(("point received in ECDH was not valid"));
+ crStopV;
+ }
+ }
+
+ ssh_pkt_getstring(pktin, &s->sigdata, &s->siglen);
+
+ ssh_ecdhkex_freekey(s->eckey);
} else {
logeventf(ssh, "Doing RSA key exchange with hash %s",
ssh->kex->hash->text_name);
* checked the signature of the exchange hash.)
*/
s->fingerprint = ssh->hostkey->fingerprint(s->hkey);
- ssh_set_frozen(ssh, 1);
- s->dlgret = verify_ssh_host_key(ssh->frontend,
- ssh->savedhost, ssh->savedport,
- ssh->hostkey->keytype, s->keystr,
- s->fingerprint,
- ssh_dialog_callback, ssh);
- if (s->dlgret < 0) {
- do {
- crReturnV;
- if (pktin) {
- bombout(("Unexpected data from server while waiting"
- " for user host key response"));
- crStopV;
- }
- } while (pktin || inlen > 0);
- s->dlgret = ssh->user_response;
- }
- ssh_set_frozen(ssh, 0);
- if (s->dlgret == 0) {
- ssh_disconnect(ssh, "User aborted at host key verification", NULL,
- 0, TRUE);
- crStopV;
- }
logevent("Host key fingerprint is:");
logevent(s->fingerprint);
+ /* First check against manually configured host keys. */
+ s->dlgret = verify_ssh_manual_host_key(ssh, s->fingerprint,
+ ssh->hostkey, s->hkey);
+ if (s->dlgret == 0) { /* did not match */
+ bombout(("Host key did not appear in manually configured list"));
+ crStopV;
+ } else if (s->dlgret < 0) { /* none configured; use standard handling */
+ ssh_set_frozen(ssh, 1);
+ s->dlgret = verify_ssh_host_key(ssh->frontend,
+ ssh->savedhost, ssh->savedport,
+ ssh->hostkey->keytype, s->keystr,
+ s->fingerprint,
+ ssh_dialog_callback, ssh);
+ if (s->dlgret < 0) {
+ do {
+ crReturnV;
+ if (pktin) {
+ bombout(("Unexpected data from server while waiting"
+ " for user host key response"));
+ crStopV;
+ }
+ } while (pktin || inlen > 0);
+ s->dlgret = ssh->user_response;
+ }
+ ssh_set_frozen(ssh, 0);
+ if (s->dlgret == 0) {
+ ssh_disconnect(ssh, "Aborted at host key verification", NULL,
+ 0, TRUE);
+ crStopV;
+ }
+ }
sfree(s->fingerprint);
/*
* Save this host key, to check against the one presented in
* 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.
+ * 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, or because
+ * the server initiated channel closure before we saw the response)
+ * 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)
*/
ssh2_channel_got_eof(c);
+ if (!(ssh->remote_bugs & BUG_SENDS_LATE_REQUEST_REPLY)) {
+ /*
+ * It also means we stop expecting to see replies to any
+ * outstanding channel requests, so clean those up too.
+ * (ssh_chanreq_init will enforce by assertion that we don't
+ * subsequently put anything back on this list.)
+ */
+ while (c->v.v2.chanreq_head) {
+ struct outstanding_channel_request *ocr = c->v.v2.chanreq_head;
+ ocr->handler(c, NULL, ocr->ctx);
+ c->v.v2.chanreq_head = ocr->next;
+ sfree(ocr);
+ }
+ }
+
/*
* And we also send an outgoing EOF, if we haven't already, on the
* assumption that CLOSE is a pretty forceful announcement that
ssh_pkt_getstring(pktin, &type, &typelen);
want_reply = ssh2_pkt_getbool(pktin);
+ if (c->closes & CLOSES_SENT_CLOSE) {
+ /*
+ * We don't reply to channel requests after we've sent
+ * CHANNEL_CLOSE for the channel, because our reply might
+ * cross in the network with the other side's CHANNEL_CLOSE
+ * and arrive after they have wound the channel up completely.
+ */
+ want_reply = FALSE;
+ }
+
/*
* Having got the channel number, we now look at
* the request type string to see if it's something
static void ssh2_response_authconn(struct ssh_channel *c, struct Packet *pktin,
void *ctx)
{
- do_ssh2_authconn(c->ssh, NULL, 0, pktin);
+ if (pktin)
+ do_ssh2_authconn(c->ssh, NULL, 0, pktin);
}
static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
* Transfer data!
*/
if (ssh->ldisc)
- ldisc_send(ssh->ldisc, NULL, 0, 0);/* cause ldisc to notice changes */
+ ldisc_echoedit_update(ssh->ldisc); /* cause ldisc to notice changes */
if (ssh->mainchan)
ssh->send_ok = 1;
while (1) {
ssh->sent_console_eof = FALSE;
ssh->got_pty = FALSE;
ssh->bare_connection = FALSE;
+ ssh->X11_fwd_enabled = FALSE;
+ ssh->connshare = NULL;
ssh->attempting_connshare = FALSE;
*backend_handle = ssh;
ssh->gsslibs = NULL;
#endif
+ random_ref(); /* do this now - may be needed by sharing setup code */
+
p = connect_to_host(ssh, host, port, realhost, nodelay, keepalive);
- if (p != NULL)
+ if (p != NULL) {
+ random_unref();
return p;
-
- random_ref();
+ }
return NULL;
}
PKT_END);
} else {
pktout = ssh2_chanopen_init(c, "direct-tcpip");
- ssh2_pkt_addstring(pktout, hostname);
+ {
+ char *trimmed_host = host_strduptrim(hostname);
+ ssh2_pkt_addstring(pktout, trimmed_host);
+ sfree(trimmed_host);
+ }
ssh2_pkt_adduint32(pktout, port);
/*
* We make up values for the originator data; partly it's
}
/*
- * cfg_info for SSH is the currently running version of the
- * protocol. (1 for 1; 2 for 2; 0 for not-decided-yet.)
+ * cfg_info for SSH is the protocol running in this session.
+ * (1 or 2 for the full SSH-1 or SSH-2 protocol; -1 for the bare
+ * SSH-2 connection protocol, i.e. a downstream; 0 for not-decided-yet.)
*/
static int ssh_cfg_info(void *handle)
{
Ssh ssh = (Ssh) handle;
- return ssh->version;
+ if (ssh->version == 0)
+ return 0; /* don't know yet */
+ else if (ssh->bare_connection)
+ return -1;
+ else
+ return ssh->version;
}
/*