URL: https://github.com/SSSD/sssd/pull/5450
Author: justin-stephenson
 Title: #5450: kcm: add support for kerberos tgt renewals
Action: synchronized

To pull the PR as Git branch:
git remote add ghsssd https://github.com/SSSD/sssd
git fetch ghsssd pull/5450/head:pr5450
git checkout pr5450
From 90427e8ac3e37299d5da36f386597d21837f3c29 Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Tue, 1 Dec 2020 11:28:59 -0500
Subject: [PATCH 01/10] KCM: Read and set KCM krb5 options

These options are necessary to call into existing
krb5 child code.

Options will be read from the [kcm] configuration section, if
no options are specified here, fallback to reading these options
from the first  'auth_provider=krb5' in sssd.conf also.

krb5_renew_interval
krb5_renew_lifetime
krb5_lifetime
krb5_validate
krb5_canonicalize
krb5_auth_timeout
---
 Makefile.am                   |   3 +
 src/confdb/confdb.h           |   6 +
 src/config/cfg_rules.ini      |   6 +
 src/man/sssd-kcm.8.xml        |  49 ++++++
 src/responder/kcm/kcm.c       |   7 +
 src/responder/kcm/kcm_renew.c | 297 ++++++++++++++++++++++++++++++++++
 src/responder/kcm/kcm_renew.h |   3 +
 7 files changed, 371 insertions(+)
 create mode 100644 src/responder/kcm/kcm_renew.c
 create mode 100644 src/responder/kcm/kcm_renew.h

diff --git a/Makefile.am b/Makefile.am
index cae9eae838..27a4f47f28 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1844,6 +1844,8 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_ccache_secdb.c \
     src/responder/kcm/kcmsrv_ops.c \
     src/responder/kcm/kcmsrv_op_queue.c \
+    src/providers/krb5/krb5_opts.c \
+    src/providers/data_provider_opts.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
@@ -1861,6 +1863,7 @@ sssd_kcm_LDADD = \
     $(KRB5_LIBS) \
     $(JANSSON_LIBS) \
     $(SSSD_LIBS) \
+    $(CARES_LIBS) \
     $(UUID_LIBS) \
     $(SYSTEMD_DAEMON_LIBS) \
     $(SSSD_INTERNAL_LTLIBS) \
diff --git a/src/confdb/confdb.h b/src/confdb/confdb.h
index a2be227ddd..9161e5cb81 100644
--- a/src/confdb/confdb.h
+++ b/src/confdb/confdb.h
@@ -287,6 +287,12 @@
 #define CONFDB_KCM_MAX_CCACHES "max_ccaches"
 #define CONFDB_KCM_MAX_UID_CCACHES "max_uid_ccaches"
 #define CONFDB_KCM_MAX_CCACHE_SIZE "max_ccache_size"
+#define CONFDB_KCM_KRB5_LIFETIME "krb5_lifetime"
+#define CONFDB_KCM_KRB5_RENEWABLE_LIFETIME "krb5_renewable_lifetime"
+#define CONFDB_KCM_KRB5_RENEW_INTERVAL "krb5_renew_interval"
+#define CONFDB_KCM_KRB5_VALIDATE "krb5_validate"
+#define CONFDB_KCM_KRB5_CANONICALIZE "krb5_canonicalize"
+#define CONFDB_KCM_KRB5_AUTH_TIMEOUT "krb5_auth_timeout"
 
 /* Certificate mapping rules */
 #define CONFDB_CERTMAP_BASEDN "cn=certmap,cn=config"
diff --git a/src/config/cfg_rules.ini b/src/config/cfg_rules.ini
index bf2d03b824..a270bb7bbc 100644
--- a/src/config/cfg_rules.ini
+++ b/src/config/cfg_rules.ini
@@ -329,6 +329,12 @@ option = responder_idle_timeout
 option = max_ccaches
 option = max_uid_ccaches
 option = max_ccache_size
+option = krb5_lifetime
+option = krb5_renewable_lifetime
+option = krb5_renew_interval
+option = krb5_validate
+option = krb5_canonicalize
+option = krb5_auth_timeout
 
 # Session recording
 [rule/allowed_session_recording_options]
diff --git a/src/man/sssd-kcm.8.xml b/src/man/sssd-kcm.8.xml
index 14ba122a5c..5f81af7367 100644
--- a/src/man/sssd-kcm.8.xml
+++ b/src/man/sssd-kcm.8.xml
@@ -162,6 +162,42 @@ systemctl restart sssd-kcm.service
         </para>
     </refsect1>
 
+    <refsect1 id='renewals'>
+        <title>RENEWALS</title>
+        <para>
+            The sssd-kcm service can be configured to attempt TGT
+            renewal for renewable TGTs stored in the KCM ccache.
+            Renewals are only attempted when half of the ticket
+            lifetime has been reached. KCM Renewals are configured
+            when the following option is set in the [kcm] section:
+            <programlisting>
+krb5_renew_interval
+            </programlisting>
+        </para>
+        <para>
+            If [kcm] section renewal options are absent, SSSD will
+            fallback to using the krb5_* options listed below from
+            the *first* auth_provider=krb5 domain, if available.
+        </para>
+        <para>
+            The following krb5 options can be configured in the
+            [kcm] section to control renewal behavior, these
+            options are described in detail in the
+            <citerefentry>
+                <refentrytitle>sssd-krb5</refentrytitle>
+                <manvolnum>5</manvolnum>
+            </citerefentry> manual page.
+            <programlisting>
+    krb5_renew_interval
+    krb5_renew_lifetime
+    krb5_lifetime
+    krb5_validate
+    krb5_canonicalize
+    krb5_auth_timeout
+            </programlisting>
+        </para>
+    </refsect1>
+
     <refsect1 id='options'>
         <title>CONFIGURATION OPTIONS</title>
         <para>
@@ -249,6 +285,19 @@ systemctl restart sssd-kcm.service
                     </para>
                 </listitem>
             </varlistentry>
+            <varlistentry>
+                <term>krb5_renew_interval (integer)</term>
+                <listitem>
+                    <para>
+                        The time in seconds between TGT renewal attempts for
+                        all KCM-stored renewable tickets. Renewals only trigger
+                        when about half of their ticket lifetime is exceeded.
+                    </para>
+                    <para>
+                        Default: 0 (Automatic renewal is disabled)
+                    </para>
+                </listitem>
+            </varlistentry>
         </variablelist>
     </refsect1>
 
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index f1fc958be2..bdf515d4b0 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -27,6 +27,7 @@
 #include "responder/kcm/kcmsrv_ccache.h"
 #include "responder/kcm/kcmsrv_pvt.h"
 #include "responder/common/responder.h"
+#include "providers/krb5/krb5_common.h"
 #include "util/util.h"
 #include "util/sss_krb5.h"
 
@@ -254,6 +255,12 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
+    ret = kcm_get_renewal_config(kctx, &krb5_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM renewal config\n");
+        goto fail;
+    }
+
     /* Set up file descriptor limits */
     responder_set_fd_limit(kctx->fd_limit);
 
diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c
new file mode 100644
index 0000000000..c63facff33
--- /dev/null
+++ b/src/responder/kcm/kcm_renew.c
@@ -0,0 +1,297 @@
+extern struct dp_option default_krb5_opts[];
+
+static int kcm_get_auth_provider_options(struct kcm_ctx *kctx,
+                                         struct krb5_ctx *krb5_ctx)
+{
+    errno_t ret = 0;
+    char *lifetime_str = NULL;
+    char *rtime = NULL;
+    bool validate = false;
+    bool canonicalize = false;
+    int child_timeout = 0;
+    struct dp_option *opts;
+    const char *conf_path = NULL;
+    char *auth_provider = NULL;
+    struct sss_domain_info *domains = NULL;
+    struct sss_domain_info *dom = NULL;
+
+    ret = confdb_get_domains(kctx->rctx->cdb, &domains);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Cannot get domains\n");
+        goto done;
+    }
+
+    for (dom = domains; dom != NULL;
+            dom = get_next_domain(dom, SSS_GND_DESCEND)) {
+
+        conf_path = talloc_asprintf(kctx->rctx, CONFDB_DOMAIN_PATH_TMPL,
+                                    dom->name);
+        if (conf_path == NULL) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Out of memory\n");
+            ret = ENOMEM;
+            goto done;
+        }
+
+        ret = confdb_get_string(kctx->rctx->cdb,
+                                kctx->rctx,
+                                conf_path,
+                                CONFDB_DOMAIN_AUTH_PROVIDER,
+                                0, &auth_provider);
+
+        if (auth_provider != NULL) {
+            if (strcasecmp(auth_provider, "krb5") == 0) {
+                DEBUG(SSSDBG_TRACE_FUNC, "Checking auth provider options for: "
+                                         "[%s]\n", dom->name);
+                ret = dp_get_options(kctx->rctx, kctx->rctx->cdb, conf_path,
+                                     default_krb5_opts, KRB5_OPTS, &opts);
+                if (ret != EOK) {
+                    DEBUG(SSSDBG_CRIT_FAILURE, "dp_get_options failed\n");
+                    goto done;
+                }
+
+                /* Lifetime */
+                lifetime_str = dp_opt_get_string(opts, KRB5_LIFETIME);
+                if (lifetime_str != NULL) {
+                    ret = krb5_string_to_deltat(lifetime_str,
+                                                &krb5_ctx->lifetime);
+                    if (ret != 0) {
+                        DEBUG(SSSDBG_OP_FAILURE,
+                              "Failed to convert lifetime string.\n");
+                        goto done;
+                    }
+                    krb5_ctx->lifetime_str = lifetime_str;
+                }
+
+                /* Renewable lifetime */
+                rtime = dp_opt_get_string(opts, KRB5_RENEWABLE_LIFETIME);
+                if (rtime != 0) {
+                    ret = krb5_string_to_deltat(rtime, &krb5_ctx->rlife);
+                    if (ret != 0) {
+                        DEBUG(SSSDBG_OP_FAILURE,
+                              "Failed to convert renewable lifetime string.\n");
+                        goto done;
+                    }
+                    krb5_ctx->rlife_str = rtime;
+                }
+
+                /* Validate */
+                validate = dp_opt_get_bool(opts, KRB5_VALIDATE);
+                ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_VALIDATE, validate);
+                if (ret != 0) {
+                    DEBUG(SSSDBG_OP_FAILURE,
+                          "Cannot set dp opt krb5 validate\n");
+                    goto done;
+                }
+
+                /* Canonicalize */
+                canonicalize = dp_opt_get_bool(opts, KRB5_CANONICALIZE);
+                ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_CANONICALIZE,
+                                      canonicalize);
+                if (ret != 0) {
+                    DEBUG(SSSDBG_OP_FAILURE, "Cannot set dp opt krb5 "
+                                             "canonicalize\n");
+                    goto done;
+                }
+
+                /* Child timeout */
+                child_timeout = dp_opt_get_int(opts, KRB5_AUTH_TIMEOUT);
+                if (child_timeout > 0) {
+                    ret = dp_opt_set_int(krb5_ctx->opts, KRB5_AUTH_TIMEOUT,
+                                         child_timeout);
+                    if (ret != 0) {
+                        DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child "
+                                                 "timeout\n");
+                        goto done;
+                    }
+                }
+
+                break;
+            }
+        }
+    }
+
+    ret = EOK;
+done:
+    return ret;
+}
+
+static int kcm_get_krb5_config(struct kcm_ctx *kctx,
+                               struct krb5_ctx *krb5_ctx)
+{
+    errno_t ret;
+    char *rtime = NULL;
+    char *lifetime_str = NULL;
+    bool validate = false;
+    bool canonicalize = false;
+    int child_timeout = 0;
+    bool kcm_renew_option_defined = false;
+
+    /* Lifetime */
+    ret = confdb_get_string(kctx->rctx->cdb,
+                            kctx->rctx,
+                            kctx->rctx->confdb_service_path,
+                            CONFDB_KCM_KRB5_LIFETIME,
+                            0, &lifetime_str);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get confdb lifetime\n");
+        goto done;
+    }
+
+    if (lifetime_str != NULL) {
+        ret = krb5_string_to_deltat(lifetime_str, &krb5_ctx->lifetime);
+        if (ret != 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to convert lifetime string.\n");
+            goto done;
+        }
+        kcm_renew_option_defined = true;
+        krb5_ctx->lifetime_str = lifetime_str;
+    }
+
+    /* Renewable lifetime */
+    ret = confdb_get_string(kctx->rctx->cdb,
+                            kctx->rctx,
+                            kctx->rctx->confdb_service_path,
+                            CONFDB_KCM_KRB5_RENEWABLE_LIFETIME,
+                            0, &rtime);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get confdb renewable lifetime\n");
+        goto done;
+    }
+
+    if (rtime != 0) {
+        ret = krb5_string_to_deltat(rtime, &krb5_ctx->rlife);
+        if (ret != 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to convert renewable lifetime "
+                                     "string.\n");
+            goto done;
+        }
+        kcm_renew_option_defined = true;
+        krb5_ctx->rlife_str = rtime;
+    }
+
+    /* Validate */
+    ret = confdb_get_bool(kctx->rctx->cdb,
+                          kctx->rctx->confdb_service_path,
+                          CONFDB_KCM_KRB5_VALIDATE,
+                          false, &validate);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get confdb krb5 validate\n");
+        goto done;
+    }
+
+    ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_VALIDATE, validate);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot set dp opt krb5 validate\n");
+        goto done;
+    }
+
+    /* Canonicalize */
+    ret = confdb_get_bool(kctx->rctx->cdb,
+                          kctx->rctx->confdb_service_path,
+                          CONFDB_KCM_KRB5_CANONICALIZE,
+                          false, &canonicalize);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get confdb krb5 canonicalize\n");
+        goto done;
+    }
+    ret = dp_opt_set_bool(krb5_ctx->opts, KRB5_CANONICALIZE, canonicalize);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot set dp opt krb5 canonicalize\n");
+        goto done;
+    }
+
+    /* Child timeout */
+    ret = confdb_get_int(kctx->rctx->cdb,
+                         kctx->rctx->confdb_service_path,
+                         CONFDB_KCM_KRB5_AUTH_TIMEOUT,
+                         0, &child_timeout);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get krb5 child timeout\n");
+        goto done;
+    }
+
+    if (child_timeout > 0) {
+        ret = dp_opt_set_int(krb5_ctx->opts, KRB5_AUTH_TIMEOUT, child_timeout);
+        if (ret != 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "Cannot set krb5 child timeout\n");
+            goto done;
+        }
+        kcm_renew_option_defined = true;
+    }
+
+    /* Fallback to first auth_provider=krb5 domain */
+    if (kcm_renew_option_defined == false) {
+        ret = kcm_get_auth_provider_options(kctx, krb5_ctx);
+        if (ret != EOK) {
+            /* Not fatal */
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to read auth provider options\n");
+        }
+    }
+
+    ret = EOK;
+done:
+    return ret;
+}
+
+int kcm_get_renewal_config(struct kcm_ctx *kctx,
+                           struct krb5_ctx **_krb5_ctx)
+{
+    int ret;
+    struct krb5_ctx *krb5_ctx;
+    int i;
+
+    krb5_ctx = talloc_zero(kctx->rctx, struct krb5_ctx);
+    if (krb5_ctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "fatal error allocating krb5_ctx\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    /* Set default Kerberos options */
+    krb5_ctx->opts = talloc_zero_array(krb5_ctx, struct dp_option, KRB5_OPTS);
+    if (!krb5_ctx->opts) goto done;
+    for (i = 0; i < KRB5_OPTS; i++) {
+        krb5_ctx->opts[i].opt_name = default_krb5_opts[i].opt_name;
+        krb5_ctx->opts[i].type = default_krb5_opts[i].type;
+        krb5_ctx->opts[i].def_val = default_krb5_opts[i].def_val;
+        switch (krb5_ctx->opts[i].type) {
+            case DP_OPT_STRING:
+                ret = dp_opt_set_string(krb5_ctx->opts, i,
+                                        default_krb5_opts[i].def_val.string);
+                break;
+            case DP_OPT_BLOB:
+                ret = dp_opt_set_blob(krb5_ctx->opts, i,
+                                      default_krb5_opts[i].def_val.blob);
+                break;
+            case DP_OPT_NUMBER:
+                ret = dp_opt_set_int(krb5_ctx->opts, i,
+                                     default_krb5_opts[i].def_val.number);
+                break;
+            case DP_OPT_BOOL:
+                ret = dp_opt_set_bool(krb5_ctx->opts, i,
+                                      default_krb5_opts[i].def_val.boolean);
+                break;
+        }
+        if (ret != 0) {
+            DEBUG(SSSDBG_OP_FAILURE, "Failed setting default KCM kerberos "
+                                     "options\n");
+            talloc_free(krb5_ctx->opts);
+            goto done;
+        }
+    }
+
+    /* Override with config options */
+    kcm_get_krb5_config(kctx, krb5_ctx);
+
+    *_krb5_ctx = krb5_ctx;
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(krb5_ctx);
+    }
+    return ret;
+}
+
+
diff --git a/src/responder/kcm/kcm_renew.h b/src/responder/kcm/kcm_renew.h
new file mode 100644
index 0000000000..0bb1bf8d5c
--- /dev/null
+++ b/src/responder/kcm/kcm_renew.h
@@ -0,0 +1,3 @@
+int kcm_get_renewal_config(struct kcm_ctx *kctx,
+                           struct krb5_ctx **_krb5_ctx);
+

