From: Joseph Reynolds <joseph-reyno...@charter.net>

This implements the SSH server Linux PAM expired password dialog.
That is, when Linux PAM is used for authentication, and a RFC 4252
SSH_MSG_USERAUTH_REQUEST comes in with a request to change the password,
this code will call pam_chauthtok to perform the password change request.

This is a re-work of a patch to support handling/changing expired
password, found in the dropbear email list:
https://lists.ucc.gu.uwa.edu.au/pipermail/dropbear/2016q2/001895.html
The patch was modified to work with DROPBEAR_2019.78.

This may require PAM config file changes.  The dropbear SSH server identifies
itself to PAM (via pam_start) as "sshd".  So PAM config files must have an
entry for the "sshd" service "password" type, for example:
  sshd password include common-password

tested: yes
Via the OpenSSH client (ssh -v: OpenSSH_7.8p1, OpenSSL 1.0.1u  22 Sep 2016)
Test scenario:
  As the root user on the device which implements the dropbear SSH server:
    useradd testuser
    passwd testuser
      TESTPASSWORD1=TestP4ssw0rd
      TESTPASSWORD2=N3wTestPswd
      TESTPASSWORD3=Th1rdB4dPswd
    passwd --expire testuser
  From a test host (various ssh clients):
    ssh testuser@$ip "echo GOT HERE"
    Use various combinations of good and bad passwords from the initial
    prompt, from the password change prompt (for old and new).
  The ssh prompts and responses were all reasonable, and when I successfully
  changed the password, was able to access the device's shell.

Signed-off-by: Joseph Reynolds <joseph-reyno...@charter.net>
---
 auth.h        |  1 +
 svr-auth.c    | 22 ++++++++++++++++++++++
 svr-authpam.c | 57 +++++++++++++++++++++++++++++++++++++++------------------
 3 files changed, 62 insertions(+), 18 deletions(-)

diff --git a/auth.h b/auth.h
index 4bdb359..b17d26b 100644
--- a/auth.h
+++ b/auth.h
@@ -36,6 +36,7 @@ void cli_authinitialise(void);
 void recv_msg_userauth_request(void);
 void send_msg_userauth_failure(int partial, int incrfail);
 void send_msg_userauth_success(void);
+void send_msg_userauth_chauthtok(void);
 void send_msg_userauth_banner(const buffer *msg);
 void svr_auth_password(int valid_user);
 void svr_auth_pubkey(int valid_user);
diff --git a/svr-auth.c b/svr-auth.c
index 7575f90..7fd641e 100644
--- a/svr-auth.c
+++ b/svr-auth.c
@@ -475,3 +475,25 @@ void send_msg_userauth_success() {
        TRACE(("leave send_msg_userauth_success"))
 
 }
+
+
+/* Send change password */
+void send_msg_userauth_chauthtok() {
+#ifdef ENABLE_SVR_PAM_AUTH
+       const char * msg = "";
+#else
+       const char * msg = "The password has expired.  Please change it now.";
+#endif
+
+       TRACE(("enter send_msg_userauth_chauthtok"))
+
+       CHECKCLEARTOWRITE();
+
+       buf_putbyte(ses.writepayload, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ);
+       buf_putstring(ses.writepayload, msg, strlen(msg));
+       buf_putstring(ses.writepayload, "en", 2);
+
+       encrypt_packet();
+
+       TRACE(("leave send_msg_userauth_chauthtok"))
+}
diff --git a/svr-authpam.c b/svr-authpam.c
index e236db4..6361f3c 100644
--- a/svr-authpam.c
+++ b/svr-authpam.c
@@ -42,6 +42,7 @@
 struct UserDataS {
        char* user;
        char* passwd;
+        char* new_passwd;
 };
 
 /* PAM conversation function - for now we only handle one message */
