From 75b09812bd47f7820513f37297b14c096c03d243 Mon Sep 17 00:00:00 2001
From: Jan Zeleny <jzeleny@redhat.com>
Date: Tue, 1 May 2012 03:36:37 -0400
Subject: [PATCH] Modify behavior of pam_pwd_expiration_warning

New option pwd_expiration_warning is introduced which can be set per
domain and can override the value specified by the original
pam_pwd_expiration_warning.

If the value of expiration warning is set to zero, the filter isn't
apllied at all - if backend server returns the warning, it will be
automatically displayed.

Default value for Kerberos: 7 days
Default value for LDAP: don't apply the filter

Technical note: default value when creating the domain is -1. This is
important so we can distinguish between "no value set" and 0. Without
this possibility it would be impossible to set different values for LDAP
and Kerberos provider.
---
 src/confdb/confdb.c            |   18 +++++++++++++++++
 src/confdb/confdb.h            |    3 ++
 src/config/etc/sssd.api.conf   |    1 +
 src/man/sssd.conf.5.xml        |   35 ++++++++++++++++++++++++++++++++-
 src/providers/krb5/krb5_auth.c |   31 +++++++++++++++++++++++++---
 src/providers/ldap/ldap_auth.c |   42 ++++++++++++++++++++++++++++-----------
 src/responder/pam/pamsrv_cmd.c |   35 ---------------------------------
 src/util/domain_info_utils.c   |    1 +
 src/util/sss_krb5.h            |    5 ++++
 9 files changed, 119 insertions(+), 52 deletions(-)

diff --git a/src/confdb/confdb.c b/src/confdb/confdb.c
index d938d4f61741768ffed1027be07031ae34d05e0c..427101e9b841af7f93eb5084bead7ba15b1d4503 100644
--- a/src/confdb/confdb.c
+++ b/src/confdb/confdb.c
@@ -922,6 +922,24 @@ static int confdb_get_domain_internal(struct confdb_ctx *cdb,
         goto done;
     }
 