From ceb5adc5d0dd7bb1aaa7919f688f7c1a008d9b7c Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Tue, 24 Nov 2020 16:18:08 -0500
Subject: [PATCH 02/10] KCM: Add renewable KCM tickets to renew table

Extract TGT times from existing KCM ccache credentials, add
times and ccache data(name, CACHEID) to renew table to be processed
for renewal elsewhere.
---
 Makefile.am                              |   2 +
 src/responder/kcm/kcm.c                  |  20 +-
 src/responder/kcm/kcm_renew.c            | 259 ++++++++++++++++++++++-
 src/responder/kcm/kcm_renew.h            |  45 +++-
 src/responder/kcm/kcmsrv_ccache.c        |  21 ++
 src/responder/kcm/kcmsrv_ccache.h        |  11 +
 src/responder/kcm/kcmsrv_ccache_be.h     |   7 +
 src/responder/kcm/kcmsrv_ccache_binary.c |   2 +-
 src/responder/kcm/kcmsrv_ccache_secdb.c  | 205 ++++++++++++++++++
 src/util/secrets/secrets.c               |  58 +++++
 src/util/secrets/secrets.h               |   4 +
 src/util/sss_krb5.c                      |  10 +
 src/util/sss_krb5.h                      |   5 +
 13 files changed, 641 insertions(+), 8 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index 27a4f47f28..a98727914c 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -771,6 +771,7 @@ dist_noinst_HEADERS = \
     src/responder/secrets/secsrv_local.h \
     src/responder/secrets/secsrv_proxy.h \
     src/responder/kcm/kcm.h \
+    src/responder/kcm/kcm_renew.h \
     src/responder/kcm/kcmsrv_pvt.h \
     src/responder/kcm/kcmsrv_ccache.h \
     src/responder/kcm/kcmsrv_ccache_pvt.h \
@@ -1835,6 +1836,7 @@ endif
 if BUILD_KCM
 sssd_kcm_SOURCES = \
     src/responder/kcm/kcm.c \
+    src/responder/kcm/kcm_renew.c \
     src/responder/kcm/kcmsrv_cmd.c \
     src/responder/kcm/kcmsrv_ccache.c \
     src/responder/kcm/kcmsrv_ccache_binary.c \
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index bdf515d4b0..7512832a0c 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -26,6 +26,7 @@
 #include "responder/kcm/kcm.h"
 #include "responder/kcm/kcmsrv_ccache.h"
 #include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcm_renew.h"
 #include "responder/common/responder.h"
 #include "providers/krb5/krb5_common.h"
 #include "util/util.h"
@@ -211,6 +212,7 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
 {
     struct resp_ctx *rctx;
     struct kcm_ctx *kctx;
+    time_t renew_intv = 0;
     int ret;
 
     rctx = talloc_zero(mem_ctx, struct resp_ctx);
@@ -255,12 +257,28 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
-    ret = kcm_get_renewal_config(kctx, &krb5_ctx);
+    ret = kcm_get_renewal_config(kctx, &krb5_ctx, &renew_intv);
     if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM renewal config\n");
         goto fail;
     }
 
+    if (renew_intv > 0) {
+        ret = kcm_renewal_init(rctx, krb5_ctx, ev, kctx->kcm_data->db, renew_intv);
+        if (ret != 0) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  "fatal error initializing KCM renewals\n");
+            goto fail;
+        }
+
+        ret = kcm_ccdb_renew_init(rctx, krb5_ctx, ev, kctx->kcm_data->db);
+        if (ret != 0) {
+            DEBUG(SSSDBG_FATAL_FAILURE,
+                  "fatal error initializing KCM ccdb renewals\n");
+            goto fail;
+        }
+    }
+
     /* Set up file descriptor limits */
     responder_set_fd_limit(kctx->fd_limit);
 
diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c
index c63facff33..0d5cd97ce3 100644
--- a/src/responder/kcm/kcm_renew.c
+++ b/src/responder/kcm/kcm_renew.c
@@ -1,7 +1,69 @@
+/*
+    SSSD
+
+    KCM Kerberos renewals -- Renew a TGT automatically
+
+    Authors:
+        Justin Stephenson <jstep...@redhat.com>
+
+    Copyright (C) 2020 Red Hat
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "util/util.h"
+#include "providers/krb5/krb5_common.h"
+#include "providers/krb5/krb5_auth.h"
+#include "providers/krb5/krb5_utils.h"
+#include "providers/krb5/krb5_ccache.h"
+#include "responder/kcm/kcmsrv_ccache.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcm_renew.h"
+
+#define INITIAL_TGT_TABLE_SIZE 10
+
 extern struct dp_option default_krb5_opts[];
 
+struct renew_tgt_ctx {
+    hash_table_t *tgt_table;
+    struct tevent_context *ev;
+    struct krb5_ctx *krb5_ctx;
+    struct resp_ctx *rctx;
+    struct kcm_ccdb *db;
+    time_t timer_interval;
+    struct tevent_timer *te;
+};
+
+struct renew_data {
+    const char *ccname;
+    uid_t uid;
+    gid_t gid;
+    time_t start_time;
+    time_t lifetime;
+    time_t start_renew_at;
+    time_t renew_till;
+};
+
+struct auth_data {
+    struct krb5_ctx *krb5_ctx;
+    struct renew_data *renew_data;
+    hash_table_t *table;
+    const char *key;
+};
+
 static int kcm_get_auth_provider_options(struct kcm_ctx *kctx,
-                                         struct krb5_ctx *krb5_ctx)
+                                         struct krb5_ctx *krb5_ctx,
+                                         time_t *_renew_intv)
 {
     errno_t ret = 0;
     char *lifetime_str = NULL;
@@ -116,7 +178,8 @@ static int kcm_get_auth_provider_options(struct kcm_ctx *kctx,
 }
 
 static int kcm_get_krb5_config(struct kcm_ctx *kctx,
-                               struct krb5_ctx *krb5_ctx)
+                               struct krb5_ctx *krb5_ctx,
+                               time_t *_renew_intv)
 {
     errno_t ret;
     char *rtime = NULL;
@@ -125,6 +188,33 @@ static int kcm_get_krb5_config(struct kcm_ctx *kctx,
     bool canonicalize = false;
     int child_timeout = 0;
     bool kcm_renew_option_defined = false;
+    char *renew_intv_str = NULL;
+    time_t renew_intv = 0;
+    krb5_deltat renew_interval_delta;
+    krb5_error_code kerr;
+
+    /* Renew interval */
+    ret = confdb_get_string(kctx->rctx->cdb,
+                            kctx->rctx,
+                            kctx->rctx->confdb_service_path,
+                            CONFDB_KCM_KRB5_RENEW_INTERVAL,
+                            0, &renew_intv_str);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Cannot get renew interval\n");
+        goto done;
+    }
+
+    if (renew_intv_str != NULL) {
+        kerr = krb5_string_to_deltat(renew_intv_str, &renew_interval_delta);
+        if (kerr != 0) {
+            DEBUG(SSSDBG_FATAL_FAILURE, "krb5_string_to_deltat failed\n");
+            ret = ENOMEM;
+            goto done;
+        }
+
+        kcm_renew_option_defined = true;
+        renew_intv = renew_interval_delta;
+    }
 
     /* Lifetime */
     ret = confdb_get_string(kctx->rctx->cdb,
@@ -221,20 +311,22 @@ static int kcm_get_krb5_config(struct kcm_ctx *kctx,
 
     /* Fallback to first auth_provider=krb5 domain */
     if (kcm_renew_option_defined == false) {
-        ret = kcm_get_auth_provider_options(kctx, krb5_ctx);
+        ret = kcm_get_auth_provider_options(kctx, krb5_ctx, &renew_intv);
         if (ret != EOK) {
             /* Not fatal */
             DEBUG(SSSDBG_OP_FAILURE, "Failed to read auth provider options\n");
         }
     }
 
+    *_renew_intv = renew_intv;
     ret = EOK;
 done:
     return ret;
 }
 
 int kcm_get_renewal_config(struct kcm_ctx *kctx,
-                           struct krb5_ctx **_krb5_ctx)
+                           struct krb5_ctx **_krb5_ctx,
+                           time_t *renew_intv)
 {
     int ret;
     struct krb5_ctx *krb5_ctx;
@@ -282,7 +374,7 @@ int kcm_get_renewal_config(struct kcm_ctx *kctx,
     }
 
     /* Override with config options */
-    kcm_get_krb5_config(kctx, krb5_ctx);
+    kcm_get_krb5_config(kctx, krb5_ctx, renew_intv);
 
     *_krb5_ctx = krb5_ctx;
     ret = EOK;
@@ -294,4 +386,161 @@ int kcm_get_renewal_config(struct kcm_ctx *kctx,
     return ret;
 }
 
+static void kcm_renew_tgt_timer_handler(struct tevent_context *ev,
+                                        struct tevent_timer *te,
+                                        struct timeval current_time,
+										void *data)
+{
+    struct renew_tgt_ctx *renew_tgt_ctx = talloc_get_type(data,
+                                                          struct renew_tgt_ctx);
+    errno_t ret;
+    struct timeval next;
+
+    /* forget the timer event, it will be freed by the tevent timer loop */
+    renew_tgt_ctx->te = NULL;
+
+	/* Add any renew-applicable KCM tickets to renew table */
+	ret = kcm_ccdb_renew_init(renew_tgt_ctx->rctx, renew_tgt_ctx->krb5_ctx,
+                              ev, renew_tgt_ctx->db);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to add KCM tickets to table.\n");
+        talloc_zfree(renew_tgt_ctx);
+        return;
+    }
+
+    next = tevent_timeval_current_ofs(renew_tgt_ctx->timer_interval, 0);
+    renew_tgt_ctx->te = tevent_add_timer(ev, renew_tgt_ctx,
+                                         next, kcm_renew_tgt_timer_handler,
+                                         renew_tgt_ctx);
+    if (renew_tgt_ctx->te == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Unable to setup timer\n");
+        talloc_zfree(renew_tgt_ctx);
+        return;
+    }
+}
+
+errno_t kcm_add_tgt_to_renew_table(struct krb5_ctx *krb5_ctx,
+                                   const char *ccname,
+                                   uid_t uid,
+                                   gid_t gid,
+                                   struct tgt_times *tgtt,
+                                   const char *upn)
+{
+    int ret;
+    struct renew_data *renew_data = NULL;
+    struct renew_data *existing_tgt_entry = NULL;
+
+    if (krb5_ctx->renew_tgt_ctx == NULL) {
+        DEBUG(SSSDBG_TRACE_LIBS, "Renew context not initialized, "
+                  "automatic renewal not available.\n");
+        return EOK;
+    }
+
+    if (upn == NULL || ccname == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Missing user principal name or ccname.\n");
+        return EINVAL;
+    }
+
+    renew_data = talloc_zero(krb5_ctx->renew_tgt_ctx, struct renew_data);
+    if (renew_data == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    existing_tgt_entry = talloc_zero(krb5_ctx->renew_tgt_ctx, struct renew_data);
+    if (existing_tgt_entry == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+        ret = ENOMEM;
+        goto done;
+    }
+
+    renew_data->ccname = talloc_strdup(renew_data, ccname);
+    renew_data->uid = uid;
+    renew_data->gid = gid;
+
+    renew_data->start_time = tgtt->starttime;
+    renew_data->lifetime = tgtt->endtime;
+    renew_data->start_renew_at = (time_t) (tgtt->starttime +
+                                        0.5 *(tgtt->endtime - tgtt->starttime));
+    renew_data->renew_till = tgtt->renew_till;
+
+    /* Overwrite existing entry with updated times */
+    if (sss_ptr_hash_has_key(krb5_ctx->renew_tgt_ctx->tgt_table, upn)) {
+        existing_tgt_entry = sss_ptr_hash_lookup(krb5_ctx->renew_tgt_ctx->tgt_table, upn,
+                                         struct renew_data);
+        if (strcmp(existing_tgt_entry->ccname, ccname) == 0) {
+            sss_ptr_hash_delete(krb5_ctx->renew_tgt_ctx->tgt_table, upn, true);
+        }
+    }
+
+    ret = sss_ptr_hash_add(krb5_ctx->renew_tgt_ctx->tgt_table,
+                     upn, renew_data, struct renew_data);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sss_ptr_hash_add failed\n");
+        ret = EFAULT;
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_LIBS,
+          "Added [%s][%s] for renewal at [%s].",
+          upn, renew_data->ccname,
+          ctime(&renew_data->start_renew_at));
+
+    ret = EOK;
+
+done:
+    if (ret != EOK) {
+        talloc_free(renew_data);
+    }
+    return ret;
+}
+
+errno_t kcm_renewal_init(struct resp_ctx *rctx,
+                         struct krb5_ctx *krb5_ctx,
+                         struct tevent_context *ev,
+                         struct kcm_ccdb *db,
+                         time_t renew_intv)
+{
+    int ret;
+    struct timeval next;
 
+    krb5_ctx->renew_tgt_ctx = talloc_zero(krb5_ctx, struct renew_tgt_ctx);
+    if (krb5_ctx->renew_tgt_ctx == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+        return ENOMEM;
+    }
+
+    krb5_ctx->renew_tgt_ctx->tgt_table = sss_ptr_hash_create(krb5_ctx->renew_tgt_ctx,
+                                                             NULL,
+                                                             NULL);
+    if (krb5_ctx->renew_tgt_ctx->tgt_table == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "sss_ptr_hash_create failed.\n");
+        goto fail;
+    }
+
+    krb5_ctx->renew_tgt_ctx->rctx = rctx;
+    krb5_ctx->renew_tgt_ctx->krb5_ctx = krb5_ctx;
+    krb5_ctx->renew_tgt_ctx->db = db,
+    krb5_ctx->renew_tgt_ctx->ev = ev;
+    krb5_ctx->renew_tgt_ctx->timer_interval = renew_intv;
+
+    /* Check KCM for tickets to renew */
+    next = tevent_timeval_current_ofs(krb5_ctx->renew_tgt_ctx->timer_interval,
+                                      0);
+    krb5_ctx->renew_tgt_ctx->te = tevent_add_timer(ev, krb5_ctx->renew_tgt_ctx,
+                                                   next,
+                                                   kcm_renew_tgt_timer_handler,
+                                                   krb5_ctx->renew_tgt_ctx);
+    if (krb5_ctx->renew_tgt_ctx->te == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    return EOK;
+
+fail:
+    talloc_zfree(krb5_ctx->renew_tgt_ctx);
+    return ret;
+}
diff --git a/src/responder/kcm/kcm_renew.h b/src/responder/kcm/kcm_renew.h
index 0bb1bf8d5c..5aed33b5ac 100644
--- a/src/responder/kcm/kcm_renew.h
+++ b/src/responder/kcm/kcm_renew.h
@@ -1,3 +1,46 @@
+/*
+    SSSD
+
+    KCM Renewal, private header file
+
+    Authors:
+        Justin Stephenson <jstep...@redhat.com>
+
+    Copyright (C) 2020 Red Hat
+
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef __KCM_RENEW_H__
+#define __KCM_RENEW_H__
+
+#include "providers/krb5/krb5_common.h"
+#include "src/providers/krb5/krb5_ccache.h"
+#include "util/sss_ptr_hash.h"
+
 int kcm_get_renewal_config(struct kcm_ctx *kctx,
-                           struct krb5_ctx **_krb5_ctx);
+                           struct krb5_ctx **_krb5_ctx,
+                           time_t *_renew_intv);
+
+errno_t kcm_renewal_init(struct resp_ctx *rctx, struct krb5_ctx *kctx,
+                         struct tevent_context *ev, struct kcm_ccdb *db,
+                         time_t renew_intv);
+errno_t kcm_add_tgt_to_renew_table(struct krb5_ctx *kctx,
+                                   const char *ccname,
+                                   uid_t uid, gid_t gid,
+                                   struct tgt_times *tgtt,
+                                   const char *upn);
 
+#endif /* __KCM_RENEW_H__ */
diff --git a/src/responder/kcm/kcmsrv_ccache.c b/src/responder/kcm/kcmsrv_ccache.c
index 60eacd4516..d39f5a10b0 100644
--- a/src/responder/kcm/kcmsrv_ccache.c
+++ b/src/responder/kcm/kcmsrv_ccache.c
@@ -293,6 +293,27 @@ struct sss_iobuf *kcm_cred_get_creds(struct kcm_cred *crd)
     return crd ? crd->cred_blob : NULL;
 }
 
+errno_t kcm_ccdb_renew_init(TALLOC_CTX *mem_ctx,
+                            struct krb5_ctx *kctx,
+                            struct tevent_context *ev,
+                            struct kcm_ccdb *ccdb)
+{
+    errno_t ret;
+
+    if (kctx == NULL || ev == NULL || ccdb == NULL) {
+        ret = EINVAL;
+        return ret;
+    }
+
+    ret = ccdb->ops->renew_init(kctx, ev, ccdb);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failure to execute ccdb renewal init\n");
+        return ret;
+    }
+
+    return EOK;
+}
+
 struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
                                struct tevent_context *ev,
                                struct confdb_ctx *cdb,
diff --git a/src/responder/kcm/kcmsrv_ccache.h b/src/responder/kcm/kcmsrv_ccache.h
index 77cf8f61d5..76e7dd89b3 100644
--- a/src/responder/kcm/kcmsrv_ccache.h
+++ b/src/responder/kcm/kcmsrv_ccache.h
@@ -29,6 +29,7 @@
 #include "util/util.h"
 #include "util/sss_iobuf.h"
 #include "util/util_creds.h"
+#include "providers/krb5/krb5_common.h"
 #include "responder/kcm/kcmsrv_pvt.h"
 
 #define UUID_BYTES    16
@@ -140,6 +141,13 @@ struct kcm_ccdb *kcm_ccdb_init(TALLOC_CTX *mem_ctx,
                                struct confdb_ctx *cdb,
                                const char *confdb_service_path,
                                enum kcm_ccdb_be cc_be);
+/*
+ * Initialize KCM renewals
+ */
+errno_t kcm_ccdb_renew_init(TALLOC_CTX *mem_ctx,
+                            struct krb5_ctx *kctx,
+                            struct tevent_context *ev,
+                            struct kcm_ccdb *cdb);
 
 /*
  * In KCM, each ccache name is usually in the form of "UID:<num>
@@ -376,4 +384,7 @@ errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx,
                                        struct kcm_ccache *cc,
                                        struct sss_iobuf **_payload);
 
+errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx,
+                        struct sss_iobuf *buf,
+                        krb5_data *out);
 #endif /* _KCMSRV_CCACHE_H_ */
diff --git a/src/responder/kcm/kcmsrv_ccache_be.h b/src/responder/kcm/kcmsrv_ccache_be.h
index 166af3a764..171acbbcfd 100644
--- a/src/responder/kcm/kcmsrv_ccache_be.h
+++ b/src/responder/kcm/kcmsrv_ccache_be.h
@@ -34,6 +34,11 @@ typedef errno_t
                 struct confdb_ctx *cdb,
                 const char *confdb_service_path);
 
