Blob Blame History Raw
From 663c5a61e6ab5aeee901684b8b43176711d5554e Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Sat, 18 Oct 2014 22:03:13 +0200
Subject: [PATCH 87/92] KRB5: Move all ccache operations to krb5_child.c
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

The credential cache operations must be now performed by the krb5_child
completely, because the sssd_be process might be running as the sssd
user who doesn't have access to the ccaches.

src/providers/krb5/krb5_ccache.c is still linked against libsss_krb5
until we fix Kerberos ticket renewal as non-root.

Also includes a new error code that indicates that the back end should
remove the old ccache attribute -- the child can't do that if it's
running as the user.

Related:
https://fedorahosted.org/sssd/ticket/2370

Reviewed-by: Sumit Bose <sbose@redhat.com>
Reviewed-by: Lukáš Slebodník <lslebodn@redhat.com>
---
 Makefile.am                             |  13 +-
 src/providers/krb5/krb5_auth.c          | 223 ++++----------------------------
 src/providers/krb5/krb5_ccache.c        |  62 ++++-----
 src/providers/krb5/krb5_ccache.h        |   5 +-
 src/providers/krb5/krb5_child.c         | 208 +++++++++++++++++++++++++++--
 src/providers/krb5/krb5_child_handler.c |  13 ++
 src/tests/krb5_child-test.c             |   3 +-
 src/util/util_errors.c                  |   1 +
 src/util/util_errors.h                  |   1 +
 9 files changed, 281 insertions(+), 248 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 4a69ecb0cfe48e20bde958c9351c2a5ece5ffffa..5325d51e7240ae39a546e68b2a2aea202b3dfdfa 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -2496,27 +2496,36 @@ libsss_ad_la_LDFLAGS = \
 
 krb5_child_SOURCES = \
     src/providers/krb5/krb5_child.c \
+    src/providers/krb5/krb5_ccache.c \
     src/providers/dp_pam_data_util.c \
     src/util/user_info_msg.c \
     src/util/sss_krb5.c \
+    src/util/find_uid.c \
     src/util/atomic_io.c \
     src/util/authtok.c \
     src/util/util.c \
     src/util/signal.c \
+    src/util/strtonum.c \
     src/util/become_user.c \
     src/sss_client/common.c \
     $(NULL)
 krb5_child_CFLAGS = \
     $(AM_CFLAGS) \
     $(POPT_CFLAGS) \
-    $(KRB5_CFLAGS)
+    $(KRB5_CFLAGS) \
+    $(PCRE_CFLAGS) \
+    $(SYSTEMD_LOGIN_CFLAGS) \
+    $(NULL)
 krb5_child_LDADD = \
     libsss_debug.la \
     $(TALLOC_LIBS) \
     $(POPT_LIBS) \
     $(DHASH_LIBS) \
     $(KRB5_LIBS) \
-    $(CLIENT_LIBS)
+    $(CLIENT_LIBS) \
+    $(PCRE_LIBS) \
+    $(SYSTEMD_LOGIN_LIBS) \
+    $(NULL)
 
 ldap_child_SOURCES = \
     src/providers/ldap/ldap_child.c \
diff --git a/src/providers/krb5/krb5_auth.c b/src/providers/krb5/krb5_auth.c
index 5ed561601ac80e53ee795b458c5bf0ca410951bc..e791aee1c2d83f84ba617db1d5d93948c0e4e2a1 100644
--- a/src/providers/krb5/krb5_auth.c
+++ b/src/providers/krb5/krb5_auth.c
@@ -41,45 +41,6 @@
 #include "providers/krb5/krb5_utils.h"
 #include "providers/krb5/krb5_ccache.h"
 