+    /* Set the PAM warning time, if specified */
+    val = ldb_msg_find_attr_as_int(res->msgs[0],
+                                   CONFDB_DOMAIN_PWD_EXPIRATION_WARNING,
+                                   -1);
+    if (val > 0) {
+        /* The value is in days, transform it to seconds */
+        val *= 24 * 3600;
+    } else {
+        ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
+                             CONFDB_PAM_PWD_EXPIRATION_WARNING,
+                             -1, &val);
+        if (ret != EOK) {
+            DEBUG(1, ("Failed to read PAM expiration warning, not fatal.\n"));
+            val = -1;
+        }
+    }
+    domain->pwd_expiration_warning = val;
+
     ret = get_entry_as_uint32(res->msgs[0], &domain->override_gid,
                               CONFDB_DOMAIN_OVERRIDE_GID, 0);
     if (ret != EOK) {
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index b90db303ace6f9787586061c8d06a67affd1f435..5ec9f721cc8aec1c940f9daa6cc0f484e203612c 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -152,6 +152,7 @@
 #define CONFDB_DOMAIN_NETGROUP_CACHE_TIMEOUT "entry_cache_netgroup_timeout"
 #define CONFDB_DOMAIN_SERVICE_CACHE_TIMEOUT "entry_cache_service_timeout"
 #define CONFDB_DOMAIN_AUTOFS_CACHE_TIMEOUT "entry_cache_autofs_timeout"
+#define CONFDB_DOMAIN_PWD_EXPIRATION_WARNING "pwd_expiration_warning"
 
 /* Local Provider */
 #define CONFDB_LOCAL_DEFAULT_SHELL   "default_shell"
@@ -199,6 +200,8 @@ struct sss_domain_info {
     uint32_t service_timeout;
     uint32_t autofsmap_timeout;
 
+    int pwd_expiration_warning;
+
     struct sysdb_ctx *sysdb;
 
     struct sss_domain_info **subdomains;
diff --git a/src/config/etc/sssd.api.conf b/src/config/etc/sssd.api.conf
index a7bece99a8df7193d9ea6fe4ed5303805febb73e..a5fdbffb6d552c2850932544d420574e82042f95 100644
--- a/src/config/etc/sssd.api.conf
+++ b/src/config/etc/sssd.api.conf
@@ -89,6 +89,7 @@ use_fully_qualified_names = bool, None, false
 entry_cache_timeout = int, None, false
 lookup_family_order = str, None, false
 account_cache_expiration = int, None, false
+pwd_expiration_warning = int, None, false
 filter_users = list, str, false
 filter_groups = list, str, false
 dns_resolver_timeout = int, None, false
diff --git a/src/man/sssd.conf.5.xml b/src/man/sssd.conf.5.xml
index ef7490d946c07e83d80b32ab49eb67b3e911acbc..a7691edcc3858e99444d9adec2ea53cfd73297b6 100644
--- a/src/man/sssd.conf.5.xml
+++ b/src/man/sssd.conf.5.xml
@@ -632,7 +632,17 @@
                       warning.
                     </para>
                     <para>
-                      Default: 7
+                      If zero is set, then this filter is not applied,
+                      i.e. if the expiration warning was received from
+                      backend server, it will automatically be displayed.
+                    </para>
+                    <para>
+                      This setting can be overridden by setting
+                      <emphasis>pwd_expiration_warning</emphasis>
+                      for a particular domain.
+                    </para>
+                    <para>
+                      Default: 0
                     </para>
                   </listitem>
                 </varlistentry>
@@ -922,6 +932,29 @@
                         </para>
                     </listitem>
                 </varlistentry>
+                <varlistentry>
+                  <term>pwd_expiration_warning (integer)</term>
+                  <listitem>
+                    <para>
+                      Display a warning N days before the password expires.
+                    </para>
+                    <para>
+                        If zero is set, then this filter is not applied,
+                        i.e. if the expiration warning was received from
+                        backend server, it will automatically be displayed.
+                    </para>
+                    <para>
+                      Please note that the backend server has to provide
+                      information about the expiration time of the password.
+                      If this information is missing, sssd cannot display a
+                      warning. Also an auth provider has to be configured for
+                      the backend.
+                    </para>
+                    <para>
+                      Default: 7 (Kerberos), 0 (LDAP)
+                    </para>
+                  </listitem>
+                </varlistentry>
 
                 <varlistentry>
                     <term>id_provider (string)</term>
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 0306426ccac564f77d6f592fee1fe32462ad48dc..986e449fd549c25ee2f679857d60593134e76118 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -734,8 +734,16 @@ static void krb5_child_done(struct tevent_req *subreq)
     int32_t msg_len;
     int64_t time_data;
     struct tgt_times tgtt;
+    int pwd_exp_warning;
+    uint32_t *expiration;
+    uint32_t *msg_subtype;
+    bool skip;
 
     memset(&tgtt, 0, sizeof(tgtt));
+    pwd_exp_warning = state->be_ctx->domain->pwd_expiration_warning;
+    if (pwd_exp_warning < 0) {
+        pwd_exp_warning = KERBEROS_PWEXPIRE_WARNING_TIME;
+    }
 
     ret = handle_child_recv(subreq, pd, &buf, &len);
     talloc_zfree(subreq);
@@ -771,6 +779,7 @@ static void krb5_child_done(struct tevent_req *subreq)
     SAFEALIGN_COPY_INT32(&msg_status, buf+p, &p);
 
     while (p < len) {
+        skip = false;
         SAFEALIGN_COPY_INT32(&msg_type, buf+p, &p);
         SAFEALIGN_COPY_INT32(&msg_len, buf+p, &p);
 
@@ -813,10 +822,24 @@ static void krb5_child_done(struct tevent_req *subreq)
                       tgtt.starttime, tgtt.endtime, tgtt.renew_till));
         }
 