+typedef errno_t
+(*ccdb_renew_init_fn)(struct krb5_ctx *kctx,
+                      struct tevent_context *ev,
+                      struct kcm_ccdb *cdb);
+
 typedef struct tevent_req *
 (*ccdb_nextid_send_fn)(TALLOC_CTX *mem_ctx,
                        struct tevent_context *ev,
@@ -164,6 +169,8 @@ typedef errno_t
 struct kcm_ccdb_ops {
     ccdb_init_fn init;
 
+    ccdb_renew_init_fn renew_init;
+
     ccdb_nextid_send_fn nextid_send;
     ccdb_nextid_recv_fn nextid_recv;
 
diff --git a/src/responder/kcm/kcmsrv_ccache_binary.c b/src/responder/kcm/kcmsrv_ccache_binary.c
index 7bfdbf13bf..d2d5d2fa1e 100644
--- a/src/responder/kcm/kcmsrv_ccache_binary.c
+++ b/src/responder/kcm/kcmsrv_ccache_binary.c
@@ -141,7 +141,7 @@ errno_t kcm_ccache_to_sec_input_binary(TALLOC_CTX *mem_ctx,
     return ret;
 }
 
-static errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx,
+errno_t bin_to_krb_data(TALLOC_CTX *mem_ctx,
                                struct sss_iobuf *buf,
                                krb5_data *out)
 {
diff --git a/src/responder/kcm/kcmsrv_ccache_secdb.c b/src/responder/kcm/kcmsrv_ccache_secdb.c
index ea5c8f9ee3..02bdb1b230 100644
--- a/src/responder/kcm/kcmsrv_ccache_secdb.c
+++ b/src/responder/kcm/kcmsrv_ccache_secdb.c
@@ -27,8 +27,11 @@
 #include "util/util.h"
 #include "util/secrets/secrets.h"
 #include "util/crypto/sss_crypto.h"
+#include "util/sss_krb5.h"
 #include "responder/kcm/kcmsrv_ccache_pvt.h"
 #include "responder/kcm/kcmsrv_ccache_be.h"
+#include "responder/kcm/kcm_renew.h"
+#include "providers/krb5/krb5_ccache.h"
 
 #define KCM_SECDB_URL        "/kcm/persistent"
 #define KCM_SECDB_BASE_FMT    KCM_SECDB_URL"/%"SPRIuid"/"
@@ -577,6 +580,206 @@ static errno_t ccdb_secdb_init(struct kcm_ccdb *db,
     return EOK;
 }
 
+static errno_t renew_check_creds(struct krb5_ctx *krb5_ctx,
+                                 struct ccdb_secdb_state *state,
+                                 struct sss_sec_ctx *sctx,
+                                 struct cli_creds *cli,
+                                 char *key)
+{
+    errno_t ret;
+    char *secdb_key = NULL;
+    uuid_t uuid;
+    struct kcm_ccache *cc;
+    struct kcm_cred *cred;
+    struct tgt_times tgtt;
+    char *client_name = NULL;
+    krb5_context krb_context = NULL;
+    krb5_data *cred_data = NULL;
+    krb5_creds *extracted_creds;
+    krb5_error_code kerr = 0;
+    time_t now;
+
+    ret = sec_key_get_uuid(key, uuid);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+                "Malformed key, cannot get UUID\n");
+        goto immediate;
+    }
+
+    /* Get ccache by UUID */
+    ret = key_by_uuid(state, sctx, cli, uuid, &secdb_key);
+    if (ret == ENOENT) {
+        ret = EOK;
+        goto immediate;
+    } else if (ret != EOK) {
+        goto immediate;
+    }
+
+    ret = secdb_get_cc(state, sctx, secdb_key, cli, &cc);
+    if (ret != EOK) {
+        goto immediate;
+    }
+    /* Extract credentials */
+    if (cc != NULL) {
+        DEBUG(SSSDBG_TRACE_INTERNAL,
+              "Checking ccache [%s] for creds to renew\n", cc->name);
+        for (cred = kcm_cc_get_cred(cc); cred != NULL; cred = kcm_cc_next_cred(cred)) {
+            kerr = krb5_init_context(&krb_context);
+            if (kerr != 0) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "Failed to init context\n");
+                goto immediate;
+            }
+
+            cred_data = talloc_zero(state, struct _krb5_data);
+            if (cred_data == NULL) {
+                return ENOMEM;
+            }
+            ret = get_krb5_data_from_cred(state, cred->cred_blob, cred_data);
+            if (ret != EOK) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "Failed to convert to krb5_data\n");
+                goto immediate;
+            }
+
+            kerr = krb5_unmarshal_credentials(krb_context, cred_data,
+                                              &extracted_creds);
+            if (kerr != 0) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "Failed to unmarshal creds\n");
+                goto immediate;
+            }
+
+            kerr = krb5_unparse_name(krb_context, extracted_creds->client,
+                                     &client_name);
+            if (kerr != 0) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "Failed unparsing name\n");
+                goto immediate;
+            }
+
+            DEBUG(SSSDBG_TRACE_INTERNAL, "Found credentials for [%s]\n",
+                                         client_name);
+            memset(&tgtt, 0, sizeof(tgtt));
+            tgtt.authtime = extracted_creds->times.authtime;
+            tgtt.starttime = extracted_creds->times.starttime;
+            tgtt.endtime = extracted_creds->times.endtime;
+            tgtt.renew_till = extracted_creds->times.renew_till;
+
+            now = time(NULL);
+            if (tgtt.renew_till >= tgtt.endtime && tgtt.renew_till >= now
+                && tgtt.endtime >= now) {
+                ret = kcm_add_tgt_to_renew_table(krb5_ctx, cc->name,
+                                                 cc->owner.uid, cc->owner.gid,
+                                                 &tgtt, client_name);
+            } else {
+                DEBUG(SSSDBG_TRACE_INTERNAL, "Time not applicable\n");
+            }
+
+            talloc_zfree(cred_data);
+        }
+    }
+
+    ret = EOK;
+
+immediate:
+    if (krb_context != NULL) {
+        krb5_free_context(krb_context);
+    }
+    if (kerr != 0) {
+        return EIO;
+    }
+
+    return ret;
+}
+
+static errno_t renew_check_ccaches(struct krb5_ctx *krb5_ctx,
+                                   struct tevent_context *ev,
+                                   struct sss_sec_ctx *sctx,
+                                   uid_t *uids,
+                                   size_t uid_count)
+{
+    struct cli_creds *cli_cred;
+    char **keys = NULL;
+    size_t nkeys;
+    struct ccdb_secdb_state *state = NULL;
+    struct sss_sec_req *sreq = NULL;
+    struct passwd *pwd = NULL;
+    errno_t ret;
+
+	cli_cred = talloc_zero(ev, struct cli_creds);
+    if (cli_cred == NULL) {
+        return ENOMEM;
+    }
+
+    /* Get UUID list for name */
+    for (int i = 0; i < uid_count; i++) {
+        pwd = getpwuid(uids[i]);
+        if (pwd == NULL) {
+            talloc_zfree(cli_cred);
+            DEBUG(SSSDBG_OP_FAILURE, "Failed to get pwd entry for [%d]\n",
+                                     uids[i]);
+            return EINVAL;
+        }
+
+        cli_cred->ucred.uid = pwd->pw_uid;
+        cli_cred->ucred.gid = pwd->pw_gid;
+
+        ret = secdb_container_url_req(state, sctx, cli_cred, &sreq);
+        if (ret != EOK) {
+            talloc_zfree(cli_cred);
+            return EINVAL;
+        }
+
+        ret = sss_sec_list(state, sreq, &keys, &nkeys);
+        if (ret == ENOENT) {
+            nkeys = 0;
+        } else if (ret != EOK) {
+            talloc_zfree(cli_cred);
+            return EINVAL;
+        }
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Found [%zu] ccaches under uid [%u]\n",
+                                     nkeys, uids[i]);
+
+        /* Examine credentials and add renewal-ready tgts to renewal table */
+        for (size_t j = 0; j < nkeys; j++) {
+            ret = renew_check_creds(krb5_ctx, state, sctx, cli_cred, keys[j]);
+            if (ret != EOK && ret != ENOENT) {
+                talloc_zfree(cli_cred);
+                DEBUG(SSSDBG_OP_FAILURE, "Failed to check credentials\n");
+                return EINVAL;
+            }
+        }
+    }
+
+    return EOK;
+}
+
+static errno_t ccdb_secdb_renew_init(struct krb5_ctx *krb5_ctx,
+                                     struct tevent_context *ev,
+                                     struct kcm_ccdb *db)
+{
+    struct ccdb_secdb *secdb = talloc_get_type(db->db_handle, struct ccdb_secdb);
+    uid_t *uid_list = NULL;
+    size_t uid_list_count = 0;
+    errno_t ret = 0;
+
+    /* Search for ccache uids */
+    ret = sss_sec_list_cc_uids(secdb->sctx, &uid_list, &uid_list_count);
+    if (ret != EOK && ret != ENOENT) {
+        DEBUG(SSSDBG_OP_FAILURE, "Error retrieving ccache uid list\n");
+        return ret;
+    }
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Found [%lu] ccache uids\n", uid_list_count);
+
+    if (uid_list_count > 0) {
+        ret = renew_check_ccaches(krb5_ctx, ev, secdb->sctx,
+                                  uid_list, uid_list_count);
+        if (ret != EOK && ret != ENOENT) {
+            DEBUG(SSSDBG_OP_FAILURE, "Error checking ccaches in secdb\n");
+            return ret;
+        }
+    }
+
+    return EOK;
+}
+
 struct ccdb_secdb_nextid_state {
     unsigned int nextid;
 };
