>From 5ef3c879c03af2d922c75604f20980b355c9d9a2 Mon Sep 17 00:00:00 2001
From: Stephen Gallagher <sgallagh@redhat.com>
Date: Thu, 15 Aug 2013 19:36:26 -0400
Subject: [PATCH 3/3] KRB5: Add support for KEYRING cache type

---
 src/man/sssd-krb5.5.xml          |  23 ++++--
 src/providers/krb5/krb5_child.c  |  49 ++++++++++++
 src/providers/krb5/krb5_common.c |   5 ++
 src/providers/krb5/krb5_utils.c  | 159 +++++++++++++++++++++++++++++++++++++++
 src/providers/krb5/krb5_utils.h  |   2 +
 src/util/sss_krb5.c              |  14 ++++
 src/util/sss_krb5.h              |   2 +
 7 files changed, 248 insertions(+), 6 deletions(-)

diff --git a/src/man/sssd-krb5.5.xml b/src/man/sssd-krb5.5.xml
index df124b4d20f7f3b553d2eac554eaf5411c3c8436..720f39b7bc303957079390b99cf8c4be87c173cb 100644
--- a/src/man/sssd-krb5.5.xml
+++ b/src/man/sssd-krb5.5.xml
@@ -158,12 +158,15 @@
                     <term>krb5_ccname_template (string)</term>
                     <listitem>
                         <para>
-                            Location of the user's credential cache. Two credential
-                            cache types are currently supported: <quote>FILE</quote>
-                            and <quote>DIR</quote>. The cache can be specified either
-                            as <replaceable>TYPE:RESIDUAL</replaceable>, or as an absolute
-                            path, which implies the <quote>FILE</quote> type. In the
-                            template, the following sequences are substituted:
+                            Location of the user's credential cache. Three
+                            credential cache types are currently supported:
+                            <quote>FILE</quote>, <quote>DIR</quote> and
+                            <quote>KEYRING:persistent</quote>. The cache can
+                            be specified either as
+                            <replaceable>TYPE:RESIDUAL</replaceable>, or as an
+                            absolute path, which implies the
+                            <quote>FILE</quote> type. In the template, the
+                            following sequences are substituted:
                             <variablelist>
                                 <varlistentry>
                                     <term>%u</term>
@@ -209,6 +212,14 @@
                             used to create a unique filename in a safe way.
                         </para>
                         <para>
+                            When using KEYRING types, the only supported
+                            mechanism is <quote>KEYRING:persistent:%U</quote>,
+                            which uses the Linux kernel keyring to store
+                            credentials on a per-UID basis. This is also the
+                            recommended choice, as it is the most secure and
+                            predictable method.
+                        </para>
+                        <para>
                             Default: FILE:%d/krb5cc_%U_XXXXXX
                         </para>
                     </listitem>
