Fix for `ssh2-password-expiry'. Success case tested.
[u/mdw/putty] / ssh.c
diff --git a/ssh.c b/ssh.c
index 45ecf26..7936fd0 100644 (file)
--- a/ssh.c
+++ b/ssh.c
@@ -6474,11 +6474,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 +6490,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 +6727,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 +7008,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");
 
                /*
@@ -7165,7 +7156,6 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                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;
@@ -7276,7 +7266,7 @@ static void do_ssh2_authconn(Ssh ssh, unsigned char *in, int inlen,
                    end_log_omission(ssh, s->pktout);
                }
                ssh2_pkt_send(ssh, s->pktout);
-               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE; /*FIXME?*/
+               s->type = AUTH_TYPE_KEYBOARD_INTERACTIVE;
 
            } else if (s->can_passwd) {
 
@@ -7284,8 +7274,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 +7304,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 +7330,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 {