@@ -89,7 +90,7 @@ pamConvFunc(int num_msg,
 
                case PAM_PROMPT_ECHO_OFF:
 
-                       if (!(strcmp(compare_message, "password:") == 0)) {
+                    if (strstr(compare_message, "password:") == NULL) {
                                /* We don't recognise the prompt as asking for 
a password,
                                   so can't handle it. Add more above as 
required for
                                   different pam modules/implementations. If 
you need
@@ -107,8 +108,10 @@ pamConvFunc(int num_msg,
                        resp = (struct pam_response*) m_malloc(sizeof(struct 
pam_response));
                        memset(resp, 0, sizeof(struct pam_response));
 
-                       resp->resp = m_strdup(userDatap->passwd);
-                       m_burn(userDatap->passwd, strlen(userDatap->passwd));
+                        if (strstr(compare_message, "new"))
+                            resp->resp = m_strdup(userDatap->new_passwd);
+                        else
+                            resp->resp = m_strdup(userDatap->passwd);
                        (*respp) = resp;
                        break;
 
@@ -138,7 +141,6 @@ pamConvFunc(int num_msg,
                        memset(resp, 0, sizeof(struct pam_response));
 
                        resp->resp = m_strdup(userDatap->user);
-                       TRACE(("userDatap->user='%s'", userDatap->user))
                        (*respp) = resp;
                        break;
 
@@ -180,7 +182,7 @@ pamConvFunc(int num_msg,
  * interactive responses, over the network. */
 void svr_auth_pam(int valid_user) {
 
-       struct UserDataS userData = {NULL, NULL};
+        struct UserDataS userData = {NULL, NULL, NULL};
        struct pam_conv pamConv = {
                pamConvFunc,
                &userData /* submitted to pamvConvFunc as appdata_ptr */ 
@@ -190,20 +192,17 @@ void svr_auth_pam(int valid_user) {
        pam_handle_t* pamHandlep = NULL;
 
        char * password = NULL;
+       char * new_password = NULL;
        unsigned int passwordlen;
 
        int rc = PAM_SUCCESS;
-       unsigned char changepw;
+       unsigned char client_request_changepw;
 
        /* check if client wants to change password */
-       changepw = buf_getbool(ses.payload);
-       if (changepw) {
-               /* not implemented by this server */
-               send_msg_userauth_failure(0, 1);
-               goto cleanup;
-       }
-
+       client_request_changepw = buf_getbool(ses.payload);
        password = buf_getstring(ses.payload, &passwordlen);
+        if (client_request_changepw)
+           new_password = buf_getstring(ses.payload, NULL);
 
        /* We run the PAM conversation regardless of whether the username is 
valid
        in case the conversation function has an inherent delay.
@@ -215,6 +214,7 @@ void svr_auth_pam(int valid_user) {
         * function (above) which takes care of it */
        userData.user = ses.authstate.username;
        userData.passwd = password;
+       userData.new_passwd = new_password;
 
        if (ses.authstate.pw_name) {
                printable_user = ses.authstate.pw_name;
@@ -260,9 +260,23 @@ void svr_auth_pam(int valid_user) {
                goto cleanup;
        }
 
-       if ((rc = pam_acct_mgmt(pamHandlep, 0)) != PAM_SUCCESS) {
-               dropbear_log(LOG_WARNING, "pam_acct_mgmt() failed, rc=%d, %s", 
-                               rc, pam_strerror(pamHandlep, rc));
+       if (client_request_changepw) {
+           rc = pam_chauthtok(pamHandlep, 0);
+           if (rc != PAM_SUCCESS) {
+               dropbear_log(LOG_WARNING, "pam_chauthtok() failed, rc=%d, %s",
+                            rc, pam_strerror(pamHandlep, rc));
+               dropbear_log(LOG_WARNING,
+                            "Bad PAM password changing attempt for '%s' from 
%s",
+                            printable_user,
+                            svr_ses.addrstring);
+               send_msg_userauth_failure(0, 1);
+               goto cleanup;
+           }
+       }
+        rc = pam_acct_mgmt(pamHandlep, 0);
+        if (!(rc == PAM_SUCCESS || rc == PAM_NEW_AUTHTOK_REQD)) {
+                dropbear_log(LOG_WARNING, "pam_acct_mgmt() failed, rc=%d, %s", 
+                               rc, pam_strerror(pamHandlep, rc));
                dropbear_log(LOG_WARNING,
                                "Bad PAM password attempt for '%s' from %s",
                                printable_user,
@@ -282,12 +296,19 @@ void svr_auth_pam(int valid_user) {
        dropbear_log(LOG_NOTICE, "PAM password auth succeeded for '%s' from %s",
                        ses.authstate.pw_name,
                        svr_ses.addrstring);
-       send_msg_userauth_success();
+        if (rc == PAM_NEW_AUTHTOK_REQD)
+            send_msg_userauth_chauthtok();
+        else
+            send_msg_userauth_success();
 
 cleanup:
        if (password != NULL) {
-               m_burn(password, passwordlen);
+                m_burn(password, strlen(password));
                m_free(password);
+                if (new_password) {
+                    m_burn(new_password, strlen(new_password));
+                    m_free(new_password);
+                }
        }
        if (pamHandlep != NULL) {
                TRACE(("pam_end"))
-- 
1.8.3.1

Reply via email to