-static errno_t
-check_old_ccache(const char *old_ccache, struct krb5child_req *kr,
-                 const char *realm, bool *active, bool *valid)
-{
-    errno_t ret;
-
-    *active = false;
-    *valid = false;
-
-    ret = sss_krb5_cc_verify_ccache(old_ccache,
-                                    kr->uid, kr->gid,
-                                    realm, kr->upn);
-    switch (ret) {
-    case ERR_NOT_FOUND:
-    case ENOENT:
-        DEBUG(SSSDBG_TRACE_FUNC,
-              "Saved ccache %s doesn't exist.\n", old_ccache);
-        return ENOENT;
-    case EINVAL:
-        /* cache found but no tgt or expired */
-    case EOK:
-        *valid = true;
-        break;
-    default:
-        DEBUG(SSSDBG_OP_FAILURE,
-              "Cannot check if saved ccache %s is valid\n",
-               old_ccache);
-        return ret;
-    }
-
-    ret = check_if_uid_is_active(kr->uid, active);
-    if (ret != EOK) {
-        DEBUG(SSSDBG_OP_FAILURE, "check_if_uid_is_active failed.\n");
-        return ret;
-    }
-
-    return EOK;
-}
-
 static int krb5_mod_ccname(TALLOC_CTX *mem_ctx,
                            struct sysdb_ctx *sysdb,
                            struct sss_domain_info *domain,
@@ -225,7 +186,6 @@ errno_t krb5_setup(TALLOC_CTX *mem_ctx, struct pam_data *pd,
         return ENOMEM;
     }
     kr->is_offline = false;
-    kr->active_ccache = true;
     kr->run_as_user = true;
     talloc_set_destructor((TALLOC_CTX *) kr, krb5_cleanup);
 
@@ -276,47 +236,26 @@ static void krb5_auth_cache_creds(struct krb5_ctx *krb5_ctx,
 }
 
 static errno_t krb5_auth_prepare_ccache_name(struct krb5child_req *kr,
+                                             struct ldb_message *user_msg,
                                              struct be_ctx *be_ctx)
 {
     const char *ccname_template;
-    errno_t ret;
 
-    if (!kr->is_offline) {
-        kr->is_offline = be_is_offline(be_ctx);
+    ccname_template = dp_opt_get_cstring(kr->krb5_ctx->opts, KRB5_CCNAME_TMPL);
+
+    kr->ccname = expand_ccname_template(kr, kr, ccname_template,
+                                        kr->krb5_ctx->illegal_path_re, true,
+                                        be_ctx->domain->case_sensitive);
+    if (kr->ccname == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "expand_ccname_template failed.\n");
+        return ENOMEM;
     }
 
-    /* The ccache file should be (re)created if one of the following conditions
-     * is true:
-     * - it doesn't exist (kr->ccname == NULL)
-     * - the backend is online and the current ccache file is not used, i.e
-     *   the related user is currently not logged in and it is not a renewal
-     *   request
-     *   (!kr->is_offline && !kr->active_ccache && kr->pd->cmd != SSS_CMD_RENEW)
-     * - the backend is offline and the current cache file not used and
-     *   it does not contain a valid tgt
-     *   (kr->is_offline && !kr->active_ccache && !kr->valid_tgt)
-     */
-    if (kr->ccname == NULL ||
-        (kr->is_offline && !kr->active_ccache && !kr->valid_tgt) ||
-        (!kr->is_offline && !kr->active_ccache && kr->pd->cmd != SSS_CMD_RENEW)) {
-            DEBUG(SSSDBG_TRACE_ALL, "Recreating  ccache file.\n");
-            ccname_template = dp_opt_get_cstring(kr->krb5_ctx->opts,
-                                                 KRB5_CCNAME_TMPL);
-            kr->ccname = expand_ccname_template(kr, kr, ccname_template,
-                                                kr->krb5_ctx->illegal_path_re,
-                                                true,
-                                                be_ctx->domain->case_sensitive);
-            if (kr->ccname == NULL) {
-                DEBUG(SSSDBG_CRIT_FAILURE, "expand_ccname_template failed.\n");
-                return ENOMEM;
-            }
-
-            ret = sss_krb5_precreate_ccache(kr->ccname,
-                                            kr->uid, kr->gid);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_OP_FAILURE, "ccache creation failed.\n");
-                return ret;
-            }
+    kr->old_ccname = ldb_msg_find_attr_as_string(user_msg,
+                                                 SYSDB_CCACHE_FILE, NULL);
+    if (kr->old_ccname == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+                "No ccache file for user [%s] found.\n", kr->pd->user);
     }
 
     return EOK;
@@ -402,7 +341,6 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
     struct krb5_auth_state *state;
     struct ldb_result *res;
     struct krb5child_req *kr = NULL;
-    const char *ccache_file = NULL;
     const char *realm;
     struct tevent_req *req;
     struct tevent_req *subreq;
@@ -588,45 +526,10 @@ struct tevent_req *krb5_auth_send(TALLOC_CTX *mem_ctx,
             goto done;
         }
 
