@@ -, +, @@ 
---
 src/man/sssd-ldap.5.xml        |  10 +++
 src/providers/ldap/ldap_auth.c | 108 ++++++++++++++++++++++++++-------
 2 files changed, 96 insertions(+), 22 deletions(-)
--- a/src/man/sssd-ldap.5.xml	
+++ a/src/man/sssd-ldap.5.xml	
@@ -1103,6 +1103,8 @@ host/*
                             <citerefentry><refentrytitle>shadow</refentrytitle>
                             <manvolnum>5</manvolnum></citerefentry> style
                             attributes to evaluate if the password has expired.
+                            Please see option "ldap_chpass_update_last_change"
+                            as well.
                         </para>
                         <para>
                             <emphasis>mit_kerberos</emphasis> - Use the attributes
@@ -1188,6 +1190,14 @@ host/*
                             days since the Epoch after a password change
                             operation.
                         </para>
+                        <para>
+                            It is recommend to set this option explicitly if
+                            "ldap_pwd_policy = shadow" is used to let SSSD
+                            know if the LDAP server will update
+                            shadowLastChange LDAP attribute automatically
+                            after a password change or if SSSD has to update
+                            it.
+                        </para>
                         <para>
                             Default: False
                         </para>
--- a/src/providers/ldap/ldap_auth.c	
+++ a/src/providers/ldap/ldap_auth.c	
@@ -1243,6 +1243,10 @@ struct sdap_pam_chpass_handler_state {
 };
 
 static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq);
+static int
+sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state,
+                                    struct tevent_req *req,
+                                    enum pwexpire pw_expire_type);
 static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq);
 static void sdap_pam_chpass_handler_last_done(struct tevent_req *subreq);
 
@@ -1311,6 +1315,26 @@ immediately:
     return req;
 }
 
+static bool confdb_is_set_explicit(struct confdb_ctx *cdb, const char *section,
+                                   const char *attribute)
+{
+    int ret;
+    char **vals = NULL;
+    bool update_option_set_explictly = false;
+
+    ret = confdb_get_param(cdb, NULL, section, attribute, &vals);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to check if [%s] is set "
+              "explicitly in sssd.conf, assuming it is not set.\n",
+              attribute);
+    } else if (vals != NULL && vals[0] != NULL) {
+        update_option_set_explictly = true;
+    }
+    talloc_free(vals);
+
+    return update_option_set_explictly;
+}
+
 static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq)
 {
     struct sdap_pam_chpass_handler_state *state;
@@ -1368,30 +1392,13 @@ static void sdap_pam_chpass_handler_auth_done(struct tevent_req *subreq)
         case ERR_PASSWORD_EXPIRED:
             DEBUG(SSSDBG_TRACE_LIBS,
                   "user [%s] successfully authenticated.\n", state->dn);
-            if (pw_expire_type == PWEXPIRE_SHADOW) {
-                /* TODO: implement async ldap modify request */
-                DEBUG(SSSDBG_CRIT_FAILURE,
-                      "Changing shadow password attributes not implemented.\n");
-                state->pd->pam_status = PAM_MODULE_UNKNOWN;
+            ret = sdap_pam_chpass_handler_change_step(state, req, pw_expire_type);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_OP_FAILURE,
+                      "sdap_pam_chpass_handler_change_step() failed.\n");
                 goto done;
-            } else {
-                subreq = sdap_pam_change_password_send(state, state->ev,
-                                                       state->sh,
-                                                       state->auth_ctx->opts,
-                                                       state->pd,
-                                                       state->dn);
-                if (subreq == NULL) {
-                    DEBUG(SSSDBG_OP_FAILURE, "Failed to change password for "
-                          "%s\n", state->pd->user);
-                    state->pd->pam_status = PAM_SYSTEM_ERR;
-                    goto done;
-                }
-
-                tevent_req_set_callback(subreq,
-                                        sdap_pam_chpass_handler_chpass_done,
-                                        req);
-                return;
             }