diff --git a/src/providers/krb5/krb5_child.c b/src/providers/krb5/krb5_child.c
index 3fd12fec3b05292296e0401b807d02812feaa87d..842b50e6e23cf9b00f51c0a01cfd960e4156ee21 100644
--- a/src/providers/krb5/krb5_child.c
+++ b/src/providers/krb5/krb5_child.c
@@ -505,6 +505,19 @@ store_creds_in_ccache(krb5_context ctx, krb5_principal princ,
 {
     krb5_error_code kerr;
     krb5_creds *l_cred;
+    char *ccname;
+
+    if (DEBUG_IS_SET(SSSDBG_TRACE_ALL)) {
+        kerr = krb5_cc_get_full_name(ctx, cc, &ccname);
+        if (kerr != 0) {
+            DEBUG(SSSDBG_TRACE_ALL,
+                  ("Couldn't determine full name of ccache\n"));
+        } else {
+            DEBUG(SSSDBG_TRACE_ALL,
+                  ("Storing credentials in [%s]\n", ccname));
+            krb5_free_string(ctx, ccname);
+        }
+    }
 
     kerr = krb5_cc_initialize(ctx, cc, princ);
     if (kerr != 0) {
@@ -775,6 +788,37 @@ done:
     return kerr;
 }
 
+static krb5_error_code
+create_ccache_keyring(krb5_context ctx,
+                      krb5_principal princ,
+                      char *ccname,
+                      krb5_creds *creds)
+{
+    krb5_error_code kerr;
+    krb5_ccache tmp_cc = NULL;
+
+    DEBUG(SSSDBG_FUNC_DATA, ("Creating ccache at [%s]\n", ccname));
+
+    kerr = krb5_cc_resolve(ctx, ccname, &tmp_cc);
+    if (kerr != 0) {
+        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+        goto done;
+    }
+
+    kerr = store_creds_in_ccache(ctx, princ, tmp_cc, creds);
+    if (kerr != 0) {
+        KRB5_CHILD_DEBUG(SSSDBG_CRIT_FAILURE, kerr);
+        goto done;
+    }
+
+done:
+    if (kerr != 0 && tmp_cc != NULL) {
+        krb5_cc_destroy(ctx, tmp_cc);
+    }
+
+    return kerr;
+}
+
 #endif /* HAVE_KRB5_CC_COLLECTION */
 
 static krb5_error_code
@@ -787,10 +831,15 @@ create_ccache(uid_t uid, gid_t gid, krb5_context ctx,
     switch (cctype) {
         case SSS_KRB5_TYPE_FILE:
             return create_ccache_file(ctx, princ, ccname, creds);
+
 #ifdef HAVE_KRB5_CC_COLLECTION
         case SSS_KRB5_TYPE_DIR:
             return create_ccache_in_dir(uid, gid, ctx, princ, ccname, creds);
+
+        case SSS_KRB5_TYPE_KEYRING:
+            return create_ccache_keyring(ctx, princ, ccname, creds);
 #endif /* HAVE_KRB5_CC_COLLECTION */
+
         default:
             DEBUG(SSSDBG_CRIT_FAILURE, ("Unknown cache type\n"));
             return EINVAL;
diff --git a/src/providers/krb5/krb5_common.c b/src/providers/krb5/krb5_common.c
index e56dc6245dd6f334a510459f31332ac3dfdad52d..86e72157a37a0071af5679a177b4b1c7ef440c39 100644
--- a/src/providers/krb5/krb5_common.c
+++ b/src/providers/krb5/krb5_common.c
@@ -215,6 +215,11 @@ errno_t check_and_export_options(struct dp_option *opts,
         DEBUG(SSSDBG_CONF_SETTINGS, ("ccache is of type DIR\n"));
         krb5_ctx->cc_be = &dir_cc;
         break;
+
+    case SSS_KRB5_TYPE_KEYRING:
+        DEBUG(SSSDBG_CONF_SETTINGS, ("ccache is of type KEYRING\n"));
+        krb5_ctx->cc_be = &keyring_cc;
+        break;
 #endif /* HAVE_KRB5_CC_COLLECTION */
 
     default:
diff --git a/src/providers/krb5/krb5_utils.c b/src/providers/krb5/krb5_utils.c
index 13ba9b5f9c1c4d4c40f125f451a4ba6e41baa961..8166435c09002ea1bf9e381fde60cbb26b8e4af1 100644
--- a/src/providers/krb5/krb5_utils.c
+++ b/src/providers/krb5/krb5_utils.c
@@ -758,6 +758,8 @@ check_cc_validity(const char *location,
      return ret;
 }
 
+
+
 /*======== ccache back end utilities ========*/
 struct sss_krb5_cc_be *
 get_cc_be_ops(enum sss_krb5_cc_type type)
@@ -773,6 +775,10 @@ get_cc_be_ops(enum sss_krb5_cc_type type)
         case SSS_KRB5_TYPE_DIR:
             be = &dir_cc;
             break;
+
+        case SSS_KRB5_TYPE_KEYRING:
+            be = &keyring_cc;
+            break;
 #endif /* HAVE_KRB5_CC_COLLECTION */
 
         case SSS_KRB5_TYPE_UNKNOWN:
@@ -1187,6 +1193,159 @@ struct sss_krb5_cc_be dir_cc = {
     .remove             = cc_dir_remove
 };
 
+
+/*======== Operations on the KEYRING: back end ========*/
+
+errno_t
+cc_keyring_create(const char *location, pcre *illegal_re,
+                  uid_t uid, gid_t gid, bool private_path)
+{
+    const char *residual;
+
+    residual = sss_krb5_residual_check_type(location, SSS_KRB5_TYPE_KEYRING);
+    if (residual == NULL) {
+        DEBUG(SSSDBG_OP_FAILURE, ("Bad ccache type %s\n", location));
+        return EINVAL;
+    }
+
+    /* No special steps are needed to create a kernel keyring.
+     * Everything is handled in libkrb5.
+     */
+    return EOK;
+}
+
+errno_t
+cc_keyring_check_existing(const char *location, uid_t uid,
+                          const char *realm, const char *princ,
+                          const char *cc_template, bool *_active,
+                          bool *_valid)
+{
+    errno_t ret;
+    bool active;
+    bool valid;
+    const char *residual;
+
+    residual = sss_krb5_residual_check_type(location, SSS_KRB5_TYPE_KEYRING);
+    if (!residual) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("%s is not of type KEYRING:\n", location));
+        return EINVAL;
+    }
+
+    /* The keyring cache is always active */
+    active = true;
+
+    /* Check if any user is actively using this cache */
+    ret = check_cc_validity(location, realm, princ, &valid);
+    if (ret != EOK) {
+        return ret;
+    }
+
+    *_active = active;
+    *_valid = valid;
+    return EOK;
+}
+
+const char *
+cc_keyring_cache_for_princ(TALLOC_CTX *mem_ctx, const char *location,
+                           const char *princ)
+{
+    krb5_context context = NULL;
+    krb5_error_code krberr;
+    char *name = NULL;
+    const char *residual;
+    size_t i;
+    size_t count;
+    krb5_principal client_principal = NULL;
+
+    residual = sss_krb5_residual_check_type(location, SSS_KRB5_TYPE_KEYRING);
+    if (!residual) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Cannot get residual from %s\n",
+              location));
+        return NULL;
+    }
+
+    /* residual already points to a subsidiary cache if it of the
+     * form "KEYRING:<type>:<UID>:krb5_cc_XXXXXXX"
+     * For simplicity, we'll count the colons, up to three.
+     */
+    i = count = 0;
+    while (residual[i] && count < 3) {
+        if (residual[i] == ':') {
+            count ++;
+        }
+        i++;
+    }
+
+    if (count >= 3) {
+        return talloc_strdup(mem_ctx, location);
+    }
+
+    krberr = krb5_init_context(&context);
+    if (krberr) {
+        DEBUG(SSSDBG_OP_FAILURE, ("Failed to init kerberos context\n"));
+        return NULL;
+    }
+
+    krberr = krb5_parse_name(context, princ, &client_principal);
+    if (krberr != 0) {
+        KRB5_DEBUG(SSSDBG_OP_FAILURE, context, krberr);
+        DEBUG(SSSDBG_CRIT_FAILURE, ("krb5_parse_name failed.\n"));
+        goto done;
+    }
+
+    /* This function is called only as a way to validate that,
+     * we have the right cache
+     */
+    name = sss_get_ccache_name_for_principal(mem_ctx, context,
+                                             client_principal, location);
+    if (name == NULL) {
+        DEBUG(SSSDBG_CRIT_FAILURE, ("Could not get full name of ccache\n"));
+        goto done;
+    }
+
+    talloc_zfree(name);
+
+    /* Always return the master name here.
+     * We do the above only to ensure that the
+     * principal-specific name exists and can
+     * be found.
+     */
+    name = talloc_strdup(mem_ctx, location);
+
+done:
+    krb5_free_principal(context, client_principal);
+    krb5_free_context(context);
+
+    return name;
+}
+
+errno_t
+cc_keyring_remove(const char *location)
+{
+    const char *residual;
+
+    residual = sss_krb5_residual_check_type(location, SSS_KRB5_TYPE_KEYRING);
+    if (!residual) {
+        DEBUG(SSSDBG_CRIT_FAILURE,
+              ("%s is not of type KEYRING:\n", location));
+        return EINVAL;
+    }
+
+    /* No special steps are needed to create a kernel keyring.
+     * Everything is handled in libkrb5.
+     */
+    return EOK;
+}
+
+struct sss_krb5_cc_be keyring_cc = {
+    .type               = SSS_KRB5_TYPE_KEYRING,
+    .create             = cc_keyring_create,
+    .check_existing     = cc_keyring_check_existing,
+    .ccache_for_princ   = cc_keyring_cache_for_princ,
+    .remove             = cc_keyring_remove
+};
+
 #endif /* HAVE_KRB5_CC_COLLECTION */
 
 errno_t get_domain_or_subdomain(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
diff --git a/src/providers/krb5/krb5_utils.h b/src/providers/krb5/krb5_utils.h
index 4a5904cd7270e5e22c8f9647f5c5cd3b31272a10..cdc9f23641905cff300077f708087e98ba683e0d 100644
--- a/src/providers/krb5/krb5_utils.h
+++ b/src/providers/krb5/krb5_utils.h
@@ -87,12 +87,14 @@ errno_t get_ccache_file_data(const char *ccache_file, const char *client_name,
 #ifdef HAVE_KRB5_CC_COLLECTION
 
 extern struct sss_krb5_cc_be dir_cc;
+extern struct sss_krb5_cc_be keyring_cc;
 
 errno_t cc_dir_create(const char *location, pcre *illegal_re,
                       uid_t uid, gid_t gid, bool private_path);
 
 #endif /* HAVE_KRB5_CC_COLLECTION */
 
+
 errno_t get_domain_or_subdomain(TALLOC_CTX *mem_ctx, struct be_ctx *be_ctx,
                                 char *domain_name,
                                 struct sss_domain_info **dom);
diff --git a/src/util/sss_krb5.c b/src/util/sss_krb5.c
index 457a52c0b91d62e8a0067e85ba172497a0dab421..b25ed24919555666422e6a87ded8688ca76c345f 100644
--- a/src/util/sss_krb5.c
+++ b/src/util/sss_krb5.c
@@ -928,6 +928,7 @@ sss_krb5_free_keytab_entry_contents(krb5_context context,
 
 #define SSS_KRB5_FILE   "FILE:"
 #define SSS_KRB5_DIR    "DIR:"
+#define SSS_KRB5_KEYRING "KEYRING:"
 
 enum sss_krb5_cc_type
 sss_krb5_get_type(const char *full_location)
@@ -945,7 +946,12 @@ sss_krb5_get_type(const char *full_location)
                sizeof(SSS_KRB5_DIR)-1) == 0) {
         return SSS_KRB5_TYPE_DIR;
     }
+    else if (strncmp(full_location, SSS_KRB5_KEYRING,
+               sizeof(SSS_KRB5_KEYRING)-1) == 0) {
+        return SSS_KRB5_TYPE_KEYRING;
+    }
 #endif /* HAVE_KRB5_CC_COLLECTION */
+
     else if (full_location[0] == '/') {
         return SSS_KRB5_TYPE_FILE;
     }
@@ -973,7 +979,12 @@ sss_krb5_residual_by_type(const char *full_location,
         case SSS_KRB5_TYPE_DIR:
             offset = sizeof(SSS_KRB5_DIR)-1;
             break;
+
+        case SSS_KRB5_TYPE_KEYRING:
+            offset = sizeof(SSS_KRB5_KEYRING)-1;
+            break;
 #endif /* HAVE_KRB5_CC_COLLECTION */
+
         default:
             return NULL;
     }
@@ -991,6 +1002,9 @@ sss_krb5_cc_file_path(const char *full_location)
     residual = sss_krb5_residual_by_type(full_location, cc_type);
 
     switch(cc_type) {
+#ifdef HAVE_KRB5_CC_COLLECTION
+        case SSS_KRB5_TYPE_KEYRING:
+#endif /* HAVE_KRB5_CC_COLLECTION */
         case SSS_KRB5_TYPE_FILE:
             return residual;
 #ifdef HAVE_KRB5_CC_COLLECTION
diff --git a/src/util/sss_krb5.h b/src/util/sss_krb5.h
index 1c10d471336d871909c6d5312b2fce53ba5091c7..b1074f813a6b12d132b8c3d0290ad914b003e70f 100644
--- a/src/util/sss_krb5.h
+++ b/src/util/sss_krb5.h
@@ -147,7 +147,9 @@ enum sss_krb5_cc_type {
     SSS_KRB5_TYPE_FILE,
 #ifdef HAVE_KRB5_CC_COLLECTION
     SSS_KRB5_TYPE_DIR,
+    SSS_KRB5_TYPE_KEYRING,
 #endif /* HAVE_KRB5_CC_COLLECTION */
+
     SSS_KRB5_TYPE_UNKNOWN
 };
 
-- 
1.8.3.1