-        ccache_file = ldb_msg_find_attr_as_string(res->msgs[0],
-                                                  SYSDB_CCACHE_FILE,
-                                                  NULL);
-        if (ccache_file != NULL) {
-            ret = check_old_ccache(ccache_file, kr, realm,
-                                   &kr->active_ccache,
-                                   &kr->valid_tgt);
-            if (ret == ENOENT) {
-                DEBUG(SSSDBG_FUNC_DATA,
-                      "Ignoring ccache attribute [%s], because it doesn't"
-                       "exist.\n", ccache_file);
-                ccache_file = NULL;
-            } else if (ret != EOK) {
-                DEBUG(SSSDBG_CRIT_FAILURE,
-                      "check_if_ccache_file_is_used failed.\n");
-                ccache_file = NULL;
-            }
-        } else {
-            kr->active_ccache = false;
-            kr->valid_tgt = false;
-            DEBUG(SSSDBG_CONF_SETTINGS,
-                  "No ccache file for user [%s] found.\n", pd->user);
-        }
-        DEBUG(SSSDBG_TRACE_ALL,
-              "Ccache_file is [%s] and is %s active and TGT is %s valid.\n",
-                  ccache_file ? ccache_file : "not set",
-                  kr->active_ccache ? "" : "not",
-                  kr->valid_tgt ? "" : "not");
-        if (ccache_file != NULL) {
-            kr->ccname = ccache_file;
-            kr->old_ccname = talloc_strdup(kr, ccache_file);
-            if (kr->old_ccname == NULL) {
-                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
-                ret = ENOMEM;
-                goto done;
-            }
-        } else {
-            kr->ccname = NULL;
-            kr->old_ccname = NULL;
+        ret = krb5_auth_prepare_ccache_name(kr, res->msgs[0], state->be_ctx);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Cannot prepare ccache names!\n");
+            goto done;
         }
         break;
 
@@ -669,7 +572,6 @@ static void krb5_auth_resolve_done(struct tevent_req *subreq)
     struct tevent_req *req = tevent_req_callback_data(subreq, struct tevent_req);
     struct krb5_auth_state *state = tevent_req_data(req, struct krb5_auth_state);
     struct krb5child_req *kr = state->kr;
-    char *msg;
     int ret;
 
     if (!state->search_kpasswd) {
@@ -728,45 +630,8 @@ static void krb5_auth_resolve_done(struct tevent_req *subreq)
         }
     }
 
