>From 70de230740844ead0a68f31dbff095bbcfe75aa2 Mon Sep 17 00:00:00 2001
From: Pavel Reichl <preichl@redhat.com>
Date: Wed, 11 Feb 2015 19:38:16 -0500
Subject: [PATCH 1/3] PAM: do not reject abruptly

If account has expired then pass message.

Resolves:
https://fedorahosted.org/sssd/ticket/2050
---
 src/responder/pam/pamsrv_cmd.c | 53 ++++++++++++++++++++++++++++++++++
 src/sss_client/pam_sss.c       | 64 +++++++++++++++++++++++++++++++++++++++++-
 src/sss_client/sss_cli.h       | 18 +++++++++---
 3 files changed, 130 insertions(+), 5 deletions(-)

diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index 90cdbec519587a0d5dd680bfe3a991d896d6c008..c874cae61960ffa17dbe8aab7b96b792d65ac618 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -44,6 +44,54 @@ enum pam_verbosity {
 
 static void pam_reply(struct pam_auth_req *preq);
 
+static errno_t pack_user_info_account_expired(TALLOC_CTX *mem_ctx,
+                                              const char *user_error_message,
+                                              size_t *resp_len,
+                                              uint8_t **_resp)
+{
+    uint32_t resp_type = SSS_PAM_USER_INFO_ACCOUNT_EXPIRED;
+    size_t err_len;
+    uint8_t *resp;
+    size_t p;
+
+    err_len = strlen(user_error_message);
+    *resp_len = 2 * sizeof(uint32_t) + err_len;
+    resp = talloc_size(mem_ctx, *resp_len);
+    if (resp == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_size failed.\n");
+        return ENOMEM;
+    }
+
+    p = 0;
+    SAFEALIGN_SET_UINT32(&resp[p], resp_type, &p);
+    SAFEALIGN_SET_UINT32(&resp[p], err_len, &p);
+    safealign_memcpy(&resp[p], user_error_message, err_len, &p);
+    if (p != *resp_len) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Size mismatch\n");
+    }
+
+    *_resp = resp;
+    return EOK;
+}
+
+static void inform_account_expired(struct pam_data* pd)
+{
+    size_t msg_len;
+    uint8_t *msg;
+    errno_t ret;
+
+    ret = pack_user_info_account_expired(pd, "", &msg_len, &msg);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "pack_user_info_account_expired failed.\n");
+    } else {
+        ret = pam_add_response(pd, SSS_PAM_USER_INFO, msg_len, msg);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+        }
+    }
+}
+
 static bool is_domain_requested(struct pam_data *pd, const char *domain_name)
 {
     int i;
@@ -609,6 +657,11 @@ static void pam_reply(struct pam_auth_req *preq)
         goto done;
     }
 
