+ for (i = 0; i < lenof(compressions) + 1; i++) {
+ const struct ssh_compress *c =
+ i == 0 ? preferred_comp : compressions[i - 1];
+ if (in_commasep_string(c->name, str, len)) {
+ sccomp_tobe = c;
+ break;
+ }
+ }
+
+ /*
+ * Work out the number of bits of key we will need from the key
+ * exchange. We start with the maximum key length of either
+ * cipher...
+ */
+ {
+ int csbits, scbits;
+
+ csbits = cscipher_tobe->keylen;
+ scbits = sccipher_tobe->keylen;
+ nbits = (csbits > scbits ? csbits : scbits);
+ }
+ /* The keys only have 160-bit entropy, since they're based on
+ * a SHA-1 hash. So cap the key size at 160 bits. */
+ if (nbits > 160)
+ nbits = 160;
+
+ /*
+ * If we're doing Diffie-Hellman group exchange, start by
+ * requesting a group.
+ */
+ if (kex == &ssh_diffiehellman_gex) {
+ logevent("Doing Diffie-Hellman group exchange");
+ /*
+ * Work out how big a DH group we will need to allow that
+ * much data.
+ */
+ pbits = 512 << ((nbits - 1) / 64);
+ ssh2_pkt_init(SSH2_MSG_KEX_DH_GEX_REQUEST);
+ ssh2_pkt_adduint32(pbits);
+ ssh2_pkt_send();
+
+ crWaitUntil(ispkt);
+ if (pktin.type != SSH2_MSG_KEX_DH_GEX_GROUP) {
+ bombout(("expected key exchange group packet from server"));
+ crReturn(0);
+ }
+ p = ssh2_pkt_getmp();
+ g = ssh2_pkt_getmp();
+ dh_setup_group(p, g);
+ kex_init_value = SSH2_MSG_KEX_DH_GEX_INIT;
+ kex_reply_value = SSH2_MSG_KEX_DH_GEX_REPLY;
+ } else {
+ dh_setup_group1();
+ kex_init_value = SSH2_MSG_KEXDH_INIT;
+ kex_reply_value = SSH2_MSG_KEXDH_REPLY;
+ }
+
+ logevent("Doing Diffie-Hellman key exchange");
+ /*
+ * Now generate and send e for Diffie-Hellman.
+ */
+ e = dh_create_e(nbits * 2);
+ ssh2_pkt_init(kex_init_value);
+ ssh2_pkt_addmp(e);
+ ssh2_pkt_send();
+
+ crWaitUntil(ispkt);
+ if (pktin.type != kex_reply_value) {
+ bombout(("expected key exchange reply packet from server"));
+ crReturn(0);
+ }
+ ssh2_pkt_getstring(&hostkeydata, &hostkeylen);
+ f = ssh2_pkt_getmp();
+ ssh2_pkt_getstring(&sigdata, &siglen);
+
+ K = dh_find_K(f);
+
+ sha_string(&exhash, hostkeydata, hostkeylen);
+ if (kex == &ssh_diffiehellman_gex) {
+ sha_uint32(&exhash, pbits);
+ sha_mpint(&exhash, p);
+ sha_mpint(&exhash, g);
+ }
+ sha_mpint(&exhash, e);
+ sha_mpint(&exhash, f);
+ sha_mpint(&exhash, K);
+ SHA_Final(&exhash, exchange_hash);
+
+ dh_cleanup();
+
+#if 0
+ debug(("Exchange hash is:\n"));
+ dmemdump(exchange_hash, 20);
+#endif
+
+ hkey = hostkey->newkey(hostkeydata, hostkeylen);
+ if (!hkey ||
+ !hostkey->verifysig(hkey, sigdata, siglen, exchange_hash, 20)) {
+ bombout(("Server's host key did not match the signature supplied"));
+ crReturn(0);
+ }
+
+ /*
+ * Authenticate remote host: verify host key. (We've already
+ * checked the signature of the exchange hash.)
+ */
+ keystr = hostkey->fmtkey(hkey);
+ fingerprint = hostkey->fingerprint(hkey);
+ verify_ssh_host_key(savedhost, savedport, hostkey->keytype,
+ keystr, fingerprint);
+ if (first_kex) { /* don't bother logging this in rekeys */
+ logevent("Host key fingerprint is:");
+ logevent(fingerprint);
+ }
+ sfree(fingerprint);
+ sfree(keystr);
+ hostkey->freekey(hkey);
+
+ /*
+ * Send SSH2_MSG_NEWKEYS.
+ */
+ 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.
+ */
+ cscipher = cscipher_tobe;
+ sccipher = sccipher_tobe;
+ csmac = csmac_tobe;
+ scmac = scmac_tobe;
+ cscomp = cscomp_tobe;
+ sccomp = sccomp_tobe;
+ cscomp->compress_init();
+ sccomp->decompress_init();
+ /*
+ * Set IVs after keys. Here we use the exchange hash from the
+ * _first_ key exchange.
+ */
+ if (first_kex)
+ memcpy(ssh2_session_id, exchange_hash, sizeof(exchange_hash));
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'C', keyspace);
+ cscipher->setcskey(keyspace);
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'D', keyspace);
+ sccipher->setsckey(keyspace);
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'A', keyspace);
+ cscipher->setcsiv(keyspace);
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'B', keyspace);
+ sccipher->setsciv(keyspace);
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'E', keyspace);
+ csmac->setcskey(keyspace);
+ ssh2_mkkey(K, exchange_hash, ssh2_session_id, 'F', keyspace);
+ scmac->setsckey(keyspace);
+
+ /*
+ * 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 (!first_kex) {
+ crReturn(0);
+ }
+ first_kex = 0;
+
+ /*
+ * Now we're encrypting. Begin returning 1 to the protocol main
+ * function so that other things can run on top of the
+ * transport. If we ever see a KEXINIT, we must go back to the
+ * start.
+ */
+ while (!(ispkt && pktin.type == SSH2_MSG_KEXINIT)) {
+ crReturn(1);
+ }
+ logevent("Server initiated key re-exchange");
+ goto begin_key_exchange;
+
+ crFinish(1);
+}
+
+/*
+ * Add data to an SSH2 channel output buffer.
+ */
+static void ssh2_add_channel_data(struct ssh_channel *c, char *buf,
+ int len)
+{
+ bufchain_add(&c->v.v2.outbuffer, buf, len);
+}
+
+/*
+ * Attempt to send data on an SSH2 channel.
+ */
+static int ssh2_try_send(struct ssh_channel *c)
+{
+ while (c->v.v2.remwindow > 0 && bufchain_size(&c->v.v2.outbuffer) > 0) {
+ int len;
+ void *data;
+ bufchain_prefix(&c->v.v2.outbuffer, &data, &len);
+ if ((unsigned)len > c->v.v2.remwindow)
+ len = c->v.v2.remwindow;
+ if ((unsigned)len > c->v.v2.remmaxpkt)
+ len = c->v.v2.remmaxpkt;
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_DATA);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_addstring_start();
+ ssh2_pkt_addstring_data(data, len);
+ ssh2_pkt_send();
+ bufchain_consume(&c->v.v2.outbuffer, len);
+ c->v.v2.remwindow -= len;
+ }
+
+ /*
+ * After having sent as much data as we can, return the amount
+ * still buffered.
+ */
+ return bufchain_size(&c->v.v2.outbuffer);
+}
+
+/*
+ * Potentially enlarge the window on an SSH2 channel.
+ */
+static void ssh2_set_window(struct ssh_channel *c, unsigned newwin)
+{
+ if (newwin > c->v.v2.locwindow) {
+ ssh2_pkt_init(SSH2_MSG_CHANNEL_WINDOW_ADJUST);
+ ssh2_pkt_adduint32(c->remoteid);
+ ssh2_pkt_adduint32(newwin - c->v.v2.locwindow);
+ ssh2_pkt_send();
+ c->v.v2.locwindow = newwin;
+ }
+}
+
+/*
+ * Handle the SSH2 userauth and connection layers.
+ */
+static void do_ssh2_authconn(unsigned char *in, int inlen, int ispkt)
+{
+ static enum {
+ AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE,
+ AUTH_PASSWORD,
+ AUTH_KEYBOARD_INTERACTIVE
+ } method;
+ static enum {
+ AUTH_TYPE_NONE,
+ AUTH_TYPE_PUBLICKEY,
+ AUTH_TYPE_PUBLICKEY_OFFER_LOUD,
+ AUTH_TYPE_PUBLICKEY_OFFER_QUIET,
+ AUTH_TYPE_PASSWORD,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE,
+ AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET
+ } type;
+ static int gotit, need_pw, can_pubkey, can_passwd, can_keyb_inter;
+ static int tried_pubkey_config, tried_agent, tried_keyb_inter;
+ static int kbd_inter_running;
+ static int we_are_in;
+ static int num_prompts, echo;
+ static char username[100];
+ static char pwprompt[200];
+ static char password[100];
+
+ crBegin;
+
+ /*
+ * Request userauth protocol, and await a response to it.
+ */
+ ssh2_pkt_init(SSH2_MSG_SERVICE_REQUEST);
+ ssh2_pkt_addstring("ssh-userauth");
+ ssh2_pkt_send();
+ crWaitUntilV(ispkt);
+ if (pktin.type != SSH2_MSG_SERVICE_ACCEPT) {
+ bombout(("Server refused user authentication protocol"));
+ crReturnV;
+ }
+
+ /*
+ * We repeat this whole loop, including the username prompt,
+ * until we manage a successful authentication. If the user
+ * types the wrong _password_, they are sent back to the
+ * beginning to try another username. (If they specify a
+ * username in the config, they are never asked, even if they
+ * do give a wrong password.)
+ *
+ * I think this best serves the needs of
+ *
+ * - the people who have no configuration, no keys, and just
+ * want to try repeated (username,password) pairs until they
+ * type both correctly
+ *
+ * - people who have keys and configuration but occasionally
+ * need to fall back to passwords
+ *
+ * - people with a key held in Pageant, who might not have
+ * logged in to a particular machine before; so they want to
+ * type a username, and then _either_ their key will be
+ * accepted, _or_ they will type a password. If they mistype
+ * the username they will want to be able to get back and
+ * retype it!
+ */
+ do {
+ static int pos;
+ static char c;
+
+ /*
+ * Get a username.
+ */
+ pos = 0;
+ if ((flags & FLAG_INTERACTIVE) && !*cfg.username) {
+ if (ssh_get_line) {
+ if (!ssh_get_line("login as: ",
+ username, sizeof(username), FALSE)) {
+ /*
+ * get_line failed to get a username.
+ * Terminate.
+ */
+ logevent("No username provided. Abandoning session.");
+ ssh_state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+ } else {
+ c_write_str("login as: ");
+ ssh_send_ok = 1;
+ while (pos >= 0) {
+ 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:
+ random_save_seed();
+ exit(0);
+ break;
+ default:
+ if (((c >= ' ' && c <= '~') ||
+ ((unsigned char) c >= 160))
+ && pos < sizeof(username)-1) {
+ username[pos++] = c;
+ c_write(&c, 1);
+ }
+ break;
+ }
+ }
+ }
+ c_write_str("\r\n");
+ username[strcspn(username, "\n\r")] = '\0';
+ } else {
+ char stuff[200];
+ strncpy(username, cfg.username, 99);
+ username[99] = '\0';
+ if ((flags & FLAG_VERBOSE) || (flags & FLAG_INTERACTIVE)) {
+ sprintf(stuff, "Using username \"%s\".\r\n", username);
+ c_write_str(stuff);
+ }
+ }
+
+ /*
+ * Send an authentication request using method "none": (a)
+ * just in case it succeeds, and (b) so that we know what
+ * authentication methods we can usefully try next.
+ */
+ ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(username);
+ ssh2_pkt_addstring("ssh-connection"); /* service requested */
+ ssh2_pkt_addstring("none"); /* method */
+ ssh2_pkt_send();
+ type = AUTH_TYPE_NONE;
+ gotit = FALSE;
+ we_are_in = FALSE;
+
+ tried_pubkey_config = FALSE;
+ tried_agent = FALSE;
+ tried_keyb_inter = FALSE;
+ kbd_inter_running = FALSE;
+
+ while (1) {
+ /*
+ * Wait for the result of the last authentication request.
+ */
+ if (!gotit)
+ crWaitUntilV(ispkt);
+ while (pktin.type == SSH2_MSG_USERAUTH_BANNER) {
+ char *banner;
+ int size;
+ /*
+ * Don't show the banner if we're operating in
+ * non-verbose non-interactive mode. (It's probably
+ * a script, which means nobody will read the
+ * banner _anyway_, and moreover the printing of
+ * the banner will screw up processing on the
+ * output of (say) plink.)
+ */
+ if (flags & (FLAG_VERBOSE | FLAG_INTERACTIVE)) {
+ ssh2_pkt_getstring(&banner, &size);
+ if (banner)
+ c_write_untrusted(banner, size);
+ }
+ crWaitUntilV(ispkt);
+ }
+ if (pktin.type == SSH2_MSG_USERAUTH_SUCCESS) {
+ logevent("Access granted");
+ we_are_in = TRUE;
+ break;
+ }
+
+ if (kbd_inter_running &&
+ pktin.type == SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ /*
+ * This is a further prompt in keyboard-interactive
+ * authentication. Do nothing.
+ */
+ } else if (pktin.type != SSH2_MSG_USERAUTH_FAILURE) {
+ bombout(("Strange packet received during authentication: type %d",
+ pktin.type));
+ crReturnV;
+ }
+
+ gotit = FALSE;
+
+ /*
+ * OK, we're now sitting on a USERAUTH_FAILURE message, so
+ * we can look at the string in it and know what we can
+ * helpfully try next.
+ */
+ if (pktin.type == SSH2_MSG_USERAUTH_FAILURE) {
+ char *methods;
+ int methlen;
+ ssh2_pkt_getstring(&methods, &methlen);
+ kbd_inter_running = FALSE;
+ if (!ssh2_pkt_getbool()) {
+ /*
+ * We have received an unequivocal Access
+ * Denied. This can translate to a variety of
+ * messages:
+ *
+ * - if we'd just tried "none" authentication,
+ * it's not worth printing anything at all
+ *
+ * - if we'd just tried a public key _offer_,
+ * the message should be "Server refused our
+ * key" (or no message at all if the key
+ * came from Pageant)
+ *
+ * - if we'd just tried anything else, the
+ * message really should be "Access denied".
+ *
+ * Additionally, if we'd just tried password
+ * authentication, we should break out of this
+ * whole loop so as to go back to the username
+ * prompt.
+ */
+ if (type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ c_write_str("Server refused our key\r\n");
+ logevent("Server refused public key");
+ } else if (type == AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* server declined keyboard-interactive; ignore */
+ } else {
+ c_write_str("Access denied\r\n");
+ logevent("Access denied");
+ if (type == AUTH_TYPE_PASSWORD) {
+ we_are_in = FALSE;
+ break;
+ }
+ }
+ } else {
+ c_write_str("Further authentication required\r\n");
+ logevent("Further authentication required");
+ }