-    ret = krb5_auth_prepare_ccache_name(kr, state->be_ctx);
-    if (ret) {
-        goto done;
-    }
-
-    if (kr->is_offline) {
-        DEBUG(SSSDBG_TRACE_ALL, "Preparing for offline operation.\n");
-
-        if (kr->valid_tgt || kr->active_ccache) {
-            DEBUG(SSSDBG_TRACE_ALL, "Valid TGT available or "
-                      "ccache file is already in use.\n");
-            kr->ccname = kr->old_ccname;
-            msg = talloc_asprintf(kr->pd,
-                                  "%s=%s", CCACHE_ENV_NAME, kr->ccname);
-            if (msg == NULL) {
-                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_asprintf failed.\n");
-            } else {
-                ret = pam_add_response(kr->pd, SSS_PAM_ENV_ITEM,
-                                       strlen(msg) + 1, (uint8_t *) msg);
-                if (ret != EOK) {
-                    DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
-                }
-            }
-
-            if (dp_opt_get_bool(kr->krb5_ctx->opts,
-                                KRB5_STORE_PASSWORD_IF_OFFLINE)) {
-                krb5_auth_cache_creds(state->kr->krb5_ctx,
-                                      state->domain,
-                                      state->be_ctx->cdb,
-                                      kr->pd, kr->uid,
-                                      &state->pam_status, &state->dp_err);
-            } else {
-                state->pam_status = PAM_AUTHINFO_UNAVAIL;
-                state->dp_err = DP_ERR_OFFLINE;
-            }
-            ret = EOK;
-            goto done;
-
-        }
+    if (!kr->is_offline) {
+        kr->is_offline = be_is_offline(state->be_ctx);
     }
 
     /* We need to keep the root privileges to read the keytab file if
@@ -814,7 +679,6 @@ static void krb5_auth_done(struct tevent_req *subreq)
     char *renew_interval_str;
     time_t renew_interval_time = 0;
     bool use_enterprise_principal;
-    uint32_t user_info_type;
 
     ret = handle_child_recv(subreq, pd, &buf, &len);
     talloc_zfree(subreq);
@@ -974,6 +838,14 @@ static void krb5_auth_done(struct tevent_req *subreq)
         }
         break;
 
+    case ERR_CREDS_EXPIRED_CCACHE:
+        ret = krb5_delete_ccname(state, state->sysdb, state->domain,
+                pd->user, kr->old_ccname);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "krb5_delete_ccname failed.\n");
+        }
+        /* FALLTHROUGH */
+
     case ERR_CREDS_EXPIRED:
         /* If the password is expired we can safely remove the ccache from the
          * cache and disk if it is not actively used anymore. This will allow
@@ -981,14 +853,6 @@ static void krb5_auth_done(struct tevent_req *subreq)
          * used. */
         if (pd->cmd == SSS_PAM_AUTHENTICATE && !kr->active_ccache) {
             if (kr->old_ccname != NULL) {
-                ret = safe_remove_old_ccache_file(kr->old_ccname, NULL,
-                                                  kr->uid, kr->gid);
-                if (ret != EOK) {
-                    DEBUG(SSSDBG_CRIT_FAILURE,
-                          "Failed to remove old ccache file [%s], "
-                              "please remove it manually.\n", kr->old_ccname);
-                }
-
                 ret = krb5_delete_ccname(state, state->sysdb, state->domain,
                                          pd->user, kr->old_ccname);
                 if (ret != EOK) {
@@ -1062,37 +926,6 @@ static void krb5_auth_done(struct tevent_req *subreq)
         goto done;
     }
 
-    ret = sss_krb5_check_ccache_princ(kr->uid, kr->gid, kr->ccname, kr->upn);
-    if (ret) {
-        if (res->otp == true && pd->cmd == SSS_PAM_CHAUTHTOK) {
-            DEBUG(SSSDBG_IMPORTANT_INFO,
-                  "Password change succeeded but currently "
-                  "post-chpass kinit is not implemented\n");
-
-            user_info_type = SSS_PAM_USER_INFO_OTP_CHPASS;
-            ret = pam_add_response(pd, SSS_PAM_USER_INFO, sizeof(uint32_t),
-                                   (const uint8_t *) &user_info_type);
-            if (ret != EOK) {
-                DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
-                /* Not fatal */
-            }
-        } else {
-            DEBUG(SSSDBG_CRIT_FAILURE,
-                  "No ccache for %s in %s?\n", kr->upn, kr->ccname);
-            goto done;
-        }
-    }
-
-    if (kr->old_ccname) {
-        ret = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname,
-                                          kr->uid, kr->gid);
-        if (ret != EOK) {
-            DEBUG(SSSDBG_MINOR_FAILURE,
-                  "Failed to remove old ccache file [%s], "
-                   "please remove it manually.\n", kr->old_ccname);
-        }
-    }
-
     ret = krb5_save_ccname(state, state->sysdb, state->domain,
                            pd->user, kr->ccname);
     if (ret) {
diff --git a/src/providers/krb5/krb5_ccache.c b/src/providers/krb5/krb5_ccache.c
index c0f5b7b8ced3fd2d6d8cbbf4e3339caba60888ff..7aa36b744ddcf7e46edcc26405a5101645b8b546 100644
--- a/src/providers/krb5/krb5_ccache.c
+++ b/src/providers/krb5/krb5_ccache.c
@@ -374,49 +374,32 @@ done:
 
 /* This function is called only as a way to validate that we have the
  * right cache */
-errno_t sss_krb5_check_ccache_princ(uid_t uid, gid_t gid,
-                                    const char *ccname, const char *principal)
+errno_t sss_krb5_check_ccache_princ(krb5_context kctx,
+                                    const char *ccname,
+                                    krb5_principal user_princ)
 {
-    struct sss_krb5_ccache *cc = NULL;
+    krb5_ccache kcc = NULL;
     krb5_principal ccprinc = NULL;
-    krb5_principal kprinc = NULL;
     krb5_error_code kerr;
     const char *cc_type;
-    TALLOC_CTX *tmp_ctx;
     errno_t ret;
 
-    tmp_ctx = talloc_new(NULL);
-    if (tmp_ctx == NULL) {
-        DEBUG(SSSDBG_OP_FAILURE, "talloc_new failed.\n");
-        return ENOMEM;
-    }
-
-    ret = sss_open_ccache_as_user(tmp_ctx, ccname, uid, gid, &cc);
-    if (ret) {
-        goto done;
-    }
-
-    cc_type = krb5_cc_get_type(cc->context, cc->ccache);
-
-    DEBUG(SSSDBG_TRACE_INTERNAL,
-          "Searching for [%s] in cache of type [%s]\n", principal, cc_type);
-
-    kerr = krb5_parse_name(cc->context, principal, &kprinc);
-    if (kerr != 0) {
-        KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr);
-        DEBUG(SSSDBG_CRIT_FAILURE, "krb5_parse_name failed.\n");
+    kerr = krb5_cc_resolve(kctx, ccname, &kcc);
+    if (kerr) {
         ret = ERR_INTERNAL;
         goto done;
     }
 
-    kerr = krb5_cc_get_principal(cc->context, cc->ccache, &ccprinc);
+    cc_type = krb5_cc_get_type(kctx, kcc);
+
+    kerr = krb5_cc_get_principal(kctx, kcc, &ccprinc);
     if (kerr != 0) {
-        KRB5_DEBUG(SSSDBG_OP_FAILURE, cc->context, kerr);
+        KRB5_DEBUG(SSSDBG_OP_FAILURE, kctx, kerr);
         DEBUG(SSSDBG_CRIT_FAILURE, "krb5_cc_get_principal failed.\n");
     }
 
     if (ccprinc) {
-        if (krb5_principal_compare(cc->context, kprinc, ccprinc) == TRUE) {
+        if (krb5_principal_compare(kctx, user_princ, ccprinc) == TRUE) {
             /* found in the primary ccache */
             ret = EOK;
             goto done;
@@ -425,23 +408,23 @@ errno_t sss_krb5_check_ccache_princ(uid_t uid, gid_t gid,
 
 #ifdef HAVE_KRB5_CC_COLLECTION
 
-    if (krb5_cc_support_switch(cc->context, cc_type)) {
+    if (krb5_cc_support_switch(kctx, cc_type)) {
 
-        krb5_cc_close(cc->context, cc->ccache);
-        cc->ccache = NULL;
+        krb5_cc_close(kctx, kcc);
+        kcc = NULL;
 
-        kerr = krb5_cc_set_default_name(cc->context, ccname);
+        kerr = krb5_cc_set_default_name(kctx, ccname);
         if (kerr != 0) {
-            KRB5_DEBUG(SSSDBG_MINOR_FAILURE, cc->context, kerr);
+            KRB5_DEBUG(SSSDBG_MINOR_FAILURE, kctx, kerr);
             /* try to continue despite failure */
         }
 
-        kerr = krb5_cc_cache_match(cc->context, kprinc, &cc->ccache);
+        kerr = krb5_cc_cache_match(kctx, user_princ, &kcc);
         if (kerr == 0) {
             ret = EOK;
             goto done;
         }
-        KRB5_DEBUG(SSSDBG_TRACE_INTERNAL, cc->context, kerr);
+        KRB5_DEBUG(SSSDBG_TRACE_INTERNAL, kctx, kerr);
     }
 
 #endif /* HAVE_KRB5_CC_COLLECTION */
@@ -449,11 +432,12 @@ errno_t sss_krb5_check_ccache_princ(uid_t uid, gid_t gid,
     ret = ERR_NOT_FOUND;
 
 done:
-    if (cc) {
-        krb5_free_principal(cc->context, ccprinc);
-        krb5_free_principal(cc->context, kprinc);
+    if (ccprinc) {
+        krb5_free_principal(kctx, ccprinc);
+    }
+    if (kcc) {
+        krb5_cc_close(kctx, kcc);
     }
-    talloc_free(tmp_ctx);
     return ret;
 }
 
diff --git a/src/providers/krb5/krb5_ccache.h b/src/providers/krb5/krb5_ccache.h
index e39f96cad6f46c4003103dce4eadf007bc0f8920..e47df3665e3f325cc56d34767b416662577cc048 100644
--- a/src/providers/krb5/krb5_ccache.h
+++ b/src/providers/krb5/krb5_ccache.h
@@ -39,8 +39,9 @@ errno_t sss_krb5_precreate_ccache(const char *ccname, uid_t uid, gid_t gid);
 
 errno_t sss_krb5_cc_destroy(const char *ccname, uid_t uid, gid_t gid);
 
-errno_t sss_krb5_check_ccache_princ(uid_t uid, gid_t gid,
-                                    const char *ccname, const char *principal);
+errno_t sss_krb5_check_ccache_princ(krb5_context kctx,
+                                    const char *ccname,
+                                    krb5_principal user_princ);
 
 errno_t sss_krb5_cc_verify_ccache(const char *ccname, uid_t uid, gid_t gid,
                                   const char *realm, const char *principal);
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 7fa5f0c344a4afe110afa08f479f283aefce8d23..94cd34e433cf6a197860d233fbf9ca30cd3eb535 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -33,6 +33,7 @@
 #include "util/sss_krb5.h"
 #include "util/user_info_msg.h"
 #include "util/child_common.h"
+#include "util/find_uid.h"
 #include "providers/dp_backend.h"
 #include "providers/krb5/krb5_auth.h"
 #include "providers/krb5/krb5_utils.h"
@@ -61,6 +62,10 @@ struct krb5_req {
     const char *upn;
     uid_t uid;
     gid_t gid;
+
+    char *old_ccname;
+    bool old_cc_valid;
+    bool old_cc_active;
 };
 
 static krb5_context krb5_error_ctx;
@@ -1021,6 +1026,24 @@ static krb5_error_code get_and_save_tgt(struct krb5_req *kr,
         goto done;
     }
 
+    /* Successfull authentication! Check if ccache contains the
+     * right principal...
+     */
+    kerr = sss_krb5_check_ccache_princ(kr->ctx, kr->ccname, kr->creds->client);
+    if (kerr) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "No ccache for %s in %s?\n", kr->upn, kr->ccname);
+        goto done;
+    }
+
+    kerr = safe_remove_old_ccache_file(kr->old_ccname, kr->ccname,
+                                       kr->uid, kr->gid);
+    if (kerr != EOK) {
+        DEBUG(SSSDBG_MINOR_FAILURE,
+              "Failed to remove old ccache file [%s], "
+              "please remove it manually.\n", kr->old_ccname);
+    }
+
     kerr = add_ticket_times_and_upn_to_response(kr);
     if (kerr != 0) {
         DEBUG(SSSDBG_CRIT_FAILURE,
@@ -1094,6 +1117,7 @@ static errno_t changepw_child(struct krb5_req *kr, bool prelim)
     int realm_length;
     size_t msg_len;
     uint8_t *msg;
+    uint32_t user_info_type;
 
     DEBUG(SSSDBG_TRACE_LIBS, "Password change operation\n");
 
@@ -1222,6 +1246,14 @@ static errno_t changepw_child(struct krb5_req *kr, bool prelim)
     krb5_free_cred_contents(kr->ctx, kr->creds);
 
     if (kr->otp == true) {
+        user_info_type = SSS_PAM_USER_INFO_OTP_CHPASS;
+        ret = pam_add_response(kr->pd, SSS_PAM_USER_INFO, sizeof(uint32_t),
+                               (const uint8_t *) &user_info_type);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "pam_add_response failed.\n");
+            /* Not fatal */
+        }
+
         sss_authtok_set_empty(kr->pd->newauthtok);
         return map_krb5_error(kerr);
     }
@@ -1298,6 +1330,21 @@ static errno_t tgt_req_child(struct krb5_req *kr)
     krb5_free_cred_contents(kr->ctx, kr->creds);
     if (kerr == 0) {
         ret = ERR_CREDS_EXPIRED;
+
+        /* If the password is expired we can safely remove the ccache from the
+         * cache and disk if it is not actively used anymore. This will allow
+         * to create a new random ccache if sshd with privilege separation is
+         * used. */
+        if (kr->old_cc_active == false && kr->old_ccname) {
+            ret = safe_remove_old_ccache_file(kr->old_ccname, NULL,
+                    kr->uid, kr->gid);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                        "Failed to remove old ccache file [%s], "
+                        "please remove it manually.\n", kr->old_ccname);
+            }
+            ret = ERR_CREDS_EXPIRED_CCACHE;
+        }
     } else {
         ret = map_krb5_error(kerr);
     }
@@ -1423,12 +1470,17 @@ static errno_t create_empty_ccache(struct krb5_req *kr)
     krb5_creds *creds = NULL;
     krb5_error_code kerr;
 
-    DEBUG(SSSDBG_TRACE_LIBS, "Creating empty ccache\n");
-
-    kerr = create_empty_cred(kr->ctx, kr->princ, &creds);
-    if (kerr == 0) {
-        kerr = create_ccache(kr->ccname, creds);
+    if (kr->old_cc_valid == false) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Creating empty ccache\n");
+        kerr = create_empty_cred(kr->ctx, kr->princ, &creds);
+        if (kerr == 0) {
+            kerr = create_ccache(kr->ccname, creds);
+        }
+    } else {
+        DEBUG(SSSDBG_TRACE_LIBS, "Existing ccache still valid, reusing\n");
+        kerr = 0;
     }
+
     if (kerr != 0) {
         KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
     } else {
@@ -1529,6 +1581,17 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size,
 
         SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
         if ((p + len ) > size) return EINVAL;
+
+        if (len > 0) {
+            kr->old_ccname = talloc_strndup(pd, (char *)(buf + p), len);
+            if (kr->old_ccname == NULL) return ENOMEM;
+            p += len;
+        } else {
+            DEBUG(SSSDBG_TRACE_INTERNAL, "No old ccache\n");
+        }
+
+        SAFEALIGN_COPY_UINT32_CHECK(&len, buf + p, size, &p);
+        if ((p + len ) > size) return EINVAL;
         kr->keytab = talloc_strndup(pd, (char *)(buf + p), len);
         if (kr->keytab == NULL) return ENOMEM;
         p += len;
@@ -1538,10 +1601,14 @@ static errno_t unpack_buffer(uint8_t *buf, size_t size,
             return ret;
         }
 
-        DEBUG(SSSDBG_CONF_SETTINGS, "ccname: [%s] keytab: [%s]\n",
-              kr->ccname, kr->keytab);
+        DEBUG(SSSDBG_CONF_SETTINGS,
+              "ccname: [%s] old_ccname: [%s] keytab: [%s]\n",
+              kr->ccname,
+              kr->old_ccname ? kr->old_ccname : "not set",
+              kr->keytab);
     } else {
         kr->ccname = NULL;
+        kr->old_ccname = NULL;
         kr->keytab = NULL;
         sss_authtok_set_empty(pd->authtok);
     }
@@ -1870,6 +1937,126 @@ static errno_t check_use_fast(enum k5c_fast_opt *_fast_val)
     return EOK;
 }
 
+static errno_t old_ccache_valid(struct krb5_req *kr, bool *_valid)
+{
+    errno_t ret;
+    bool valid;
+
+    valid = false;
+
+    ret = sss_krb5_cc_verify_ccache(kr->old_ccname,
+                                    kr->uid, kr->gid,
+                                    kr->realm, kr->upn);
+    switch (ret) {
+        case ERR_NOT_FOUND:
+        case ENOENT:
+            DEBUG(SSSDBG_TRACE_FUNC,
+                  "Saved ccache %s doesn't exist, ignoring\n", kr->old_ccname);
+            break;
+        case EINVAL:
+            /* cache found but no tgt or expired */
+        case EOK:
+            valid = true;
+            break;
+        default:
+            DEBUG(SSSDBG_OP_FAILURE,
+                  "Cannot check if saved ccache %s is valid\n",
+                   kr->old_ccname);
+            return ret;
+    }
+
+    *_valid = valid;
+    return EOK;
+}
+
+static int k5c_check_old_ccache(struct krb5_req *kr)
+{
+    errno_t ret;
+
+    if (kr->old_ccname) {
+        ret = old_ccache_valid(kr, &kr->old_cc_valid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "old_ccache_valid failed.\n");
+            return ret;
+        }
+
+        ret = check_if_uid_is_active(kr->uid, &kr->old_cc_active);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "check_if_uid_is_active failed.\n");
+            return ret;
+        }
+
+        DEBUG(SSSDBG_TRACE_ALL,
+                "Ccache_file is [%s] and is %s active and TGT is %s valid.\n",
+                kr->old_ccname ? kr->old_ccname : "not set",
+                kr->old_cc_active ? "" : "not",
+                kr->old_cc_valid ? "" : "not");
+    }
+
+    return EOK;
+}
+
+static int k5c_precreate_ccache(struct krb5_req *kr, uint32_t offline)
+{
+    errno_t ret;
+
+    /* The ccache file should be (re)created if one of the following conditions
+     * is true:
+     * - it doesn't exist (kr->old_ccname == NULL)
+     * - the backend is online and the current ccache file is not used, i.e
+     * the related user is currently not logged in and it is not a renewal
+     * request
+     * (offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW)
+     * - the backend is offline and the current cache file not used and
+     * it does not contain a valid tgt
+     * (offline && !kr->old_cc_active && !kr->valid_tgt)
+     */
+    if (kr->old_ccname == NULL ||
+            (offline && !kr->old_cc_active && !kr->old_cc_valid) ||
+            (!offline && !kr->old_cc_active && kr->pd->cmd != SSS_CMD_RENEW)) {
+        DEBUG(SSSDBG_TRACE_ALL, "Recreating ccache\n");
+
+        ret = sss_krb5_precreate_ccache(kr->ccname, kr->uid, kr->gid);
+        if (ret != EOK) {
+            DEBUG(SSSDBG_OP_FAILURE, "ccache creation failed.\n");
+            return ret;
+        }
+    } else {
+        /* We can reuse the old ccache */
+        kr->ccname = kr->old_ccname;
+    }
+
+    return EOK;
+}
+
+static int k5c_ccache_setup(struct krb5_req *kr, uint32_t offline)
+{
+    errno_t ret;
+
+    if (kr->pd->cmd == SSS_PAM_ACCT_MGMT) {
+        return EOK;
+    }
+
+    ret = k5c_check_old_ccache(kr);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot check old ccache\n");
+        return ret;
+    }
+
+    /* Pre-creating the ccache must be done as root, otherwise we can't mkdir
+     * some of the DIR: cache components. One example is /run/user/$UID because
+     * logind doesn't create the directory until the session phase, whereas
+     * we need the directory during the auth phase already
+     */
+    ret = k5c_precreate_ccache(kr, offline);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot precreate ccache\n");
+        return ret;
+    }
+
+    return EOK;
+}
+
 static int k5c_setup(struct krb5_req *kr, uint32_t offline)
 {
     krb5_error_code kerr;
@@ -1881,6 +2068,11 @@ static int k5c_setup(struct krb5_req *kr, uint32_t offline)
         return kerr;
     }
 
