+ s->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 (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE) {
+ char *methods;
+ int methlen;
+ ssh2_pkt_getstring(ssh, &methods, &methlen);
+ s->kbd_inter_running = FALSE;
+ if (!ssh2_pkt_getbool(ssh)) {
+ /*
+ * 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 (s->type == AUTH_TYPE_NONE) {
+ /* do nothing */
+ } else if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD ||
+ s->type == AUTH_TYPE_PUBLICKEY_OFFER_QUIET) {
+ if (s->type == AUTH_TYPE_PUBLICKEY_OFFER_LOUD)
+ c_write_str(ssh, "Server refused our key\r\n");
+ logevent("Server refused public key");
+ } else if (s->type==AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET) {
+ /* server declined keyboard-interactive; ignore */
+ } else {
+ c_write_str(ssh, "Access denied\r\n");
+ logevent("Access denied");
+ if (s->type == AUTH_TYPE_PASSWORD) {
+ s->we_are_in = FALSE;
+ break;
+ }
+ }
+ } else {
+ c_write_str(ssh, "Further authentication required\r\n");
+ logevent("Further authentication required");
+ }
+
+ s->can_pubkey =
+ in_commasep_string("publickey", methods, methlen);
+ s->can_passwd =
+ in_commasep_string("password", methods, methlen);
+ s->can_keyb_inter = ssh->cfg.try_ki_auth &&
+ in_commasep_string("keyboard-interactive", methods, methlen);
+ }
+
+ s->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.
+ */
+ s->echo = 0;
+
+ if (!s->method && s->can_pubkey &&
+ agent_exists() && !s->tried_agent) {
+ /*
+ * Attempt public-key authentication using Pageant.
+ */
+ void *r;
+ s->authed = FALSE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+ s->tried_agent = TRUE;
+
+ logevent("Pageant is running. Requesting keys.");
+
+ /* Request the keys held by the agent. */
+ PUT_32BIT(s->request, 1);
+ s->request[4] = SSH2_AGENTC_REQUEST_IDENTITIES;
+ agent_query(s->request, 5, &r, &s->responselen);
+ s->response = (unsigned char *) r;
+ if (s->response && s->responselen >= 5 &&
+ s->response[4] == SSH2_AGENT_IDENTITIES_ANSWER) {
+ s->p = s->response + 5;
+ s->nkeys = GET_32BIT(s->p);
+ s->p += 4;
+ {
+ char buf[64];
+ sprintf(buf, "Pageant has %d SSH2 keys", s->nkeys);
+ logevent(buf);
+ }
+ for (s->keyi = 0; s->keyi < s->nkeys; s->keyi++) {
+ void *vret;
+
+ {
+ char buf[64];
+ sprintf(buf, "Trying Pageant key #%d", s->keyi);
+ logevent(buf);
+ }
+ s->pklen = GET_32BIT(s->p);
+ s->p += 4;
+ if (s->publickey_blob &&
+ s->pklen == s->publickey_bloblen &&
+ !memcmp(s->p, s->publickey_blob,
+ s->publickey_bloblen)) {
+ logevent("This key matches configured key file");
+ s->tried_pubkey_config = 1;
+ }
+ s->pkblob = (char *)s->p;
+ s->p += s->pklen;
+ s->alglen = GET_32BIT(s->pkblob);
+ s->alg = s->pkblob + 4;
+ s->commentlen = GET_32BIT(s->p);
+ s->p += 4;
+ s->commentp = (char *)s->p;
+ s->p += s->commentlen;
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, FALSE); /* no signature included */
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+ ssh2_pkt_send(ssh);
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+ logevent("Key refused");
+ continue;
+ }
+
+ if (flags & FLAG_VERBOSE) {
+ c_write_str(ssh, "Authenticating with "
+ "public key \"");
+ c_write(ssh, s->commentp, s->commentlen);
+ c_write_str(ssh, "\" from agent\r\n");
+ }
+
+ /*
+ * Server is willing to accept the key.
+ * Construct a SIGN_REQUEST.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, TRUE);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->alg, s->alglen);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, s->pkblob, s->pklen);
+
+ s->siglen = ssh->pktout.length - 5 + 4 + 20;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ s->siglen -= 4;
+ s->len = 1; /* message type */
+ s->len += 4 + s->pklen; /* key blob */
+ s->len += 4 + s->siglen; /* data to sign */
+ s->len += 4; /* flags */
+ s->agentreq = smalloc(4 + s->len);
+ PUT_32BIT(s->agentreq, s->len);
+ s->q = s->agentreq + 4;
+ *s->q++ = SSH2_AGENTC_SIGN_REQUEST;
+ PUT_32BIT(s->q, s->pklen);
+ s->q += 4;
+ memcpy(s->q, s->pkblob, s->pklen);
+ s->q += s->pklen;
+ PUT_32BIT(s->q, s->siglen);
+ s->q += 4;
+ /* Now the data to be signed... */
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(s->q, 20);
+ s->q += 4;
+ }
+ memcpy(s->q, ssh->v2_session_id, 20);
+ s->q += 20;
+ memcpy(s->q, ssh->pktout.data + 5,
+ ssh->pktout.length - 5);
+ s->q += ssh->pktout.length - 5;
+ /* And finally the (zero) flags word. */
+ PUT_32BIT(s->q, 0);
+ agent_query(s->agentreq, s->len + 4, &vret, &s->retlen);
+ s->ret = vret;
+ sfree(s->agentreq);
+ if (s->ret) {
+ if (s->ret[4] == SSH2_AGENT_SIGN_RESPONSE) {
+ logevent("Sending Pageant's response");
+ ssh2_add_sigblob(ssh, s->pkblob, s->pklen,
+ s->ret + 9,
+ GET_32BIT(s->ret + 5));
+ ssh2_pkt_send(ssh);
+ s->authed = TRUE;
+ break;
+ } else {
+ logevent
+ ("Pageant failed to answer challenge");
+ sfree(s->ret);
+ }
+ }
+ }
+ if (s->authed)
+ continue;
+ }
+ }
+
+ if (!s->method && s->can_pubkey && s->publickey_blob
+ && !s->tried_pubkey_config) {
+ unsigned char *pub_blob;
+ char *algorithm, *comment;
+ int pub_blob_len;
+
+ s->tried_pubkey_config = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_PUBLICKEY;
+
+ /*
+ * Try the public key supplied in the configuration.
+ *
+ * First, offer the public blob to see if the server is
+ * willing to accept it.
+ */
+ pub_blob =
+ (unsigned char *)ssh2_userkey_loadpub(&ssh->cfg.keyfile,
+ &algorithm,
+ &pub_blob_len);
+ if (pub_blob) {
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, FALSE); /* no signature included */
+ ssh2_pkt_addstring(ssh, algorithm);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, (char *)pub_blob,
+ pub_blob_len);
+ ssh2_pkt_send(ssh);
+ logevent("Offered public key"); /* FIXME */
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_PK_OK) {
+ s->gotit = TRUE;
+ s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD;
+ continue; /* key refused; give up on it */
+ }
+
+ logevent("Offer of public key accepted");
+ /*
+ * Actually attempt a serious authentication using
+ * the key.
+ */
+ if (ssh2_userkey_encrypted(&ssh->cfg.keyfile, &comment)) {
+ sprintf(s->pwprompt,
+ "Passphrase for key \"%.100s\": ",
+ comment);
+ s->need_pw = TRUE;
+ } else {
+ s->need_pw = FALSE;
+ }
+ c_write_str(ssh, "Authenticating with public key \"");
+ c_write_str(ssh, comment);
+ c_write_str(ssh, "\"\r\n");
+ s->method = AUTH_PUBLICKEY_FILE;
+ }
+ }
+
+ if (!s->method && s->can_keyb_inter && !s->tried_keyb_inter) {
+ s->method = AUTH_KEYBOARD_INTERACTIVE;
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+ s->tried_keyb_inter = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "keyboard-interactive"); /* method */
+ ssh2_pkt_addstring(ssh, ""); /* lang */
+ ssh2_pkt_addstring(ssh, "");
+ ssh2_pkt_send(ssh);
+
+ crWaitUntilV(ispkt);
+ if (ssh->pktin.type != SSH2_MSG_USERAUTH_INFO_REQUEST) {
+ if (ssh->pktin.type == SSH2_MSG_USERAUTH_FAILURE)
+ s->gotit = TRUE;
+ logevent("Keyboard-interactive authentication refused");
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE_QUIET;
+ continue;
+ }
+
+ s->kbd_inter_running = TRUE;
+ s->curr_prompt = 0;
+ }
+
+ if (s->kbd_inter_running) {
+ s->method = AUTH_KEYBOARD_INTERACTIVE;
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+ s->tried_keyb_inter = TRUE;
+
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_KBDINTER;
+
+ if (s->curr_prompt == 0) {
+ /*
+ * We've got a fresh USERAUTH_INFO_REQUEST.
+ * Display header data, and start going through
+ * the prompts.
+ */
+ char *name, *inst, *lang;
+ int name_len, inst_len, lang_len;
+
+ ssh2_pkt_getstring(ssh, &name, &name_len);
+ ssh2_pkt_getstring(ssh, &inst, &inst_len);
+ ssh2_pkt_getstring(ssh, &lang, &lang_len);
+ if (name_len > 0) {
+ c_write_untrusted(ssh, name, name_len);
+ c_write_str(ssh, "\r\n");
+ }
+ if (inst_len > 0) {
+ c_write_untrusted(ssh, inst, inst_len);
+ c_write_str(ssh, "\r\n");
+ }
+ s->num_prompts = ssh2_pkt_getuint32(ssh);
+ }
+
+ /*
+ * If there are prompts remaining in the packet,
+ * display one and get a response.
+ */
+ if (s->curr_prompt < s->num_prompts) {
+ char *prompt;
+ int prompt_len;
+
+ ssh2_pkt_getstring(ssh, &prompt, &prompt_len);
+ if (prompt_len > 0) {
+ strncpy(s->pwprompt, prompt, sizeof(s->pwprompt));
+ s->pwprompt[prompt_len < sizeof(s->pwprompt) ?
+ prompt_len : sizeof(s->pwprompt)-1] = '\0';
+ } else {
+ strcpy(s->pwprompt,
+ "<server failed to send prompt>: ");
+ }
+ s->echo = ssh2_pkt_getbool(ssh);
+ s->need_pw = TRUE;
+ } else
+ s->need_pw = FALSE;
+ }
+
+ if (!s->method && s->can_passwd) {
+ s->method = AUTH_PASSWORD;
+ ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
+ ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
+ sprintf(s->pwprompt, "%.90s@%.90s's password: ", s->username,
+ ssh->savedhost);
+ s->need_pw = TRUE;
+ }
+
+ if (s->need_pw) {
+ if (ssh_get_line) {
+ if (!ssh_get_line(s->pwprompt, s->password,
+ sizeof(s->password), TRUE)) {
+ /*
+ * get_line failed to get a password (for
+ * example because one was supplied on the
+ * command line which has already failed to
+ * work). Terminate.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(ssh,SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring(ssh, "No more passwords available"
+ " to try");
+ ssh2_pkt_addstring(ssh, "en"); /* language tag */
+ ssh2_pkt_send(ssh);
+ logevent("Unable to authenticate");
+ connection_fatal(ssh->frontend,
+ "Unable to authenticate");
+ ssh->state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+ } else {
+ int ret; /* need not be saved across crReturn */
+ c_write_untrusted(ssh, s->pwprompt, strlen(s->pwprompt));
+ ssh->send_ok = 1;
+
+ setup_userpass_input(ssh, s->password,
+ sizeof(s->password), s->echo);
+ do {
+ crWaitUntilV(!ispkt);
+ ret = process_userpass_input(ssh, in, inlen);
+ } while (ret == 0);
+ if (ret < 0)
+ cleanup_exit(0);
+ c_write_str(ssh, "\r\n");
+ }
+ }
+
+ if (s->method == AUTH_PUBLICKEY_FILE) {
+ /*
+ * We have our passphrase. Now try the actual authentication.
+ */
+ struct ssh2_userkey *key;
+
+ key = ssh2_load_userkey(&ssh->cfg.keyfile, s->password);
+ if (key == SSH2_WRONG_PASSPHRASE || key == NULL) {
+ if (key == SSH2_WRONG_PASSPHRASE) {
+ c_write_str(ssh, "Wrong passphrase\r\n");
+ s->tried_pubkey_config = FALSE;
+ } else {
+ c_write_str(ssh, "Unable to load private key\r\n");
+ s->tried_pubkey_config = TRUE;
+ }
+ /* Send a spurious AUTH_NONE to return to the top. */
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "none"); /* method */
+ ssh2_pkt_send(ssh);
+ s->type = AUTH_TYPE_NONE;
+ } else {
+ unsigned char *pkblob, *sigblob, *sigdata;
+ int pkblob_len, sigblob_len, sigdata_len;
+ int p;
+
+ /*
+ * We have loaded the private key and the server
+ * has announced that it's willing to accept it.
+ * Hallelujah. Generate a signature and send it.
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "publickey"); /* method */
+ ssh2_pkt_addbool(ssh, TRUE);
+ ssh2_pkt_addstring(ssh, key->alg->name);
+ pkblob = key->alg->public_blob(key->data, &pkblob_len);
+ ssh2_pkt_addstring_start(ssh);
+ ssh2_pkt_addstring_data(ssh, (char *)pkblob, pkblob_len);
+
+ /*
+ * The data to be signed is:
+ *
+ * string session-id
+ *
+ * followed by everything so far placed in the
+ * outgoing packet.
+ */
+ sigdata_len = ssh->pktout.length - 5 + 4 + 20;
+ if (ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)
+ sigdata_len -= 4;
+ sigdata = smalloc(sigdata_len);
+ p = 0;
+ if (!(ssh->remote_bugs & BUG_SSH2_PK_SESSIONID)) {
+ PUT_32BIT(sigdata+p, 20);
+ p += 4;
+ }
+ memcpy(sigdata+p, ssh->v2_session_id, 20); p += 20;
+ memcpy(sigdata+p, ssh->pktout.data + 5,
+ ssh->pktout.length - 5);
+ p += ssh->pktout.length - 5;
+ assert(p == sigdata_len);
+ sigblob = key->alg->sign(key->data, (char *)sigdata,
+ sigdata_len, &sigblob_len);
+ ssh2_add_sigblob(ssh, pkblob, pkblob_len,
+ sigblob, sigblob_len);
+ sfree(pkblob);
+ sfree(sigblob);
+ sfree(sigdata);
+
+ ssh2_pkt_send(ssh);
+ s->type = AUTH_TYPE_PUBLICKEY;
+ }
+ } else if (s->method == AUTH_PASSWORD) {
+ /*
+ * We send the password packet lumped tightly together with
+ * an SSH_MSG_IGNORE packet. The IGNORE packet contains a
+ * string long enough to make the total length of the two
+ * packets constant. This should ensure that a passive
+ * listener doing traffic analyis can't work out the length
+ * of the password.
+ *
+ * For this to work, we need an assumption about the
+ * maximum length of the password packet. I think 256 is
+ * pretty conservative. Anyone using a password longer than
+ * that probably doesn't have much to worry about from
+ * people who find out how long their password is!
+ */
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_REQUEST);
+ ssh2_pkt_addstring(ssh, s->username);
+ ssh2_pkt_addstring(ssh, "ssh-connection"); /* service requested */
+ ssh2_pkt_addstring(ssh, "password");
+ ssh2_pkt_addbool(ssh, FALSE);
+ ssh2_pkt_addstring(ssh, s->password);
+ memset(s->password, 0, sizeof(s->password));
+ ssh2_pkt_defer(ssh);
+ /*
+ * We'll include a string that's an exact multiple of the
+ * cipher block size. If the cipher is NULL for some
+ * reason, we don't do this trick at all because we gain
+ * nothing by it.
+ */
+ if (ssh->cscipher) {
+ int stringlen, i;
+
+ stringlen = (256 - ssh->deferred_len);
+ stringlen += ssh->cscipher->blksize - 1;
+ stringlen -= (stringlen % ssh->cscipher->blksize);
+ if (ssh->cscomp) {
+ /*
+ * Temporarily disable actual compression,
+ * so we can guarantee to get this string
+ * exactly the length we want it. The
+ * compression-disabling routine should
+ * return an integer indicating how many
+ * bytes we should adjust our string length
+ * by.
+ */
+ stringlen -=
+ ssh->cscomp->disable_compression(ssh->cs_comp_ctx);
+ }
+ ssh2_pkt_init(ssh, SSH2_MSG_IGNORE);
+ ssh2_pkt_addstring_start(ssh);
+ for (i = 0; i < stringlen; i++) {
+ char c = (char) random_byte();
+ ssh2_pkt_addstring_data(ssh, &c, 1);
+ }
+ ssh2_pkt_defer(ssh);
+ }
+ ssh_pkt_defersend(ssh);
+ logevent("Sent password");
+ s->type = AUTH_TYPE_PASSWORD;
+ } else if (s->method == AUTH_KEYBOARD_INTERACTIVE) {
+ if (s->curr_prompt == 0) {
+ ssh2_pkt_init(ssh, SSH2_MSG_USERAUTH_INFO_RESPONSE);
+ ssh2_pkt_adduint32(ssh, s->num_prompts);
+ }
+ if (s->need_pw) { /* only add pw if we just got one! */
+ ssh2_pkt_addstring(ssh, s->password);
+ memset(s->password, 0, sizeof(s->password));
+ s->curr_prompt++;
+ }
+ if (s->curr_prompt >= s->num_prompts) {
+ ssh2_pkt_send(ssh);
+ } else {
+ /*
+ * If there are prompts remaining, we set
+ * `gotit' so that we won't attempt to get
+ * another packet. Then we go back round the
+ * loop and will end up retrieving another
+ * prompt out of the existing packet. Funky or
+ * what?
+ */
+ s->gotit = TRUE;
+ }
+ s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
+ } else {
+ c_write_str(ssh, "No supported authentication methods"
+ " left to try!\r\n");
+ logevent("No supported authentications offered."
+ " Disconnecting");
+ ssh2_pkt_init(ssh, SSH2_MSG_DISCONNECT);
+ ssh2_pkt_adduint32(ssh, SSH2_DISCONNECT_BY_APPLICATION);
+ ssh2_pkt_addstring(ssh, "No supported authentication"
+ " methods available");
+ ssh2_pkt_addstring(ssh, "en"); /* language tag */
+ ssh2_pkt_send(ssh);
+ ssh->state = SSH_STATE_CLOSED;
+ crReturnV;
+ }
+ }
+ } while (!s->we_are_in);