-        ret = pam_add_response(pd, msg_type, msg_len, &buf[p]);
-        if (ret != EOK) {
-            /* This is not a fatal error */
-            DEBUG(1, ("pam_add_response failed.\n"));
+        if (msg_type == SSS_PAM_USER_INFO) {
+            msg_subtype = (uint32_t *)&buf[p];
+            if (*msg_subtype == SSS_PAM_USER_INFO_EXPIRE_WARN)
+            {
+                expiration = (uint32_t *)&buf[p+sizeof(uint32_t)];
+                if (pwd_exp_warning > 0 &&
+                    difftime(pwd_exp_warning, *expiration) < 0.0) {
+                    skip = true;
+                }
+            }
+        }
+
+        if (!skip) {
+            ret = pam_add_response(pd, msg_type, msg_len, &buf[p]);
+            if (ret != EOK) {
+                /* This is not a fatal error */
+                DEBUG(1, ("pam_add_response failed.\n"));
+            }
         }
         p += msg_len;
 
diff --git a/src/providers/ldap/ldap_auth.c b/src/providers/ldap/ldap_auth.c
index 734249ced33e3b70dcfd452f674a9e3dd5b736da..da64f249ecc790fb76d33eb22e76e12ccb3fdc4c 100644
--- a/src/providers/ldap/ldap_auth.c
+++ b/src/providers/ldap/ldap_auth.c
@@ -47,11 +47,7 @@
 #include "providers/ldap/sdap_async.h"
 #include "providers/ldap/sdap_async_private.h"
 
-/* MIT Kerberos has the same hardcoded warning interval of 7 days. Due to the
- * fact that using the expiration time of a Kerberos password with LDAP
- * authentication is presumably a rare case a separate config option is not
- * necessary. */
-#define KERBEROS_PWEXPIRE_WARNING_TIME (7 * 24 * 60 * 60)
+#define LDAP_PWEXPIRE_WARNING_TIME 0
 
 enum pwexpire {
     PWEXPIRE_NONE = 0,
@@ -90,11 +86,13 @@ static errno_t add_expired_warning(struct pam_data *pd, long exp_time)
 
 static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now,
                                        struct pam_data *pd,
-                                       enum sdap_result *result)
+                                       enum sdap_result *result,
+                                       int pwd_exp_warning)
 {
     char *end;
     struct tm tm;
     time_t expire_time;
+    int expiration_warning;
     int ret;
 
     memset(&tm, 0, sizeof(tm));
@@ -130,8 +128,14 @@ static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now,
     } else {
         *result = SDAP_AUTH_SUCCESS;
 
+        if (pwd_exp_warning >= 0) {
+            expiration_warning = pwd_exp_warning;
+        } else {
+            expiration_warning = KERBEROS_PWEXPIRE_WARNING_TIME;
+        }
         if (pd != NULL &&
-            difftime(now + KERBEROS_PWEXPIRE_WARNING_TIME, expire_time) > 0.0) {
+            (difftime(now + expiration_warning, expire_time) > 0.0 ||
+            expiration_warning == 0)) {
             ret = add_expired_warning(pd, (long) difftime(expire_time, now));
             if (ret != EOK) {
                 DEBUG(1, ("add_expired_warning failed.\n"));
@@ -202,13 +206,19 @@ static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
 
 static errno_t check_pwexpire_ldap(struct pam_data *pd,
                                    struct sdap_ppolicy_data *ppolicy,
-                                   enum sdap_result *result)
+                                   enum sdap_result *result,
+                                   int pwd_exp_warning)
 {
     if (ppolicy->grace > 0 || ppolicy->expire > 0) {
         uint32_t *data;
         uint32_t *ptr;
+        time_t now = time(NULL);
         int ret;
 
+        if (pwd_exp_warning < 0) {
+            pwd_exp_warning = 0;
+        }
+
         data = talloc_size(pd, 2* sizeof(uint32_t));
         if (data == NULL) {
             DEBUG(1, ("talloc_size failed.\n"));
@@ -221,6 +231,10 @@ static errno_t check_pwexpire_ldap(struct pam_data *pd,
             ptr++;
             *ptr = ppolicy->grace;
         } else if (ppolicy->expire > 0) {
+            if (pwd_exp_warning == 0 ||
+                difftime(now + pwd_exp_warning, ppolicy->expire) > 0.0) {
+                goto done;
+            }
             *ptr = SSS_PAM_USER_INFO_EXPIRE_WARN;
             ptr++;
             *ptr = ppolicy->expire;
@@ -234,6 +248,7 @@ static errno_t check_pwexpire_ldap(struct pam_data *pd,
         }
     }
 
+done:
     *result = SDAP_AUTH_SUCCESS;
     return EOK;
 }
@@ -830,8 +845,8 @@ static void sdap_auth4chpass_done(struct tevent_req *req)
                 }
                 break;
             case PWEXPIRE_KERBEROS:
-                ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), NULL,
-                                              &result);
+                ret = check_pwexpire_kerberos(pw_expire_data, time(NULL), NULL, &result,
+                                              state->breq->domain->pwd_expiration_warning);
                 if (ret != EOK) {
                     DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
                     state->pd->pam_status = PAM_SYSTEM_ERR;
@@ -1064,6 +1079,7 @@ static void sdap_pam_auth_done(struct tevent_req *req)
                     tevent_req_callback_data(req, struct sdap_pam_auth_state);
     enum sdap_result result;
     enum pwexpire pw_expire_type;
+    struct be_ctx *be_ctx = state->breq->be_ctx;
     void *pw_expire_data;
     int dp_err = DP_ERR_OK;
     int ret;
@@ -1091,7 +1107,8 @@ static void sdap_pam_auth_done(struct tevent_req *req)
                 break;
             case PWEXPIRE_KERBEROS:
                 ret = check_pwexpire_kerberos(pw_expire_data, time(NULL),
-                                              state->pd, &result);
+                                              state->pd, &result,
+                                              be_ctx->domain->pwd_expiration_warning);
                 if (ret != EOK) {
                     DEBUG(1, ("check_pwexpire_kerberos failed.\n"));
                     state->pd->pam_status = PAM_SYSTEM_ERR;
@@ -1099,7 +1116,8 @@ static void sdap_pam_auth_done(struct tevent_req *req)
                 }
                 break;
             case PWEXPIRE_LDAP_PASSWORD_POLICY:
-                ret = check_pwexpire_ldap(state->pd, pw_expire_data, &result);
+                ret = check_pwexpire_ldap(state->pd, pw_expire_data, &result,
+                                          be_ctx->domain->pwd_expiration_warning);
                 if (ret != EOK) {
                     DEBUG(1, ("check_pwexpire_ldap failed.\n"));
                     state->pd->pam_status = PAM_SYSTEM_ERR;
diff --git a/src/responder/pam/pamsrv_cmd.c b/src/responder/pam/pamsrv_cmd.c
index c3e027e3c3c0c693dd6d65f3f625533248d96ed2..608a38d4f7e3aafebe6692c6de5acb7cdf11881b 100644
--- a/src/responder/pam/pamsrv_cmd.c
+++ b/src/responder/pam/pamsrv_cmd.c
@@ -42,7 +42,6 @@ enum pam_verbosity {
 };
 
 #define DEFAULT_PAM_VERBOSITY PAM_VERBOSITY_IMPORTANT
-#define DEFAULT_PAM_PWD_EXPIRATION_WARNING 7
 
 static void pam_reply(struct pam_auth_req *preq);
 
@@ -515,16 +514,7 @@ static errno_t filter_responses(struct confdb_ctx *cdb,
     struct response_data *resp;
     uint32_t user_info_type;
     int64_t expire_date;
-    uint32_t expire_warn;
-    TALLOC_CTX *tmp_ctx;
     int pam_verbosity;
-    int pam_expiration_warning;
-
-    tmp_ctx = talloc_new(NULL);
-    if (tmp_ctx == NULL) {
-        DEBUG(1, ("talloc_new failed.\n"));
-        return ENOMEM;
-    }
 
     ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
                          CONFDB_PAM_VERBOSITY, DEFAULT_PAM_VERBOSITY,
@@ -534,20 +524,7 @@ static errno_t filter_responses(struct confdb_ctx *cdb,
         pam_verbosity = DEFAULT_PAM_VERBOSITY;
     }
 
-
-    ret = confdb_get_int(cdb, CONFDB_PAM_CONF_ENTRY,
-                         CONFDB_PAM_PWD_EXPIRATION_WARNING,
-                         DEFAULT_PAM_PWD_EXPIRATION_WARNING,
-                         &pam_expiration_warning);
-    if (ret != EOK) {
-        DEBUG(1, ("Failed to read PAM expiration warning, not fatal.\n"));
-        pam_expiration_warning = DEFAULT_PAM_PWD_EXPIRATION_WARNING;
-    }
-
-    talloc_free(tmp_ctx);
-
     resp = resp_list;
-
     while(resp != NULL) {
         if (resp->type == SSS_PAM_USER_INFO) {
             if (resp->len < sizeof(uint32_t)) {
@@ -581,18 +558,6 @@ static errno_t filter_responses(struct confdb_ctx *cdb,
                     }
 
                     break;
-                case SSS_PAM_USER_INFO_EXPIRE_WARN:
-                    if (resp->len != 2 * sizeof(uint32_t)) {
-                        DEBUG(1, ("User info expire warning entry is "
-                                  "too short.\n"));
-                        return EINVAL;
-                    }
-                    memcpy(&expire_warn, resp->data + sizeof(uint32_t),
-                           sizeof(uint32_t));
-                    if(expire_warn > pam_expiration_warning * (60 * 60 * 24)) {
-                        resp->do_not_send_to_client = true;
-                    }
-                    break;
                 default:
                     DEBUG(7, ("User info type [%d] not filtered.\n"));
             }
diff --git a/src/util/domain_info_utils.c b/src/util/domain_info_utils.c
index d9f320d80fb5045a861996975714a5b0bf904401..45f98d858ad5f6e671f509701cc996a1de84154d 100644
--- a/src/util/domain_info_utils.c
+++ b/src/util/domain_info_utils.c
@@ -77,6 +77,7 @@ struct sss_domain_info *new_subdomain(TALLOC_CTX *mem_ctx,
     /* FIXME: get ranges from the server */
     dom->id_min = 0;
     dom->id_max = 0xffffffff;
+    dom->pwd_expiration_warning = parent->pwd_expiration_warning;
     dom->cache_credentials = parent->cache_credentials;
     dom->case_sensitive = parent->case_sensitive;
     dom->user_timeout = parent->user_timeout;
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index 50c4b696f94706fe16069634229cd0e46db9b9cd..6ad80806c0af7d59ba10d6d8f29523f433eeab90 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -34,6 +34,11 @@
 
 #include "util/util.h"
 
+/* MIT Kerberos has the same hardcoded warning interval of 7 days. Due to the
+ * fact that using the expiration time of a Kerberos password with LDAP
+ * authentication is presumably a rare case a separate config option is not
+ * necessary. */
+#define KERBEROS_PWEXPIRE_WARNING_TIME (7 * 24 * 60 * 60)
 #define KEYTAB_CLEAN_NAME keytab_name ? keytab_name : "default"
 
 const char * KRB5_CALLCONV sss_krb5_get_error_message (krb5_context,
-- 
1.7.7.6