+            return;
             break;
         case ERR_AUTH_DENIED:
         case ERR_AUTH_FAILED:
@@ -1424,6 +1431,63 @@ done:
     tevent_req_done(req);
 }
 
+static int
+sdap_pam_chpass_handler_change_step(struct sdap_pam_chpass_handler_state *state,
+                                    struct tevent_req *req,
+                                    enum pwexpire pw_expire_type)
+{
+    int ret;
+    struct tevent_req *subreq;
+    bool update_option_set_explictly = false;
+
+    if (pw_expire_type == PWEXPIRE_SHADOW) {
+
+        update_option_set_explictly = confdb_is_set_explicit(
+                   state->be_ctx->cdb, state->be_ctx->conf_path,
+                   state->auth_ctx->opts->basic[SDAP_CHPASS_UPDATE_LAST_CHANGE].opt_name);
+
+        if (!dp_opt_get_bool(state->auth_ctx->opts->basic,
+                             SDAP_CHPASS_UPDATE_LAST_CHANGE)
+                    && !update_option_set_explictly) {
+            DEBUG(SSSDBG_CRIT_FAILURE,
+               "Shadow password policy is selected but "
+               "ldap_chpass_update_last_change is not set, please "
+               "make sure your LDAP server can update the [%s] "
+               "attribute automatically. Otherwise SSSD might "
+               "consider your password as expired.\n",
+               state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name);
+        }
+        ret = sysdb_invalidate_cache_entry(state->be_ctx->domain,
+                                           state->pd->user, true);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE,
+               "Failed to invalidate cache entry for user [%s] with error code "
+               "[%d][%s]. The last changed attribute [%s] might not get "
+               "refreshed after the password and the password might still be "
+               "considered as expired. Call sss_cache for this user "
+               "to expire the entry manually in this case.\n",
+               state->pd->user, ret, sss_strerror(ret),
+               state->auth_ctx->opts->user_map[SDAP_AT_SP_LSTCHG].name);
+        }
+    }
+    subreq = sdap_pam_change_password_send(state, state->ev,
+                                           state->sh,
+                                           state->auth_ctx->opts,
+                                           state->pd,
+                                           state->dn);
+    if (subreq == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, "Failed to change password for "
+              "%s\n", state->pd->user);
+        state->pd->pam_status = PAM_SYSTEM_ERR;
+        return ENOMEM;
+    }
+
+    tevent_req_set_callback(subreq,
+                            sdap_pam_chpass_handler_chpass_done,
+                            req);
+    return EOK;
+}
+
 static void sdap_pam_chpass_handler_chpass_done(struct tevent_req *subreq)
 {
     struct sdap_pam_chpass_handler_state *state;
-- 
 keys
---
 src/man/sssd-ldap.5.xml        |  4 ----
 src/providers/ldap/ldap_auth.c | 10 ++++++++++
 2 files changed, 10 insertions(+), 4 deletions(-)
--- a/src/man/sssd-ldap.5.xml	
+++ a/src/man/sssd-ldap.5.xml	
@@ -1376,10 +1376,6 @@ ldap_access_filter = (employeeType=admin)
                             pwd_expire_policy_renew - user is prompted to change
                             his password immediately.
                         </para>
-                        <para>
-                            Note If user password is expired no explicit message
-                            is prompted by SSSD.
-                        </para>
                         <para>
                             Please note that 'access_provider = ldap' must
                             be set for this feature to work. Also 'ldap_pwd_policy'
--- a/src/providers/ldap/ldap_auth.c	
+++ a/src/providers/ldap/ldap_auth.c	
@@ -103,6 +103,12 @@ static errno_t check_pwexpire_kerberos(const char *expire_date, time_t now,
 
     if (difftime(now, expire_time) > 0.0) {
         DEBUG(SSSDBG_CONF_SETTINGS, "Kerberos password expired.\n");
+        if (pd != NULL) {
+            ret = add_expired_warning(pd, 0);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n");
+            }
+        }
         ret = ERR_PASSWORD_EXPIRED;
     } else {
         if (pwd_exp_warning >= 0) {
@@ -156,6 +162,10 @@ static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
 
     if (spwd->sp_max != -1 && password_age > spwd->sp_max) {
         DEBUG(SSSDBG_CONF_SETTINGS, "Password expired.\n");
+        ret = add_expired_warning(pd, 0);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n");
+        }
         return ERR_PASSWORD_EXPIRED;
     }
 
-- 
---
 src/providers/ldap/ldap_auth.c | 8 +++++---
 1 file changed, 5 insertions(+), 3 deletions(-)
--- a/src/providers/ldap/ldap_auth.c	
+++ a/src/providers/ldap/ldap_auth.c	
@@ -162,9 +162,11 @@ static errno_t check_pwexpire_shadow(struct spwd *spwd, time_t now,
 
     if (spwd->sp_max != -1 && password_age > spwd->sp_max) {
         DEBUG(SSSDBG_CONF_SETTINGS, "Password expired.\n");
-        ret = add_expired_warning(pd, 0);
-        if (ret != EOK) {
-            DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n");
+        if (pd != NULL) {
+            ret = add_expired_warning(pd, 0);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "add_expired_warning failed.\n");
+            }
         }
         return ERR_PASSWORD_EXPIRED;
     }
-- 
---
 src/sss_client/pam_sss.c | 34 ++++++++++++++++++++++------------
 1 file changed, 22 insertions(+), 12 deletions(-)
--- a/src/sss_client/pam_sss.c	
+++ a/src/sss_client/pam_sss.c	
@@ -52,6 +52,7 @@ 
 
 #include <libintl.h>
 #define _(STRING) dgettext (PACKAGE, STRING)
+#define _n(SINGULAR, PLURAL, VALUE) dngettext(PACKAGE, SINGULAR, PLURAL, VALUE)
 
 #define PWEXP_FLAG "pam_sss:password_expired_flag"
 #define FD_DESTRUCTOR "pam_sss:fd_destructor"
@@ -587,26 +588,35 @@ static int user_info_expire_warn(pam_handle_t *pamh,
     int ret;
     uint32_t expire;
     char user_msg[256];
-    const char* unit="second(s)";
+    const char* unit;
 
     if (buflen != 2* sizeof(uint32_t)) {
         D(("User info response data has the wrong size"));
         return PAM_BUF_ERR;
     }
     memcpy(&expire, buf + sizeof(uint32_t), sizeof(uint32_t));
-    if (expire >= DAYSEC) {
-        expire /= DAYSEC;
-        unit = "day(s)";
-    } else if (expire >= HOURSEC) {
-        expire /= HOURSEC;
-        unit = "hour(s)";
-    } else if (expire >= MINSEC) {
-        expire /= MINSEC;
-        unit = "minute(s)";
+    /* expire == 0 indicates the password expired */
+    if (expire != 0) {
+        if (expire >= DAYSEC) {
+            expire /= DAYSEC;
+            unit = _n("day", "days", expire);
+        } else if (expire >= HOURSEC) {
+            expire /= HOURSEC;
+            unit = _n("hour", "hours", expire);
+        } else if (expire >= MINSEC) {
+            expire /= MINSEC;
+            unit = _n("minute", "minutes", expire);
+        } else {
+            unit = _n("second", "seconds", expire);
+        }
+
+        ret = snprintf(user_msg, sizeof(user_msg),
+                       _("Your password will expire in %1$d %2$s."), expire, unit);
+    } else {
+        ret = snprintf(user_msg, sizeof(user_msg),
+                       _("Your password has expired."));
     }
 
-    ret = snprintf(user_msg, sizeof(user_msg),
-                   _("Your password will expire in %1$d %2$s."), expire, unit);
     if (ret < 0 || ret >= sizeof(user_msg)) {
         D(("snprintf failed."));
         return PAM_SYSTEM_ERR;
-- 