+    if (pd->pam_status == PAM_ACCT_EXPIRED && pd->service != NULL &&
+        strcasecmp(pd->service, "sshd") == 0) {
+        inform_account_expired(pd);
+    }
+
     ret = filter_responses(pctx->rctx->cdb, pd->resp_list);
     if (ret != EOK) {
         DEBUG(SSSDBG_CRIT_FAILURE, "filter_responses failed, not fatal.\n");
diff --git a/src/sss_client/pam_sss.c b/src/sss_client/pam_sss.c
index fdf6c9e6da75c9f7eaa7c00d9a5792fbdd97eabc..59529796c682416d49c7f92f5feea3b0ace8d2d4 100644
--- a/src/sss_client/pam_sss.c
+++ b/src/sss_client/pam_sss.c
@@ -60,6 +60,9 @@
 #define OPT_RETRY_KEY "retry="
 #define OPT_DOMAINS_KEY "domains="
 
+#define EXP_ACC_MSG _("Your account has expired. ")
+#define SRV_MSG     _("Server message: ")
+
 struct pam_items {
     const char* pam_service;
     const char* pam_user;
@@ -797,6 +800,63 @@ static int user_info_otp_chpass(pam_handle_t *pamh)
     return PAM_SUCCESS;
 }
 
+static int user_info_account_expired(pam_handle_t *pamh, size_t buflen,
+                                     uint8_t *buf)
+{
+    int ret;
+    uint32_t msg_len;
+    char *user_msg;
+    size_t bufsize = 0;
+
+    /* resp_type and length of message are expected to be in buf */
+    if (buflen < 2* sizeof(uint32_t)) {
+        D(("User info response data is too short"));
+        return PAM_BUF_ERR;
+    }
+
+    /* msg_len = legth of message */
+    memcpy(&msg_len, buf + sizeof(uint32_t), sizeof(uint32_t));
+
+    if (buflen != 2* sizeof(uint32_t) + msg_len) {
+        D(("User info response data has the wrong size"));
+        return PAM_BUF_ERR;
+    }
+
+    bufsize = strlen(EXP_ACC_MSG) + 1;
+
+    if (msg_len > 0) {
+        bufsize += strlen(SRV_MSG) + msg_len;
+    }
+
+    user_msg = (char *)malloc(sizeof(char) * bufsize);
+    if (!user_msg) {
+       D(("Out of memory."));
+       return PAM_SYSTEM_ERR;
+    }
+
+    ret = snprintf(user_msg, bufsize, "%s%s%.*s",
+                   EXP_ACC_MSG,
+                   msg_len > 0 ? SRV_MSG : "",
+                   msg_len,
+                   msg_len > 0 ? (char *)(buf + 2 * sizeof(uint32_t)) : "" );
+    if (ret < 0 || ret > bufsize) {
+        D(("snprintf failed."));
+
+        free(user_msg);
+        return PAM_SYSTEM_ERR;
+    }
+
+    ret = do_pam_conversation(pamh, PAM_TEXT_INFO, user_msg, NULL, NULL);
+    free(user_msg);
+    if (ret != PAM_SUCCESS) {
+        D(("do_pam_conversation failed."));
+
+        return PAM_SYSTEM_ERR;
+    }
+
+    return PAM_SUCCESS;
+}
+
 static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen,
                                   uint8_t *buf)
 {
@@ -852,7 +912,6 @@ static int user_info_chpass_error(pam_handle_t *pamh, size_t buflen,
     return PAM_SUCCESS;
 }
 
-
 static int eval_user_info_response(pam_handle_t *pamh, size_t buflen,
                                    uint8_t *buf)
 {
@@ -888,6 +947,9 @@ static int eval_user_info_response(pam_handle_t *pamh, size_t buflen,
         case SSS_PAM_USER_INFO_CHPASS_ERROR:
             ret = user_info_chpass_error(pamh, buflen, buf);
             break;
+        case SSS_PAM_USER_INFO_ACCOUNT_EXPIRED:
+            ret = user_info_account_expired(pamh, buflen, buf);
+            break;
         default:
             D(("Unknown user info type [%d]", type));
             ret = PAM_SYSTEM_ERR;
diff --git a/src/sss_client/sss_cli.h b/src/sss_client/sss_cli.h
index 6286077fcf25aead1dfcba5c6483e4ff8ae63b9f..d508a0671cd1b3ee087e0967f7015628ceabe20f 100644
--- a/src/sss_client/sss_cli.h
+++ b/src/sss_client/sss_cli.h
@@ -461,15 +461,25 @@ enum user_info_type {
                                      * indicates that no message is following.
                                      * @param String with the specified
                                      * length. */
+
     SSS_PAM_USER_INFO_GRACE_LOGIN, /**< Warn the user that the password is
                                     * expired and inform about the remaining
                                     * number of grace logins.
                                     * @param The number of remaining grace
                                     * logins as uint32_t */
-    SSS_PAM_USER_INFO_EXPIRE_WARN /**< Warn the user that the password will
-                                   * expire soon.
-                                   * @param Number of seconds before the user's
-                                   * password will expire. */
+    SSS_PAM_USER_INFO_EXPIRE_WARN, /**< Warn the user that the password will
+                                    * expire soon.
+                                    * @param Number of seconds before the
+                                    * user's password will expire. */
+
+    SSS_PAM_USER_INFO_ACCOUNT_EXPIRED, /**< Tell the user that the account
+                                        * has expired and optionally give
+                                        * a reason.
+                                        * @param Size of the message as
+                                        * unsigned 32-bit integer value. A
+                                        * value of 0 indicates that no message
+                                        * is following. @param String with the
+                                        * specified length. */
 };
 /**
  * @}
-- 
2.1.0