@@ -1468,6 +1671,8 @@ static errno_t ccdb_secdb_delete_recv(struct tevent_req *req)
 const struct kcm_ccdb_ops ccdb_secdb_ops = {
     .init = ccdb_secdb_init,
 
+    .renew_init = ccdb_secdb_renew_init,
+
     .nextid_send = ccdb_secdb_nextid_send,
     .nextid_recv = ccdb_secdb_nextid_recv,
 
diff --git a/src/util/secrets/secrets.c b/src/util/secrets/secrets.c
index c6310b5852..92678c43dc 100644
--- a/src/util/secrets/secrets.c
+++ b/src/util/secrets/secrets.c
@@ -26,6 +26,7 @@
 #include "config.h"
 
 #include "util/util.h"
+#include "util/strtonum.h"
 #include "util/crypto/sss_crypto.h"
 #include "util/secrets/sec_pvt.h"
 #include "util/secrets/secrets.h"
@@ -937,6 +938,63 @@ static char *local_dn_to_path(TALLOC_CTX *mem_ctx,
     return path;
 }
 
+/* Unfiltered request for names */
+errno_t sss_sec_list_cc_uids(struct sss_sec_ctx *sec,
+							  uid_t **_uid_list,
+							  size_t *_uid_list_count)
+{
+
+	TALLOC_CTX *tmp_ctx;
+    struct ldb_result *res;
+    struct ldb_dn *dn;
+    const struct ldb_val *name_val;
+    static const char *attrs[] = { "distinguishedName", NULL };
+	uid_t *uid_list;
+    int ret;
+
+    tmp_ctx = talloc_new(NULL);
+    if (tmp_ctx == NULL) {
+        return ENOMEM;
+    }
+
+    dn = ldb_dn_new(tmp_ctx, sec->ldb, "cn=persistent,cn=kcm");
+
+    ret = ldb_search(sec->ldb, tmp_ctx, &res, dn, LDB_SCOPE_SUBTREE,
+           attrs, "%s", "(type=container)");
+    if (ret != EOK) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "ldb_search returned [%d]: %s\n", ret, ldb_strerror(ret));
+        return EIO;
+    }
+
+    if (res->count == 0) {
+        DEBUG(SSSDBG_TRACE_LIBS, "No uids found\n");
+        return EOK;
+    }
+
+	uid_list = talloc_array(tmp_ctx, uint32_t, res->count);
+    if (uid_list == NULL) {
+        return ENOMEM;
+    }
+
+	for (unsigned i = 0; i < res->count; i++) {
+        name_val = ldb_dn_get_component_val(res->msgs[i]->dn, 1);
+
+        uid_list[i] = strtouint32((const char *)name_val->data, NULL, 10);
+        ret = errno;
+        if (ret != EOK) {
+            DEBUG(SSSDBG_CRIT_FAILURE, "Invalid UID\n");
+            return ENOMEM;
+        }
+        DEBUG(SSSDBG_TRACE_INTERNAL, "uid: [%u]\n", uid_list[i]);
+    }
+
+    *_uid_list = uid_list;
+    *_uid_list_count = res->count;
+
+    return EOK;
+}
+
 errno_t sss_sec_list(TALLOC_CTX *mem_ctx,
                      struct sss_sec_req *req,
                      char ***_keys,
diff --git a/src/util/secrets/secrets.h b/src/util/secrets/secrets.h
index f79bfaa4b9..ba6f3bc6db 100644
--- a/src/util/secrets/secrets.h
+++ b/src/util/secrets/secrets.h
@@ -87,6 +87,10 @@ errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx,
 
 errno_t sss_sec_delete(struct sss_sec_req *req);
 
+errno_t sss_sec_list_cc_uids(struct sss_sec_ctx *sec_ctx,
+                             uid_t **_keys,
+                             size_t *num_keys);
+
 errno_t sss_sec_list(TALLOC_CTX *mem_ctx,
                      struct sss_sec_req *req,
                      char ***_keys,
diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
index b44e8d9025..cf96f3ffc4 100644
--- a/src/util/sss_krb5.c
+++ b/src/util/sss_krb5.c
@@ -1211,6 +1211,16 @@ static errno_t iobuf_get_len_bytes(TALLOC_CTX *mem_ctx,
     return EOK;
 }
 
+errno_t get_krb5_data_from_cred(TALLOC_CTX *mem_ctx,
+                                struct sss_iobuf *iobuf,
+                                krb5_data *k5data)
+{
+    k5data->data = (char *) sss_iobuf_get_data(iobuf);
+    k5data->length = sss_iobuf_get_size(iobuf);
+
+    return EOK;
+}
+
 static errno_t get_krb5_data(TALLOC_CTX *mem_ctx,
                              struct sss_iobuf *iobuf,
                              krb5_data *k5data)
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index 4d9804fa88..aa1a258eb2 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -34,6 +34,7 @@
 
 #include "util/sss_iobuf.h"
 #include "util/util.h"
+#include <uuid/uuid.h>
 
 #define KRB5_CHILD_LOG_FILE     "krb5_child"
 #define LDAP_CHILD_LOG_FILE     "ldap_child"
@@ -198,4 +199,8 @@ krb5_error_code sss_krb5_unmarshal_princ(TALLOC_CTX *mem_ctx,
 
 krb5_error_code sss_krb5_init_context(krb5_context *context);
 
+errno_t get_krb5_data_from_cred(TALLOC_CTX *mem_ctx,
+                                struct sss_iobuf *iobuf,
+                                krb5_data *k5data);
+
 #endif /* __SSS_KRB5_H__ */

From f2768643015543156133f1d1abc8fd08f13a53be Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Mon, 30 Nov 2020 14:43:40 -0500
Subject: [PATCH 03/10] KCM: Parse renew table and prepare for renewal

Trigger renewal codepath for entries added to the renew table.
---
 src/responder/kcm/kcm_renew.c | 85 +++++++++++++++++++++++++++++++++++
 1 file changed, 85 insertions(+)

diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c
index 0d5cd97ce3..0d164b69bc 100644
--- a/src/responder/kcm/kcm_renew.c
+++ b/src/responder/kcm/kcm_renew.c
@@ -61,6 +61,8 @@ struct auth_data {
     const char *key;
 };
 
+static void kcm_renew_tgt_done(struct tevent_req *req);
+
 static int kcm_get_auth_provider_options(struct kcm_ctx *kctx,
                                          struct krb5_ctx *krb5_ctx,
                                          time_t *_renew_intv)
@@ -386,6 +388,81 @@ int kcm_get_renewal_config(struct kcm_ctx *kctx,
     return ret;
 }
 
+static void kcm_renew_tgt(struct tevent_context *ev, struct tevent_timer *te,
+                      struct timeval current_time, void *private_data)
+{
+    struct auth_data *auth_data = talloc_get_type(private_data,
+                                                  struct auth_data);
+    struct tevent_req *req;
+}
+
+static void kcm_renew_tgt_done(struct tevent_req *req)
+{
+}
+
+errno_t kcm_renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx)
+{
+    hash_key_t *keys = NULL;
+    unsigned long count;
+    int ret;
+    size_t i;
+    time_t now;
+    struct auth_data *auth_data;
+    struct renew_data *renew_data;
+    struct tevent_timer *te = NULL;
+
+    ret = hash_keys(renew_tgt_ctx->tgt_table, &count, &keys);
+    if (ret != HASH_SUCCESS) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed retrieving hash keys.\n");
+        return EIO;
+    }
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Found [%lu] renewal entries.\n", count);
+
+    now = time(NULL);
+
+    for (i = 0; i < count; i++) {
+        renew_data = sss_ptr_hash_lookup(renew_tgt_ctx->tgt_table, keys[i].c_str,
+                                         struct renew_data);
+        DEBUG(SSSDBG_TRACE_INTERNAL, "Checking [%s] for renewal at [%.24s].\n",
+              renew_data->ccname, ctime(&renew_data->start_renew_at));
+        if (renew_data->renew_till < now) {
+            DEBUG(SSSDBG_TRACE_INTERNAL, "Renew time exceeded, removing [%s].\n",
+                                         renew_data->ccname);
+            sss_ptr_hash_delete(renew_tgt_ctx->tgt_table, keys[i].c_str, true);
+        } else if (renew_data->start_renew_at <= now) {
+            auth_data = talloc_zero(renew_tgt_ctx, struct auth_data);
+            if (auth_data == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE, "talloc_zero failed.\n");
+            } else {
+                auth_data->krb5_ctx = renew_tgt_ctx->krb5_ctx;
+                auth_data->table = renew_tgt_ctx->tgt_table;
+                auth_data->renew_data = renew_data;
+                auth_data->key = talloc_strdup(auth_data, keys[i].c_str);
+                if (auth_data->key == NULL) {
+                    DEBUG(SSSDBG_CRIT_FAILURE, "talloc_strdup failed.\n");
+                } else {
+                    te = tevent_add_timer(renew_tgt_ctx->ev,
+                                          auth_data, tevent_timeval_current(),
+                                          kcm_renew_tgt, auth_data);
+                    if (te == NULL) {
+                        DEBUG(SSSDBG_CRIT_FAILURE, "tevent_add_timer failed.\n");
+                    }
+                }
+            }
+
+            if (auth_data == NULL || te == NULL) {
+                DEBUG(SSSDBG_CRIT_FAILURE,
+                      "Failed to renew TGT in [%s].\n", renew_data->ccname);
+                sss_ptr_hash_delete(renew_tgt_ctx->tgt_table, keys[i].c_str, true);
+            }
+        }
+    }
+
+    return EOK;
+}
+
+
 static void kcm_renew_tgt_timer_handler(struct tevent_context *ev,
                                         struct tevent_timer *te,
                                         struct timeval current_time,
@@ -408,6 +485,14 @@ static void kcm_renew_tgt_timer_handler(struct tevent_context *ev,
         return;
     }
 
+    ret = kcm_renew_all_tgts(renew_tgt_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to attempt renewal of KCM ticket"
+                                   " table.\n");
+        talloc_zfree(renew_tgt_ctx);
+        return;
+    }
+
     next = tevent_timeval_current_ofs(renew_tgt_ctx->timer_interval, 0);
     renew_tgt_ctx->te = tevent_add_timer(ev, renew_tgt_ctx,
                                          next, kcm_renew_tgt_timer_handler,

From 4c4c767b980451791c3851171c5f4f50d1354dbc Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Tue, 1 Dec 2020 11:44:11 -0500
Subject: [PATCH 04/10] KRB5_CHILD: Workaround domain dependency

Kerberos child renewals called from KCM will not have an
initialized domain structure.
---
 src/providers/krb5/krb5_child_handler.c | 12 ++++--------
 1 file changed, 4 insertions(+), 8 deletions(-)

diff --git a/src/providers/krb5/krb5_child_handler.c b/src/providers/krb5/krb5_child_handler.c
index 155ae3c3c9..f601bb7b8e 100644
--- a/src/providers/krb5/krb5_child_handler.c
+++ b/src/providers/krb5/krb5_child_handler.c
@@ -113,7 +113,7 @@ static errno_t create_send_buffer(struct krb5child_req *kr,
     uint32_t validate;
     uint32_t send_pac;
     uint32_t use_enterprise_principal;
-    uint32_t posix_domain;
+    uint32_t posix_domain = 0;
     size_t username_len = 0;
     errno_t ret;
 
@@ -138,14 +138,10 @@ static errno_t create_send_buffer(struct krb5child_req *kr,
             break;
     }
 
-    switch (kr->dom->type) {
-    case DOM_TYPE_POSIX:
+    /* Renewals from KCM do not initialize kr->dom  */
+    if (kr->pd->cmd == SSS_CMD_RENEW || kr->dom->type == DOM_TYPE_POSIX) {
         posix_domain = 1;
-        break;
-    case DOM_TYPE_APPLICATION:
-        posix_domain = 0;
-        break;
-    default:
+    } else if (kr->dom->type != DOM_TYPE_APPLICATION) {
         return EINVAL;
     }
 

From 8b2e1e98ae6eeaf2c06bc0ec77b5703aaa4c22cf Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Mon, 30 Nov 2020 16:05:55 -0500
Subject: [PATCH 05/10] KCM: Execute krb5 child to perform renewal

---
 Makefile.am                   |   1 +
 src/responder/kcm/kcm_renew.c | 169 +++++++++++++++++++++++++++++++++-
 2 files changed, 169 insertions(+), 1 deletion(-)

diff --git a/Makefile.am b/Makefile.am
index a98727914c..470bda7f2e 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -1847,6 +1847,7 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_ops.c \
     src/responder/kcm/kcmsrv_op_queue.c \
     src/providers/krb5/krb5_opts.c \
+    src/providers/krb5/krb5_child_handler.c \
     src/providers/data_provider_opts.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
diff --git a/src/responder/kcm/kcm_renew.c b/src/responder/kcm/kcm_renew.c
index 0d164b69bc..4282fda757 100644
--- a/src/responder/kcm/kcm_renew.c
+++ b/src/responder/kcm/kcm_renew.c
@@ -44,6 +44,20 @@ struct renew_tgt_ctx {
     struct tevent_timer *te;
 };
 
+struct kcm_renew_tgt_ctx {
+    struct tevent_context *ev;
+    struct krb5child_req *kr;
+
+    struct krb5_ctx *krb5_ctx;
+    struct auth_data *auth_data;
+    struct renew_data *renew_data;
+    hash_table_t *table;
+    const char *key;
+
+    uint8_t *buf;
+    ssize_t len;
+};
+
 struct renew_data {
     const char *ccname;
     uid_t uid;
@@ -388,16 +402,170 @@ int kcm_get_renewal_config(struct kcm_ctx *kctx,
     return ret;
 }
 
+static errno_t kcm_child_req_setup(TALLOC_CTX *mem_ctx,
+                                   struct auth_data *auth_data,
+                                   struct krb5_ctx *krb5_ctx,
+                                   struct krb5child_req **_req)
+{
+    struct krb5child_req *krreq;
+    const char *kcm_ccname = NULL;
+    errno_t ret;
+
+    DEBUG(SSSDBG_TRACE_INTERNAL, "Setup for renewal of [%s] " \
+                                 "for principal name [%s]\n",
+                                 auth_data->key,
+                                 auth_data->renew_data->ccname);
+
+    krreq = talloc_zero(mem_ctx, struct krb5child_req);
+    if (krreq == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "krreq talloc_zero failed\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    krreq->krb5_ctx = krb5_ctx;
+    if (krreq->krb5_ctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to create allocate krb5_ctx\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    /* Set uid and gid */
+    krreq->uid = auth_data->renew_data->uid;
+    krreq->gid = auth_data->renew_data->gid;
+
+    kcm_ccname = talloc_asprintf(mem_ctx, "KCM:%s",
+                                 auth_data->renew_data->ccname);
+    if (kcm_ccname == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to strdup ccname\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    krreq->upn = talloc_strdup(mem_ctx, auth_data->key);
+    if (krreq->upn == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to strdup upn");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    krreq->ccname = kcm_ccname;
+    if (krreq->ccname == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to strdup ccname");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+   /* Set PAM Data */
+    krreq->pd = create_pam_data(krreq);
+    if (krreq->pd == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "create_pam_data failed\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    krreq->pd->cmd = SSS_CMD_RENEW;
+    krreq->pd->user = talloc_strdup(mem_ctx, auth_data->key);
+    if (krreq->pd->user == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "talloc_strdup key failed\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    /* Set authtok values */
+    sss_authtok_set_empty(krreq->pd->newauthtok);
+
+    ret = sss_authtok_set_ccfile(krreq->pd->authtok, kcm_ccname, 0);
+    if (ret != 0) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Failed setting authtok ccname\n");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    krreq->old_ccname = kcm_ccname;
+    if (krreq->old_ccname == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE,
+              "Failed to strdup ccname");
+        ret = ENOMEM;
+        goto fail;
+    }
+
+    *_req = krreq;
+    return EOK;
+fail:
+    talloc_zfree(krreq);
+    return ret;
+}
+
 static void kcm_renew_tgt(struct tevent_context *ev, struct tevent_timer *te,
                       struct timeval current_time, void *private_data)
 {
     struct auth_data *auth_data = talloc_get_type(private_data,
                                                   struct auth_data);
     struct tevent_req *req;
+    struct kcm_renew_tgt_ctx *ctx = NULL;
+    errno_t ret;
+
+    ctx = talloc_zero(auth_data, struct kcm_renew_tgt_ctx);
+    if (ctx == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "ctx talloc_zero failed\n");
+        return;
+    }
+
+    ret = kcm_child_req_setup(auth_data, auth_data,
+                              auth_data->krb5_ctx, &ctx->kr);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Cannot setup krchild request\n");
+        talloc_free(auth_data);
+        return;
+    }
+
+    req = handle_child_send(ctx, ev, ctx->kr);
+    if (req == NULL) {
+        DEBUG(SSSDBG_FATAL_FAILURE, "Cannot create child request\n");
+        talloc_free(auth_data);
+        return;
+    }
+
+    tevent_req_set_callback(req, kcm_renew_tgt_done, ctx);
+
+    return;
 }
 
 static void kcm_renew_tgt_done(struct tevent_req *req)
 {
+    struct kcm_renew_tgt_ctx *ctx = tevent_req_callback_data(req,
+                                    struct kcm_renew_tgt_ctx);
+    int ret;
+    struct krb5_child_response *res;
+
+    ret = handle_child_recv(req, ctx, &ctx->buf, &ctx->len);
+    talloc_free(req);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "handle_child_recv failure\n");
+        goto done;
+    }
+    ret = parse_krb5_child_response(ctx, ctx->buf, ctx->len, ctx->kr->pd,
+            0, &res);
+    if (ret != 0) {
+        DEBUG(SSSDBG_OP_FAILURE, "Krb5 child returned an error! Please " \
+                                 "inspect the krb5_child.log file\n");
+        goto done;
+    }
+    if (res->msg_status != EOK) {
+        DEBUG(SSSDBG_TRACE_FUNC, "Renewal failed - krb5_child [%d]\n",
+                                 res->msg_status);
+        goto done;
+    }
+
+    DEBUG(SSSDBG_TRACE_FUNC, "Successfully renewed [%s]\n", res->ccname);
+done:
+    talloc_zfree(ctx);
+    return;
 }
 
 errno_t kcm_renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx)
@@ -462,7 +630,6 @@ errno_t kcm_renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx)
     return EOK;
 }
 
-
 static void kcm_renew_tgt_timer_handler(struct tevent_context *ev,
                                         struct tevent_timer *te,
                                         struct timeval current_time,

From 3b1e497335992a0af5d1c19e9bcc74cbd3d13f83 Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Mon, 21 Dec 2020 14:45:17 -0500
Subject: [PATCH 06/10] SECRETS: Don't hardcode SECRETS_DB_PATH

Allow for overriding in cmocka tests
---
 src/util/secrets/secrets.c | 32 ++++++++++++++++++++++++++------
 src/util/secrets/secrets.h |  5 +++++
 2 files changed, 31 insertions(+), 6 deletions(-)

diff --git a/src/util/secrets/secrets.c b/src/util/secrets/secrets.c
index 92678c43dc..25f63cab29 100644
--- a/src/util/secrets/secrets.c
+++ b/src/util/secrets/secrets.c
@@ -633,12 +633,13 @@ static int generate_master_key(const char *filename, size_t size)
 }
 
 static errno_t lcl_read_mkey(TALLOC_CTX *mem_ctx,
+                             const char *dbpath,
                              struct sss_sec_data *master_key)
 {
     int mfd;
     ssize_t size;
     errno_t ret;
-    const char *mkey = SECRETS_DB_PATH"/.secrets.mkey";
+    const char *mkey = dbpath;
 
     master_key->data = talloc_size(mem_ctx, MKEY_SIZE);
     if (master_key->data == NULL) {
@@ -698,11 +699,11 @@ static int set_quotas(struct sss_sec_ctx *sec_ctx,
     return EOK;
 }
 
-errno_t sss_sec_init(TALLOC_CTX *mem_ctx,
-                     struct sss_sec_hive_config **config_list,
-                     struct sss_sec_ctx **_sec_ctx)
+errno_t sss_sec_init_with_path(TALLOC_CTX *mem_ctx,
+                               struct sss_sec_hive_config **config_list,
+                               const char *dbpath,
+                               struct sss_sec_ctx **_sec_ctx)
 {
-    const char *dbpath = SECRETS_DB_PATH"/secrets.ldb";
     struct sss_sec_ctx *sec_ctx;
     TALLOC_CTX *tmp_ctx;
     errno_t ret;
@@ -744,7 +745,7 @@ errno_t sss_sec_init(TALLOC_CTX *mem_ctx,
         goto done;
     }
 
-    ret = lcl_read_mkey(sec_ctx, &sec_ctx->master_key);
+    ret = lcl_read_mkey(sec_ctx, dbpath, &sec_ctx->master_key);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE, "Cannot get the master key\n");
         goto done;
@@ -757,6 +758,25 @@ errno_t sss_sec_init(TALLOC_CTX *mem_ctx,
     return ret;
 }
 
+errno_t sss_sec_init(TALLOC_CTX *mem_ctx,
+                     struct sss_sec_hive_config **config_list,
+                     struct sss_sec_ctx **_sec_ctx)
+{
+    const char *dbpath = SECRETS_DB_PATH"/secrets.ldb";
+    errno_t ret;
+
+    ret = sss_sec_init_with_path(mem_ctx, config_list, dbpath, _sec_ctx);
+    if (ret != EOK) {
+        DEBUG(SSSDBG_CRIT_FAILURE, "Failed to initialize secdb\n");
+        ret = EIO;
+        goto done;
+    }
+
+    ret = EOK;
+done:
+    return ret;
+}
+
 static int local_db_dn(TALLOC_CTX *mem_ctx,
                        struct ldb_context *ldb,
                        const char *basedn,
diff --git a/src/util/secrets/secrets.h b/src/util/secrets/secrets.h
index ba6f3bc6db..40727dc349 100644
--- a/src/util/secrets/secrets.h
+++ b/src/util/secrets/secrets.h
@@ -79,6 +79,11 @@ errno_t sss_sec_init(TALLOC_CTX *mem_ctx,
                      struct sss_sec_hive_config **config_list,
                      struct sss_sec_ctx **_sec_ctx);
 
+errno_t sss_sec_init_with_path(TALLOC_CTX *mem_ctx,
+                               struct sss_sec_hive_config **config_list,
+                               const char *dbpath,
+                               struct sss_sec_ctx **_sec_ctx);
+
 errno_t sss_sec_new_req(TALLOC_CTX *mem_ctx,
                         struct sss_sec_ctx *sec_ctx,
                         const char *url,

From bd485e01612c9e725746ace2bf1191255a7b1ea7 Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Fri, 29 Jan 2021 20:36:24 +0000
Subject: [PATCH 07/10] TESTS: Add kcm_renewals unit test

---
 Makefile.am                          |  35 ++++
 src/tests/cmocka/test_kcm_renewals.c | 296 +++++++++++++++++++++++++++
 2 files changed, 331 insertions(+)
 create mode 100644 src/tests/cmocka/test_kcm_renewals.c

diff --git a/Makefile.am b/Makefile.am
index 470bda7f2e..cd46b989ef 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -330,6 +330,7 @@ if BUILD_KCM
 non_interactive_cmocka_based_tests += \
 	test_kcm_marshalling \
 	test_kcm_queue \
+	test_kcm_renewals \
         $(NULL)
 endif   # BUILD_KCM
 
@@ -4000,6 +4001,40 @@ test_kcm_queue_LDADD = \
     libsss_sbus.la \
     $(NULL)
 
+test_kcm_renewals_SOURCES = \
+	$(TEST_MOCK_RESP_OBJ) \
+	src/tests/cmocka/test_kcm_renewals.c \
+	src/responder/kcm/kcm_renew.c \
+	src/responder/kcm/kcmsrv_ccache.c \
+	src/responder/kcm/kcmsrv_ccache_key.c \
+	src/responder/kcm/kcmsrv_ccache_binary.c \
+	src/responder/kcm/kcmsrv_ccache_json.c \
+	src/util/sss_krb5.c \
+	src/util/sss_iobuf.c \
+	src/util/secrets/secrets.c \
+	src/util/secrets/config.c \
+	src/providers/krb5/krb5_child_handler.c \
+	src/providers/data_provider_opts.c \
+	$(NULL)
+test_kcm_renewals_CFLAGS = \
+	$(AM_CFLAGS) \
+	$(NULL)
+test_kcm_renewals_LDFLAGS = \
+	-Wl,-wrap,fstat
+test_kcm_renewals_LDADD = \
+	$(LIBADD_DL) \
+	$(UUID_LIBS) \
+	$(JANSSON_LIBS) \
+	$(KRB5_LIBS) \
+	$(CARES_LIBS) \
+	$(CMOCKA_LIBS) \
+	$(SSSD_LIBS) \
+	$(SSSD_INTERNAL_LTLIBS) \
+	libsss_test_common.la \
+	libsss_iface.la \
+	libsss_sbus.la \
+	$(NULL)
+
 endif # BUILD_KCM
 
 endif # HAVE_CMOCKA
diff --git a/src/tests/cmocka/test_kcm_renewals.c b/src/tests/cmocka/test_kcm_renewals.c
new file mode 100644
index 0000000000..10f119a56e
--- /dev/null
+++ b/src/tests/cmocka/test_kcm_renewals.c
@@ -0,0 +1,296 @@
+/*
+    Copyright (C) 2020 Red Hat
+
+    SSSD tests: Test KCM Renewals
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "config.h"
+
+#include <stdio.h>
+#include <popt.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include "util/util.h"
+#include "util/util_creds.h"
+#include "tests/cmocka/common_mock.h"
+#include "responder/kcm/kcmsrv_ccache.h"
+#include "responder/kcm/kcm_renew.h"
+#include "responder/kcm/kcmsrv_ccache_be.h"
+#include "responder/kcm/kcmsrv_ccache_pvt.h"
+#include "responder/kcm/kcmsrv_pvt.h"
+#include "responder/kcm/kcmsrv_ccache_secdb.c"
+
+#define TESTS_PATH "tp_" BASE_FILE_STEM
+#define TEST_CONF_DB "test_kcm_renewals_conf.ldb"
+#define TEST_DB_FULL_PATH  TESTS_PATH "/secrets.ldb"
+
+errno_t kcm_renew_all_tgts(struct renew_tgt_ctx *renew_tgt_ctx);
+
+const struct kcm_ccdb_ops ccdb_mem_ops;
+const struct kcm_ccdb_ops ccdb_sec_ops;
+const struct kcm_ccdb_ops ccdb_secdb_ops;
+
+struct test_ctx {
+    struct krb5_ctx *krb5_ctx;
+    struct tevent_context *ev;
+    struct kcm_ccdb *ccdb;
+};
+
+struct renew_tgt_ctx {
+    hash_table_t *tgt_table;
+    struct tevent_context *ev;
+    struct krb5_ctx *krb5_ctx;
+    struct resp_ctx *rctx;
+    struct kcm_ccdb *db;
+    time_t timer_interval;
+    struct tevent_timer *te;
+};
+
+struct renew_data {
+    const char *ccname;
+    uid_t uid;
+    gid_t gid;
+    time_t start_time;
+    time_t lifetime;
+    time_t start_renew_at;
+    time_t renew_till;
+};
+
+/* register_cli_protocol_version is required in test since it links with
+ * responder_common.c module
+ */
+struct cli_protocol_version *register_cli_protocol_version(void)
+{
+    static struct cli_protocol_version responder_test_cli_protocol_version[] = {
+        { 0, NULL, NULL }
+    };
+
+    return responder_test_cli_protocol_version;
+}
+
+/* Wrap fstat() to ignore ownership check failure
+ * from lcl_read_mkey() -> check_and_open_readonly()
+ */
+int __real_fstat(int fd, struct stat *statbuf);
+
+int __wrap_fstat(int fd, struct stat *statbuf)
+{
+    int ret;
+
+    ret = __real_fstat(fd, statbuf);
+    if (ret == 0) {
+        statbuf->st_uid = 0;
+        statbuf->st_gid = 0;
+    }
+
+    return ret;
+}
+
+/* Override perform_checks and check_fd so that fstat wrap is called */
+static errno_t perform_checks(struct stat *stat_buf,
+                              uid_t uid, gid_t gid,
+                              mode_t mode, mode_t mask)
+{
+    mode_t st_mode;
+
+    if (mask) {
+        st_mode = stat_buf->st_mode & mask;
+    } else {
+        st_mode = stat_buf->st_mode & (S_IFMT|ALLPERMS);
+    }
+
+    if ((mode & S_IFMT) != (st_mode & S_IFMT)) {
+        DEBUG(SSSDBG_TRACE_LIBS, "File is not the right type.\n");
+        return EINVAL;
+    }
+
+    if ((st_mode & ALLPERMS) != (mode & ALLPERMS)) {
+        DEBUG(SSSDBG_TRACE_LIBS,
+              "File has the wrong (bit masked) mode [%.7o], "
+              "expected [%.7o].\n",
+              (st_mode & ALLPERMS), (mode & ALLPERMS));
+        return EINVAL;
+    }
+
+    if (uid != (uid_t)(-1) && stat_buf->st_uid != uid) {
+        DEBUG(SSSDBG_TRACE_LIBS, "File must be owned by uid [%d].\n", uid);
+        return EINVAL;
+    }
+
+    if (gid != (gid_t)(-1) && stat_buf->st_gid != gid) {
+        DEBUG(SSSDBG_TRACE_LIBS, "File must be owned by gid [%d].\n", gid);
+        return EINVAL;
+    }
+
+    return EOK;
+}
+
+errno_t check_fd(int fd, uid_t uid, gid_t gid,
+                 mode_t mode, mode_t mask,
+                 struct stat *caller_stat_buf)
+{
+    int ret;
+    struct stat local_stat_buf;
+    struct stat *stat_buf;
+
+    if (caller_stat_buf == NULL) {
+        stat_buf = &local_stat_buf;
+    } else {
+        stat_buf = caller_stat_buf;
+    }
+
+    ret = fstat(fd, stat_buf);
+    if (ret == -1) {
+        ret = errno;
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              "fstat for [%d] failed: [%d][%s].\n", fd, ret,
+                                                        strerror(ret));
+        return ret;
+    }
+
+    return perform_checks(stat_buf, uid, gid, mode, mask);
+}
+
+
+static int setup_kcm_renewals(void **state)
+{
+    struct test_ctx *tctx;
+
+    tctx = talloc_zero(NULL, struct test_ctx);
+    assert_non_null(tctx);
+
+    tctx->ev = tevent_context_init(tctx);
+    assert_non_null(tctx->ev);
+
+    tctx->ccdb = talloc_zero(tctx, struct kcm_ccdb);
+    assert_non_null(tctx->ccdb);
+    tctx->ccdb->ev = tctx->ev;
+
+    tctx->ccdb->ops = &ccdb_secdb_ops;
+    assert_non_null(tctx->ccdb->ops);
+
+    *state = tctx;
+    return 0;
+}
+
+static int teardown_kcm_renewals(void **state)
+{
+    struct test_ctx *tctx = talloc_get_type(*state, struct test_ctx);
+
+    unlink(TEST_DB_FULL_PATH);
+
+    rmdir(TESTS_PATH);
+    talloc_free(tctx);
+    return 0;
+}
+
+static void test_kcm_renewals_table(void **state)
+{
+    struct test_ctx *test_ctx = talloc_get_type(*state, struct test_ctx);
+    errno_t ret;
+    struct ccdb_secdb *secdb = NULL;
+    struct renew_tgt_ctx *renew_ctx = NULL;
+    struct renew_data *renew_tgt = NULL;
+    const char *upn = "testrenew@TESTREALM";
+
+    secdb = talloc_zero(test_ctx, struct ccdb_secdb);
+
+    assert_non_null(secdb);
+
+    ret = mkdir(TESTS_PATH, 0700);
+    assert_int_equal(ret, 0);
+
+    open(TEST_DB_FULL_PATH, O_CREAT|O_EXCL|O_WRONLY, 0600);
+
+    ret = sss_sec_init_with_path(test_ctx->ccdb, NULL, TEST_DB_FULL_PATH,
+                                 &secdb->sctx);
+
+    /* Create hash table */
+    renew_ctx = talloc_zero(test_ctx, struct renew_tgt_ctx);
+    assert_non_null(renew_ctx);
+
+    renew_ctx->ev = test_ctx->ev;
+
+    renew_ctx->tgt_table = sss_ptr_hash_create(renew_ctx, NULL, NULL);
+    assert_non_null(renew_ctx->tgt_table);
+
+    /* Add entry to hash table */
+    renew_tgt = talloc_zero(test_ctx, struct renew_data);
+    assert_non_null(renew_tgt);
+
+    time_t now;
+
+    renew_tgt->ccname = talloc_strdup(test_ctx, "1000:1001");
+    renew_tgt->uid = 1000;
+    renew_tgt->gid = 1000;
+    now = time(NULL);
+    renew_tgt->start_time = now;
+    renew_tgt->lifetime = now + 60;
+    renew_tgt->renew_till = now + 300;
+
+    ret = sss_ptr_hash_add(renew_ctx->tgt_table, upn,
+                           renew_tgt, struct renew_data);
+    assert_int_equal(ret, EOK);
+
+	/* Parse renewal table */
+	ret = kcm_renew_all_tgts(renew_ctx);
+    assert_int_equal(ret, EOK);
+}
+
+int main(int argc, const char *argv[])
+{
+    poptContext pc;
+    int opt;
+    int rv;
+    struct poptOption long_options[] = {
+        POPT_AUTOHELP
+        SSSD_DEBUG_OPTS
+        POPT_TABLEEND
+    };
+
+    const struct CMUnitTest tests[] = {
+        cmocka_unit_test_setup_teardown(test_kcm_renewals_table,
+                                        setup_kcm_renewals,
+                                        teardown_kcm_renewals),
+    };
+
+    /* Set debug level to invalid value so we can decide if -d 0 was used. */
+    debug_level = SSSDBG_INVALID;
+
+    pc = poptGetContext(argv[0], argc, argv, long_options, 0);
+    while((opt = poptGetNextOpt(pc)) != -1) {
+        switch(opt) {
+        default:
+            fprintf(stderr, "\nInvalid option %s: %s\n\n",
+                    poptBadOption(pc, 0), poptStrerror(opt));
+            poptPrintUsage(pc, stderr, 0);
+            return 1;
+        }
+    }
+    poptFreeContext(pc);
+
+    DEBUG_CLI_INIT(debug_level);
+
+    /* Even though normally the tests should clean up after themselves
+     * they might not after a failed run. Remove the old DB to be sure
+     */
+    tests_set_cwd();
+
+    rv = cmocka_run_group_tests(tests, NULL, NULL);
+
+    return rv;
+}

From 9d8901df33415ae8f030c1f910d24b42311f17ae Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Mon, 21 Dec 2020 20:48:15 -0500
Subject: [PATCH 08/10] INTG: Add KCM Renewal integration test

---
 src/tests/intg/kdc.py       |  2 +
 src/tests/intg/krb5utils.py | 15 +++++++-
 src/tests/intg/test_kcm.py  | 77 +++++++++++++++++++++++++++++++++++++
 3 files changed, 93 insertions(+), 1 deletion(-)

diff --git a/src/tests/intg/kdc.py b/src/tests/intg/kdc.py
index 8f82a990bc..2f14261382 100644
--- a/src/tests/intg/kdc.py
+++ b/src/tests/intg/kdc.py
@@ -145,6 +145,8 @@ def _format_kdc_conf(self):
                 kadmind_port = {self.kadmin_port}
                 database_name = {database_path}
                 key_stash_file = {key_stash}
+                max_life = 7d
+                max_renewable_life = 14d
                 acl_file = {self.acl_file}
             }}
 
diff --git a/src/tests/intg/krb5utils.py b/src/tests/intg/krb5utils.py
index 67ae430069..3da4c6a91f 100644
--- a/src/tests/intg/krb5utils.py
+++ b/src/tests/intg/krb5utils.py
@@ -58,8 +58,10 @@ def _run_in_env(self, args, stdin=None, extra_env=None):
         out, err = cmd.communicate(stdin)
         return cmd.returncode, out.decode('utf-8'), err.decode('utf-8')
 
-    def kinit(self, principal, password, env=None):
+    def kinit(self, principal, password, options=None, env=None):
         args = ["kinit", principal]
+        if options:
+            args.extend(options)
         return self._run_in_env(args, password.encode('utf-8'), env)
 
     def kvno(self, principal, env=None):
@@ -115,6 +117,17 @@ def list_princs(self, env=None):
 
         return [ln for ln in outlines[2:] if len(ln) > 0]
 
+    def list_times(self, env=None):
+        p = self.spawn_in_env(['klist', '-A'])
+        output = p.stdout.read().splitlines()
+        for line in output:
+            if not line:
+                continue
+
+            line_str = line.decode("utf-8")
+            if line_str[0].isdigit():
+                return line_str
+
     def has_principal(self, exp_principal, exp_cache=None, env=None):
         try:
             princlist = self.list_princs(env)
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
index 3a43491b96..97d0a974f4 100644
--- a/src/tests/intg/test_kcm.py
+++ b/src/tests/intg/test_kcm.py
@@ -25,6 +25,7 @@
 import time
 import signal
 import sys
+from datetime import datetime
 from requests import HTTPError
 
 import kdc
@@ -33,9 +34,15 @@
 from util import unindent
 from test_secrets import create_sssd_secrets_fixture
 from secrets import SecretsLocalClient
+from intg.files_ops import passwd_ops_setup
 
 MAX_SECRETS = 10
 
+USER1 = dict(name='user1', passwd='x', uid=1000, gid=1000,
+             gecos='User for tests',
+             dir='/home/user1',
+             shell='/bin/bash')
+
 
 class KcmTestEnv(object):
     def __init__(self, k5kdc, k5util):
@@ -138,6 +145,29 @@ def create_sssd_conf(kcm_path, ccache_storage, max_secrets=MAX_SECRETS):
     """).format(**locals())
 
 
+def create_sssd_conf_renewals(kcm_path, ccache_storage, renew_lifetime,
+                              lifetime, renew_interval,
+                              max_secrets=MAX_SECRETS):
+    return unindent("""\
+        [sssd]
+        domains = files
+        services = nss
+
+        [domain/files]
+        id_provider = files
+
+        [kcm]
+        socket_path = {kcm_path}
+        ccache_storage = {ccache_storage}
+        krb5_renewable_lifetime = {renew_lifetime}
+        krb5_lifetime = {lifetime}
+        krb5_renew_interval = {renew_interval}
+
+        [secrets]
+        max_secrets = {max_secrets}
+    """).format(**locals())
+
+
 def common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf):
     kcm_socket_include = unindent("""
     [libdefaults]
@@ -200,6 +230,18 @@ def setup_for_kcm_secdb(request, kdc_instance):
     return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
 
 
+@pytest.fixture
+def setup_for_kcm_renewals_secdb(passwd_ops_setup, request, kdc_instance):
+    """
+    Set up the KCM renewals backed by libsss_secrets
+    """
+    kcm_path = os.path.join(config.RUNSTATEDIR, "kcm.socket")
+    sssd_conf = create_sssd_conf_renewals(kcm_path, "secdb",
+                                          "10d", "60s", "10s")
+    passwd_ops_setup.useradd(**USER1)
+    return common_setup_for_kcm_mem(request, kdc_instance, kcm_path, sssd_conf)
+
+
 def kcm_init_list_destroy(testenv):
     """
     Test that kinit, kdestroy and klist work with KCM
@@ -585,3 +627,38 @@ def test_kcm_secrets_quota(setup_for_kcm_sec,
     princ = "%s%d" % ("kcmtest", MAX_SECRETS)
     out, _, _ = testenv.k5util.kinit(princ, princ)
     assert out != 0
+
+
+def test_kcm_renewals(setup_for_kcm_renewals_secdb):
+    """
+    Test that basic KCM renewal works
+    """
+    testenv = setup_for_kcm_renewals_secdb
+    testenv.k5kdc.add_principal("user1", "Secret123")
+
+    ok = testenv.k5util.has_principal("user1@KCMTEST")
+    assert ok is False
+    nprincs = testenv.k5util.num_princs()
+    assert nprincs == 0
+
+    # Renewal is only performed after half of lifetime exceeded,
+    # see kcm_renew_all_tgts()
+    options = ["-r", "15s", "-l", "15s"]
+    out, _, _ = testenv.k5util.kinit("user1", "Secret123", options)
+    assert out == 0
+    nprincs = testenv.k5util.num_princs()
+    assert nprincs == 1
+
+    timestr_fmt = "%m/%d/%y %H:%M:%S"
+    initial_times = testenv.k5util.list_times()
+
+    # Wait for renewal to trigger once, after renew interval
+    time.sleep(15)
+
+    renewed_times = testenv.k5util.list_times()
+
+    init_times = initial_times.split()[0] + ' ' + initial_times.split()[1]
+    renew_times = renewed_times.split()[0] + ' ' + renewed_times.split()[1]
+    dt_init = datetime.strptime(init_times, timestr_fmt)
+    dt_renew = datetime.strptime(renew_times, timestr_fmt)
+    assert dt_renew > dt_init

From f85ebdcd96b87c1944533d6871fc1525dfa14e12 Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Fri, 29 Jan 2021 14:59:20 -0500
Subject: [PATCH 09/10] KCM: Conditionally build KCM renewals support

Use --enable-kcm-renewal, --disable-kcm-renewal or allow
autodetection of MIT kerberos marshalling functions
required to enable KCM renewal support.
---
 Makefile.am                             | 28 ++++++++++++++++++-------
 src/external/krb5.m4                    | 19 ++++++++++++++++-
 src/man/Makefile.am                     |  6 +++++-
 src/man/sssd-kcm.8.xml                  |  4 ++--
 src/responder/kcm/kcm.c                 |  6 +++++-
 src/responder/kcm/kcmsrv_ccache_secdb.c |  6 ++++--
 src/tests/intg/Makefile.am              |  7 +++++++
 src/tests/intg/test_kcm.py              |  6 ++++++
 8 files changed, 68 insertions(+), 14 deletions(-)

diff --git a/Makefile.am b/Makefile.am
index cd46b989ef..83fb3b2f84 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -330,10 +330,14 @@ if BUILD_KCM
 non_interactive_cmocka_based_tests += \
 	test_kcm_marshalling \
 	test_kcm_queue \
-	test_kcm_renewals \
-        $(NULL)
+    $(NULL)
 endif   # BUILD_KCM
 
+if BUILD_KCM_RENEWAL
+non_interactive_cmocka_based_tests += test_kcm_renewals
+endif # BUILD_KCM_RENEWAL
+
+
 if BUILD_SAMBA
 non_interactive_cmocka_based_tests += \
     ad_access_filter_tests \
@@ -1837,7 +1841,6 @@ endif
 if BUILD_KCM
 sssd_kcm_SOURCES = \
     src/responder/kcm/kcm.c \
-    src/responder/kcm/kcm_renew.c \
     src/responder/kcm/kcmsrv_cmd.c \
     src/responder/kcm/kcmsrv_ccache.c \
     src/responder/kcm/kcmsrv_ccache_binary.c \
@@ -1847,9 +1850,6 @@ sssd_kcm_SOURCES = \
     src/responder/kcm/kcmsrv_ccache_secdb.c \
     src/responder/kcm/kcmsrv_ops.c \
     src/responder/kcm/kcmsrv_op_queue.c \
-    src/providers/krb5/krb5_opts.c \
-    src/providers/krb5/krb5_child_handler.c \
-    src/providers/data_provider_opts.c \
     src/util/sss_sockets.c \
     src/util/sss_krb5.c \
     src/util/sss_iobuf.c \
@@ -1867,7 +1867,6 @@ sssd_kcm_LDADD = \
     $(KRB5_LIBS) \
     $(JANSSON_LIBS) \
     $(SSSD_LIBS) \
-    $(CARES_LIBS) \
     $(UUID_LIBS) \
     $(SYSTEMD_DAEMON_LIBS) \
     $(SSSD_INTERNAL_LTLIBS) \
@@ -1886,6 +1885,18 @@ sssd_kcm_LDADD += \
     $(NULL)
 endif
 
+if BUILD_KCM_RENEWAL
+sssd_kcm_SOURCES += \
+    src/responder/kcm/kcm_renew.c \
+    src/providers/krb5/krb5_opts.c \
+    src/providers/krb5/krb5_child_handler.c \
+    src/providers/data_provider_opts.c \
+    $(NULL)
+sssd_kcm_LDADD += \
+    $(CARES_LIBS) \
+    $(NULL)
+endif
+
 endif
 
 sssd_be_SOURCES = \
@@ -4001,6 +4012,7 @@ test_kcm_queue_LDADD = \
     libsss_sbus.la \
     $(NULL)
 
+if BUILD_KCM_RENEWAL
 test_kcm_renewals_SOURCES = \
 	$(TEST_MOCK_RESP_OBJ) \
 	src/tests/cmocka/test_kcm_renewals.c \
@@ -4014,6 +4026,7 @@ test_kcm_renewals_SOURCES = \
 	src/util/secrets/secrets.c \
 	src/util/secrets/config.c \
 	src/providers/krb5/krb5_child_handler.c \
+	src/providers/krb5/krb5_opts.c \
 	src/providers/data_provider_opts.c \
 	$(NULL)
 test_kcm_renewals_CFLAGS = \
@@ -4034,6 +4047,7 @@ test_kcm_renewals_LDADD = \
 	libsss_iface.la \
 	libsss_sbus.la \
 	$(NULL)
+endif # BUILD_KCM_RENEWAL
 
 endif # BUILD_KCM
 
diff --git a/src/external/krb5.m4 b/src/external/krb5.m4
index b844c2fbee..1dfdbe9111 100644
--- a/src/external/krb5.m4
+++ b/src/external/krb5.m4
@@ -65,7 +65,8 @@ AC_CHECK_FUNCS([krb5_get_init_creds_opt_alloc krb5_get_error_message \
                 krb5_set_trace_callback \
                 krb5_find_authdata \
                 krb5_kt_have_content \
-                krb5_cc_get_full_name])
+                krb5_cc_get_full_name \
+                krb5_unmarshal_credentials])
 CFLAGS=$SAVE_CFLAGS
 LIBS=$SAVE_LIBS
 CFLAGS="$CFLAGS $KRB5_CFLAGS"
@@ -112,5 +113,21 @@ AM_CONDITIONAL([BUILD_KRB5_LOCALAUTH_PLUGIN],
 AM_COND_IF([BUILD_KRB5_LOCALAUTH_PLUGIN],
            [AC_DEFINE_UNQUOTED(HAVE_KRB5_LOCALAUTH_PLUGIN, 1, [Build with krb5 localauth plugin])])
 
+AC_ARG_ENABLE([kcm-renewal],
+              [AS_HELP_STRING([--disable-kcm-renewal],
+                              [do not build support for kcm renewals])],
+              [build_kcm_renewal=$enableval],
+              [build_kcm_renewal=yes])
+
+if test x$build_kcm_renewal = xyes -a x$ac_cv_func_krb5_unmarshal_credentials != xyes
+then
+  AC_MSG_WARN([krb5 unmarshalling function not available, fallback to building without KCM renewals])
+fi
+
+AM_CONDITIONAL([BUILD_KCM_RENEWAL],
+               [test x$build_kcm_renewal = xyes -a x$ac_cv_func_krb5_unmarshal_credentials = xyes])
+AM_COND_IF([BUILD_KCM_RENEWAL],
+           [AC_DEFINE_UNQUOTED(HAVE_KCM_RENEWAL, 1, [Build with kcm renewals])])
+
 CFLAGS=$SAVE_CFLAGS
 LIBS=$SAVE_LIBS
diff --git a/src/man/Makefile.am b/src/man/Makefile.am
index c6890a792e..9eedf698ab 100644
--- a/src/man/Makefile.am
+++ b/src/man/Makefile.am
@@ -49,8 +49,12 @@ endif
 if BUILD_LOCAL_PROVIDER
 LOCAL_PROVIDER_CONDS = ;enable_local_provider
 endif
+if BUILD_KCM_RENEWAL
+KCM_RENEWAL_CONDS = ;enable_kcm_renewal
+endif
+
 
-CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SEC_CONDS)$(SYSTEMD_CONDS)$(FILES_CONDS)$(KCM_CONDS)$(STAP_CONDS)$(LOCAL_PROVIDER_CONDS)
+CONDS = with_false$(SUDO_CONDS)$(AUTOFS_CONDS)$(SSH_CONDS)$(PAC_RESPONDER_CONDS)$(IFP_CONDS)$(GPO_CONDS)$(SEC_CONDS)$(SYSTEMD_CONDS)$(FILES_CONDS)$(KCM_CONDS)$(STAP_CONDS)$(LOCAL_PROVIDER_CONDS)$(KCM_RENEWAL_CONDS)
 
 
 #Special Rules:
diff --git a/src/man/sssd-kcm.8.xml b/src/man/sssd-kcm.8.xml
index 5f81af7367..aa77446ce3 100644
--- a/src/man/sssd-kcm.8.xml
+++ b/src/man/sssd-kcm.8.xml
@@ -162,7 +162,7 @@ systemctl restart sssd-kcm.service
         </para>
     </refsect1>
 
-    <refsect1 id='renewals'>
+    <refsect1 id='renewals' condition="enable_kcm_renewal">
         <title>RENEWALS</title>
         <para>
             The sssd-kcm service can be configured to attempt TGT
@@ -285,7 +285,7 @@ systemctl restart sssd-kcm.service
                     </para>
                 </listitem>
             </varlistentry>
-            <varlistentry>
+            <varlistentry condition="enable_kcm_renewal">
                 <term>krb5_renew_interval (integer)</term>
                 <listitem>
                     <para>
diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 7512832a0c..2219158da4 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -212,7 +212,10 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
 {
     struct resp_ctx *rctx;
     struct kcm_ctx *kctx;
+#ifdef HAVE_KCM_RENEWAL
+    struct krb5_ctx *krb5_ctx;
     time_t renew_intv = 0;
+#endif
     int ret;
 
     rctx = talloc_zero(mem_ctx, struct resp_ctx);
@@ -257,6 +260,7 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
         goto fail;
     }
 
+#ifdef HAVE_KCM_RENEWAL
     ret = kcm_get_renewal_config(kctx, &krb5_ctx, &renew_intv);
     if (ret != EOK) {
         DEBUG(SSSDBG_FATAL_FAILURE, "fatal error getting KCM renewal config\n");
@@ -278,7 +282,7 @@ static int kcm_process_init(TALLOC_CTX *mem_ctx,
             goto fail;
         }
     }
-
+#endif /* HAVE_KCM_RENEWAL */
     /* Set up file descriptor limits */
     responder_set_fd_limit(kctx->fd_limit);
 
diff --git a/src/responder/kcm/kcmsrv_ccache_secdb.c b/src/responder/kcm/kcmsrv_ccache_secdb.c
index 02bdb1b230..9708687593 100644
--- a/src/responder/kcm/kcmsrv_ccache_secdb.c
+++ b/src/responder/kcm/kcmsrv_ccache_secdb.c
@@ -580,6 +580,7 @@ static errno_t ccdb_secdb_init(struct kcm_ccdb *db,
     return EOK;
 }
 
+#ifdef HAVE_KCM_RENEWAL
 static errno_t renew_check_creds(struct krb5_ctx *krb5_ctx,
                                  struct ccdb_secdb_state *state,
                                  struct sss_sec_ctx *sctx,
@@ -779,7 +780,7 @@ static errno_t ccdb_secdb_renew_init(struct krb5_ctx *krb5_ctx,
 
     return EOK;
 }
-
+#endif /* HAVE_KCM_RENEWAL */
 struct ccdb_secdb_nextid_state {
     unsigned int nextid;
 };
@@ -1671,8 +1672,9 @@ static errno_t ccdb_secdb_delete_recv(struct tevent_req *req)
 const struct kcm_ccdb_ops ccdb_secdb_ops = {
     .init = ccdb_secdb_init,
 
+#ifdef HAVE_KCM_RENEWAL
     .renew_init = ccdb_secdb_renew_init,
-
+#endif
     .nextid_send = ccdb_secdb_nextid_send,
     .nextid_recv = ccdb_secdb_nextid_recv,
 
diff --git a/src/tests/intg/Makefile.am b/src/tests/intg/Makefile.am
index f717214ad3..d5ccffc50a 100644
--- a/src/tests/intg/Makefile.am
+++ b/src/tests/intg/Makefile.am
@@ -70,6 +70,12 @@ install-data-hook:
 
 endif
 
+if BUILD_KCM_RENEWAL
+KCM_RENEW = "enabled"
+else
+KCM_RENEW = "disabled"
+endif
+
 cwrap-dbus-system.conf: data/cwrap-dbus-system.conf.in Makefile
 	$(SED) -e "s!@runstatedir[@]!$(runstatedir)!" \
            -e "s!@dbusservicedir[@]!$(dbusservicedir)!" \
@@ -194,6 +200,7 @@ intgcheck-installed: config.py passwd group pam_sss_service pam_sss_alt_service
 	PAM_WRAPPER_PATH=$$(pkg-config --libs pam_wrapper) \
 	PAM_CERT_DB_PATH=$(PAM_CERT_DB_PATH) \
 	SOFTHSM2_CONF=$(SOFTHSM2_CONF) \
+	KCM_RENEW=$(KCM_RENEW) \
 	DBUS_SOCK_DIR="$(DESTDIR)$(runstatedir)/dbus/" \
 	DBUS_SESSION_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/fake_socket" \
 	DBUS_SYSTEM_BUS_ADDRESS="unix:path=$$DBUS_SOCK_DIR/system_bus_socket" \
diff --git a/src/tests/intg/test_kcm.py b/src/tests/intg/test_kcm.py
index 97d0a974f4..ed873518c7 100644
--- a/src/tests/intg/test_kcm.py
+++ b/src/tests/intg/test_kcm.py
@@ -61,6 +61,10 @@ def ccname(self, my_uid=None):
         return "KCM:%d" % my_uid
 
 
+def have_kcm_renewal():
+    return os.environ['KCM_RENEW'] == "enabled"
+
+
 @pytest.fixture(scope="module")
 def kdc_instance(request):
     """Kerberos server instance fixture"""
@@ -629,6 +633,8 @@ def test_kcm_secrets_quota(setup_for_kcm_sec,
     assert out != 0
 
 
+@pytest.mark.skipif(not have_kcm_renewal(),
+                    reason="KCM renewal disabled, skipping")
 def test_kcm_renewals(setup_for_kcm_renewals_secdb):
     """
     Test that basic KCM renewal works

From 550c847428a902403e8e3a3e7431934c20de30fc Mon Sep 17 00:00:00 2001
From: Justin Stephenson <jstep...@redhat.com>
Date: Thu, 11 Feb 2021 19:34:26 +0000
Subject: [PATCH 10/10] KCM: Disable responder idle timeout with renewals

When SSSD is configured/built with KCM renewals, disable the
responder idle timeout to prevent KCM from being in a shut-down
state when it should be executing TGT renewals.
---
 src/responder/kcm/kcm.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src/responder/kcm/kcm.c b/src/responder/kcm/kcm.c
index 2219158da4..25f41b9e9f 100644
--- a/src/responder/kcm/kcm.c
+++ b/src/responder/kcm/kcm.c
@@ -140,6 +140,7 @@ static int kcm_get_config(struct kcm_ctx *kctx)
         goto done;
     }
 
+#ifndef HAVE_KCM_RENEWAL
     if (kctx->cc_be == CCDB_BE_SECRETS || kctx->cc_be == CCDB_BE_SECDB) {
         ret = responder_setup_idle_timeout_config(kctx->rctx);
         if (ret != EOK) {
@@ -148,7 +149,7 @@ static int kcm_get_config(struct kcm_ctx *kctx)
             /* Not fatal */
         }
     }
-
+#endif
     kctx->qctx = kcm_ops_queue_create(kctx, kctx);
     if (ret != EOK) {
         DEBUG(SSSDBG_OP_FAILURE,
_______________________________________________
sssd-devel mailing list -- sssd-devel@lists.fedorahosted.org
To unsubscribe send an email to sssd-devel-le...@lists.fedorahosted.org
Fedora Code of Conduct: 
https://docs.fedoraproject.org/en-US/project/code-of-conduct/
List Guidelines: https://fedoraproject.org/wiki/Mailing_list_guidelines
List Archives: 
https://lists.fedorahosted.org/archives/list/sssd-devel@lists.fedorahosted.org
Do not reply to spam on the list, report it: 
https://pagure.io/fedora-infrastructure

Reply via email to