+    kerr = k5c_ccache_setup(kr, offline);
+    if (kerr != EOK) {
+        return kerr;
+    }
+
     if (offline || (fast_val == K5C_FAST_NEVER && kr->validate == false)) {
         /* If krb5_child was started as setuid, but we don't need to
          * perform either validation or FAST, just drop privileges to
@@ -1974,7 +2166,7 @@ static int k5c_setup(struct krb5_req *kr, uint32_t offline)
 
     kerr = set_lifetime_options(kr->options);
     if (kerr != 0) {
-        DEBUG(SSSDBG_OP_FAILURE, ("set_lifetime_options failed.\n"));
+        DEBUG(SSSDBG_OP_FAILURE, "set_lifetime_options failed.\n");
         return kerr;
     }
 
diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 71c7f9c9f662e16b94afda0c8c0ae24666f0ba15..93961172c7a3a5d8f2a4fb320370037f188b5909 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -144,6 +144,11 @@ static errno_t create_send_buffer(struct krb5child_req *kr,
         kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
         buf->size += 4*sizeof(uint32_t) + strlen(kr->ccname) + strlen(keytab) +
                      sss_authtok_get_size(kr->pd->authtok);
+
+        buf->size += sizeof(uint32_t);
+        if (kr->old_ccname) {
+            buf->size += strlen(kr->old_ccname);
+        }
     }
 
     if (kr->pd->cmd == SSS_PAM_CHAUTHTOK) {
@@ -182,6 +187,14 @@ static errno_t create_send_buffer(struct krb5child_req *kr,
         SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->ccname), &rp);
         safealign_memcpy(&buf->data[rp], kr->ccname, strlen(kr->ccname), &rp);
 
+        if (kr->old_ccname) {
+            SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(kr->old_ccname), &rp);
+            safealign_memcpy(&buf->data[rp], kr->old_ccname,
+                             strlen(kr->old_ccname), &rp);
+        } else {
+            SAFEALIGN_SET_UINT32(&buf->data[rp], 0, &rp);
+        }
+
         SAFEALIGN_SET_UINT32(&buf->data[rp], strlen(keytab), &rp);
         safealign_memcpy(&buf->data[rp], keytab, strlen(keytab), &rp);
 
diff --git a/src/tests/krb5_child-test.c b/src/tests/krb5_child-test.c
index 8826a28ed5ea064317c62682003dc0e9a6df01b6..a59863b4d8aa8bdcc241c87befd672a5e5c876a3 100644
--- a/src/tests/krb5_child-test.c
+++ b/src/tests/krb5_child-test.c
@@ -239,8 +239,7 @@ create_dummy_req(TALLOC_CTX *mem_ctx, const char *user,
         kr->ccname = expand_ccname_template(kr, kr,
                                         dp_opt_get_cstring(kr->krb5_ctx->opts,
                                                            KRB5_CCNAME_TMPL),
-                                            kr->krb5_ctx->illegal_path_re,
-                                            true, true);
+                                            kr->krb5_ctx->illegal_path_re, true, true);
         if (!kr->ccname) goto fail;
 
         DEBUG(SSSDBG_FUNC_DATA, "ccname [%s] uid [%llu] gid [%llu]\n",
diff --git a/src/util/util_errors.c b/src/util/util_errors.c
index d5da64622eebe7f779816c7f2090da5b9a9b13f0..c1ed0fb634c447904b63335d1cd161b7e7914a08 100644
--- a/src/util/util_errors.c
+++ b/src/util/util_errors.c
@@ -31,6 +31,7 @@ struct err_string error_to_str[] = {
     { "Invalid credential type" },  /* ERR_INVALID_CRED_TYPE */
     { "No credentials available" }, /* ERR_NO_CREDS */
     { "Credentials are expired" }, /* ERR_CREDS_EXPIRED */
+    { "Credentials are expired, old ccache was removed" }, /* ERR_CREDS_EXPIRED_CCACHE */
     { "Failure setting user credentials"}, /* ERR_CREDS_INVALID */
     { "No cached credentials available" }, /* ERR_NO_CACHED_CREDS */
     { "Cached credentials are expired" }, /* ERR_CACHED_CREDS_EXPIRED */
diff --git a/src/util/util_errors.h b/src/util/util_errors.h
index 2bc576605e613d674d38b54aae1604c0b044635f..f71ede8d0fa000627a1bd994ec8bd94a632b35b2 100644
--- a/src/util/util_errors.h
+++ b/src/util/util_errors.h
@@ -56,6 +56,7 @@ enum sssd_errors {
     ERR_CREDS_INVALID,
     ERR_NO_CACHED_CREDS,
     ERR_CACHED_CREDS_EXPIRED,
+    ERR_CREDS_EXPIRED_CCACHE,
     ERR_AUTH_DENIED,
     ERR_AUTH_FAILED,
     ERR_CHPASS_DENIED,
-- 
1.9.3