X-Git-Url: https://git.distorted.org.uk/~mdw/sgt/putty/blobdiff_plain/edd0cb8aef57080ae884e06731a7892ca8cdba44..b8e3173d5169da33af5b41fa3b8e5b482bfdc6dc:/ssh.c?ds=sidebyside diff --git a/ssh.c b/ssh.c index 45ecf260..1e228446 100644 --- a/ssh.c +++ b/ssh.c @@ -3459,9 +3459,10 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, break; /* go and try something else */ } else if (ret == -1) { c_write_str(ssh, "Wrong passphrase.\r\n"); /* FIXME */ - s->tried_publickey = 0; got_passphrase = FALSE; /* and try again */ + } else { + assert(0 && "unexpected return from loadrsakey()"); } } @@ -3476,7 +3477,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, crWaitUntil(pktin); if (pktin->type == SSH1_SMSG_FAILURE) { c_write_str(ssh, "Server refused our public key.\r\n"); - continue; /* go and try password */ + continue; /* go and try something else */ } if (pktin->type != SSH1_SMSG_AUTH_RSA_CHALLENGE) { bombout(("Bizarre response to offer of public key")); @@ -3516,7 +3517,7 @@ static int do_ssh1_login(Ssh ssh, unsigned char *in, int inlen, if (flags & FLAG_VERBOSE) c_write_str(ssh, "Failed to authenticate with" " our public key.\r\n"); - continue; /* go and try password */ + continue; /* go and try something else */ } else if (pktin->type != SSH1_SMSG_SUCCESS) { bombout(("Bizarre response to RSA authentication response")); crStop(0); @@ -6474,11 +6475,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, { struct do_ssh2_authconn_state { enum { - AUTH_INVALID, AUTH_PUBLICKEY_AGENT, AUTH_PUBLICKEY_FILE, - AUTH_PASSWORD, - AUTH_KEYBOARD_INTERACTIVE - } method; - enum { AUTH_TYPE_NONE, AUTH_TYPE_PUBLICKEY, AUTH_TYPE_PUBLICKEY_OFFER_LOUD, @@ -6495,6 +6491,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, prompts_t *cur_prompt; int num_prompts; char username[100]; + char *password; int got_username; void *publickey_blob; int publickey_bloblen; @@ -6731,13 +6728,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, break; } - if (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { - /* FIXME: perhaps we should support this? */ - bombout(("PASSWD_CHANGEREQ not yet supported")); - crStopV; - } else if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { - bombout(("Strange packet received during authentication: type %d", - pktin->type)); + if (pktin->type != SSH2_MSG_USERAUTH_FAILURE) { + bombout(("Strange packet received during authentication: " + "type %d", pktin->type)); crStopV; } @@ -7016,7 +7009,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, s->type = AUTH_TYPE_PUBLICKEY_OFFER_LOUD; continue; /* process this new message */ } - s->method = AUTH_PUBLICKEY_FILE; logevent("Offer of public key accepted"); /* @@ -7161,11 +7153,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, /* * Keyboard-interactive authentication. */ - char *name, *inst, *lang; - int name_len, inst_len, lang_len; - int i; - s->method = AUTH_KEYBOARD_INTERACTIVE; s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; @@ -7184,7 +7172,9 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, crWaitUntilV(pktin); if (pktin->type != SSH2_MSG_USERAUTH_INFO_REQUEST) { /* Server is not willing to do keyboard-interactive - * at all. Give up on it entirely. */ + * at all (or, bizarrely but legally, accepts the + * user without actually issuing any prompts). + * Give up on it entirely. */ s->gotit = TRUE; if (pktin->type == SSH2_MSG_USERAUTH_FAILURE) logevent("Keyboard-interactive authentication refused"); @@ -7194,89 +7184,113 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, } /* - * We've got a fresh USERAUTH_INFO_REQUEST. - * Get the preamble and start building a prompt. + * Loop while the server continues to send INFO_REQUESTs. */ - ssh_pkt_getstring(pktin, &name, &name_len); - ssh_pkt_getstring(pktin, &inst, &inst_len); - ssh_pkt_getstring(pktin, &lang, &lang_len); - s->cur_prompt = new_prompts(ssh->frontend); - s->cur_prompt->to_server = TRUE; - if (name_len) { - /* FIXME: better prefix to distinguish from - * local prompts? */ - s->cur_prompt->name = dupprintf("SSH server: %.*s", - name_len, name); - s->cur_prompt->name_reqd = TRUE; - } else { - s->cur_prompt->name = dupstr("SSH server authentication"); - s->cur_prompt->name_reqd = FALSE; - } - s->cur_prompt->instruction = - dupprintf("Using keyboard-interactive authentication.%s%.*s", - inst_len ? "\n" : "", inst_len, inst); - s->cur_prompt->instr_reqd = TRUE; + while (pktin->type == SSH2_MSG_USERAUTH_INFO_REQUEST) { - /* - * Get the prompts from the packet. - */ - s->num_prompts = ssh_pkt_getuint32(pktin); - for (i = 0; i < s->num_prompts; i++) { - char *prompt; - int prompt_len; - int echo; - static char noprompt[] = - ": "; + char *name, *inst, *lang; + int name_len, inst_len, lang_len; + int i; - ssh_pkt_getstring(pktin, &prompt, &prompt_len); - echo = ssh2_pkt_getbool(pktin); - if (!prompt_len) { - prompt = noprompt; - prompt_len = lenof(noprompt)-1; + /* + * We've got a fresh USERAUTH_INFO_REQUEST. + * Get the preamble and start building a prompt. + */ + ssh_pkt_getstring(pktin, &name, &name_len); + ssh_pkt_getstring(pktin, &inst, &inst_len); + ssh_pkt_getstring(pktin, &lang, &lang_len); + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + if (name_len) { + /* FIXME: better prefix to distinguish from + * local prompts? */ + s->cur_prompt->name = + dupprintf("SSH server: %.*s", name_len, name); + s->cur_prompt->name_reqd = TRUE; + } else { + s->cur_prompt->name = + dupstr("SSH server authentication"); + s->cur_prompt->name_reqd = FALSE; } - add_prompt(s->cur_prompt, - dupprintf("%.*s", prompt_len, prompt), - echo, SSH_MAX_PASSWORD_LEN); - } + /* FIXME: ugly to print "Using..." in prompt _every_ + * time round. Can this be done more subtly? */ + s->cur_prompt->instruction = + dupprintf("Using keyboard-interactive authentication.%s%.*s", + inst_len ? "\n" : "", inst_len, inst); + s->cur_prompt->instr_reqd = TRUE; - /* - * Get the user's responses. - */ - if (s->num_prompts) { - int ret; /* not live over crReturn */ - ret = get_userpass_input(s->cur_prompt, NULL, 0); - while (ret < 0) { - ssh->send_ok = 1; - crWaitUntilV(!pktin); - ret = get_userpass_input(s->cur_prompt, in, inlen); - ssh->send_ok = 0; + /* + * Get the prompts from the packet. + */ + s->num_prompts = ssh_pkt_getuint32(pktin); + for (i = 0; i < s->num_prompts; i++) { + char *prompt; + int prompt_len; + int echo; + static char noprompt[] = + ": "; + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + echo = ssh2_pkt_getbool(pktin); + if (!prompt_len) { + prompt = noprompt; + prompt_len = lenof(noprompt)-1; + } + add_prompt(s->cur_prompt, + dupprintf("%.*s", prompt_len, prompt), + echo, SSH_MAX_PASSWORD_LEN); } - if (!ret) { - /* - * Failed to get responses. Terminate. - */ - free_prompts(s->cur_prompt); - ssh_disconnect(ssh, NULL, "Unable to authenticate", - SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, - TRUE); - crStopV; + + /* + * Get the user's responses. + */ + if (s->num_prompts) { + int ret; /* not live over crReturn */ + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + free_prompts(s->cur_prompt); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + } + + /* + * Send the responses to the server. + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); + s->pktout->forcepad = 256; + ssh2_pkt_adduint32(s->pktout, s->num_prompts); + for (i=0; i < s->num_prompts; i++) { + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[i]->result); + end_log_omission(ssh, s->pktout); } + ssh2_pkt_send(ssh, s->pktout); + + /* + * Get the next packet in case it's another + * INFO_REQUEST. + */ + crWaitUntilV(pktin); + } /* - * Send the responses to the server. + * We should have SUCCESS or FAILURE now. */ - s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_INFO_RESPONSE); - s->pktout->forcepad = 256; - ssh2_pkt_adduint32(s->pktout, s->num_prompts); - for (i=0; i < s->num_prompts; i++) { - dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[i]->result); - end_log_omission(ssh, s->pktout); - } - ssh2_pkt_send(ssh, s->pktout); - s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/ + s->gotit = TRUE; } else if (s->can_passwd) { @@ -7284,8 +7298,8 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, * Plain old password authentication. */ int ret; /* not live over crReturn */ + int changereq_first_time; /* not live over crReturn */ - s->method = AUTH_PASSWORD; ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK; ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD; @@ -7314,6 +7328,12 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, TRUE); crStopV; } + /* + * Squirrel away the password. (We may need it later if + * asked to change it.) + */ + s->password = dupstr(s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); /* * Send the password packet. @@ -7334,13 +7354,145 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen, ssh2_pkt_addstring(s->pktout, "password"); ssh2_pkt_addbool(s->pktout, FALSE); dont_log_password(ssh, s->pktout, PKTLOG_BLANK); - ssh2_pkt_addstring(s->pktout, - s->cur_prompt->prompts[0]->result); - free_prompts(s->cur_prompt); + ssh2_pkt_addstring(s->pktout, s->password); end_log_omission(ssh, s->pktout); ssh2_pkt_send(ssh, s->pktout); logevent("Sent password"); - s->type = AUTH_TYPE_PASSWORD; /*FIXME?*/ + s->type = AUTH_TYPE_PASSWORD; + + /* + * Wait for next packet, in case it's a password change + * request. + */ + crWaitUntilV(pktin); + changereq_first_time = TRUE; + + while (pktin->type == SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ) { + + /* + * We're being asked for a new password + * (perhaps not for the first time). + * Loop until the server accepts it. + */ + + int got_new = FALSE; /* not live over crReturn */ + char *prompt; /* not live over crReturn */ + int prompt_len; /* not live over crReturn */ + + { + char *msg; + if (changereq_first_time) + msg = "Server requested password change"; + else + msg = "Server rejected new password"; + logevent(msg); + c_write_str(ssh, msg); + c_write_str(ssh, "\r\n"); + } + + ssh_pkt_getstring(pktin, &prompt, &prompt_len); + + s->cur_prompt = new_prompts(ssh->frontend); + s->cur_prompt->to_server = TRUE; + s->cur_prompt->name = dupstr("New SSH password"); + s->cur_prompt->instruction = + dupprintf("%.*s", prompt_len, prompt); + s->cur_prompt->instr_reqd = TRUE; + add_prompt(s->cur_prompt, dupstr("Enter new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + add_prompt(s->cur_prompt, dupstr("Confirm new password: "), + FALSE, SSH_MAX_PASSWORD_LEN); + + /* + * Loop until the user manages to enter the same + * password twice. + */ + while (!got_new) { + + ret = get_userpass_input(s->cur_prompt, NULL, 0); + while (ret < 0) { + ssh->send_ok = 1; + crWaitUntilV(!pktin); + ret = get_userpass_input(s->cur_prompt, in, inlen); + ssh->send_ok = 0; + } + if (!ret) { + /* + * Failed to get responses. Terminate. + */ + /* burn the evidence */ + free_prompts(s->cur_prompt); + memset(s->password, 0, strlen(s->password)); + sfree(s->password); + ssh_disconnect(ssh, NULL, "Unable to authenticate", + SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER, + TRUE); + crStopV; + } + + /* + * Check the two passwords match. + */ + got_new = (strcmp(s->cur_prompt->prompts[0]->result, + s->cur_prompt->prompts[1]->result) + == 0); + if (!got_new) + /* They don't. Silly user. */ + c_write_str(ssh, "Passwords do not match\r\n"); + + } + + /* + * Send the new password (along with the old one). + * (see above for padding rationale) + */ + s->pktout = ssh2_pkt_init(SSH2_MSG_USERAUTH_REQUEST); + s->pktout->forcepad = 256; + ssh2_pkt_addstring(s->pktout, s->username); + ssh2_pkt_addstring(s->pktout, "ssh-connection"); + /* service requested */ + ssh2_pkt_addstring(s->pktout, "password"); + ssh2_pkt_addbool(s->pktout, TRUE); + dont_log_password(ssh, s->pktout, PKTLOG_BLANK); + ssh2_pkt_addstring(s->pktout, s->password); + ssh2_pkt_addstring(s->pktout, + s->cur_prompt->prompts[0]->result); + free_prompts(s->cur_prompt); + end_log_omission(ssh, s->pktout); + ssh2_pkt_send(ssh, s->pktout); + logevent("Sent new password"); + + /* + * Now see what the server has to say about it. + * (If it's CHANGEREQ again, it's not happy with the + * new password.) + */ + crWaitUntilV(pktin); + changereq_first_time = FALSE; + + } + + /* + * We need to reexamine the current pktin at the top + * of the loop. Either: + * - we weren't asked to change password at all, in + * which case it's a SUCCESS or FAILURE with the + * usual meaning + * - we sent a new password, and the server was + * either OK with it (SUCCESS or FAILURE w/partial + * success) or unhappy with the _old_ password + * (FAILURE w/o partial success) + * In any of these cases, we go back to the top of + * the loop and start again. + */ + s->gotit = TRUE; + + /* + * We don't need the old password any more, in any + * case. Burn the evidence. + */ + memset(s->password, 0, strlen(s->password)); + sfree(s->password); } else {