Oops, used \I where I meant \i. I think this is the only instance.
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index cad0457..1e22844 100644 (file)
--- 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);
@@ -6490,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;
@@ -6726,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;
            }
 
@@ -7155,9 +7153,6 @@ 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->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
 
@@ -7177,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");
@@ -7187,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[] =
-                       "<server failed to send prompt>: ";
+                   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[] =
+                           "<server failed to send prompt>: ";
+
+                       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;
+               s->gotit = TRUE;
 
            } else if (s->can_passwd) {
 
@@ -7277,6 +7298,7 @@ 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 */
 
                ssh->pkt_ctx &= ~SSH2_PKTCTX_AUTH_MASK;
                ssh->pkt_ctx |= SSH2_PKTCTX_PASSWORD;
@@ -7306,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.
@@ -7326,14 +7354,146 @@ 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;
 
+               /*
+                * 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 {
 
                ssh_disconnect(ssh, NULL,