krb5: Improve cccol sub naming; add gss_store_cred_into2()
authorNicolas Williams <nico@twosigma.com>
Thu, 23 Jan 2020 01:18:14 +0000 (19:18 -0600)
committerNicolas Williams <nico@twosigma.com>
Mon, 2 Mar 2020 23:48:04 +0000 (17:48 -0600)
 - Formalize the TYPE:collection_name:subsidiary_name naming scheme for
   ccaches in ccache collections
    - KEYRING: ccaches are weird because they have one more optional field: the
      "anchor", so rather than just assume a naming convention everywhere, we
      add new functions as well
 - Add krb5_cc_{resolve,default}_sub() that allows one to specify a
   "subsidiary" ccache name in a collection separately from the
   collection name
 - Add krb5_cc_{resolve,default}_for() which take a principal name,
   unparse it, and use it as the subsidiary ccache name (with colons
   replaced)
 - Make kinit use the new interfaces
 - Add missing DIR ccache iteration functionality
 - Revamps test_cc
 - Add krb5_cc_get_collection() and krb5_cc_get_subsidiary()
 - Bump the ccops SPI version number
 - Add gss_store_cred_into2()
 - Make MEMORY:anonymous not linked into the global MEMORY ccache
   collection, and uses this for delegated cred handles

TBD:

 - Split this up into a krb5 change and gss mech_krb5 change?
 - Add krb5_cc_init_and_store() utility, per Greg's suggestion?

33 files changed:
kcm/glue.c
kuser/kinit.c
lib/gssapi/gssapi/gssapi.h
lib/gssapi/gssapi_mech.h
lib/gssapi/krb5/accept_sec_context.c
lib/gssapi/krb5/copy_ccache.c
lib/gssapi/krb5/external.c
lib/gssapi/krb5/set_cred_option.c
lib/gssapi/krb5/store_cred.c
lib/gssapi/libgssapi-exports.def
lib/gssapi/mech/gss_store_cred_into.c
lib/gssapi/ntlm/external.c
lib/gssapi/spnego/external.c
lib/gssapi/test_add_store_cred.c
lib/gssapi/version-script.map
lib/krb5/acache.c
lib/krb5/cache.c
lib/krb5/dcache.c
lib/krb5/fcache.c
lib/krb5/kcm.c
lib/krb5/krb5.h
lib/krb5/krcache.c
lib/krb5/libkrb5-exports.def.in
lib/krb5/mcache.c
lib/krb5/scache.c
lib/krb5/test_cc.c
lib/krb5/verify_krb5_conf.c
lib/krb5/version-script.map
tests/gss/check-basic.in
tests/gss/check-context.in
tests/kdc/Makefile.am
tests/kdc/check-cc.in
tests/kdc/krb5-cccol.conf.in [new file with mode: 0644]

index b4a13196377dea9e8a071c642e48c101a2dba9cc..d087a26cfe29281b1221deb7f0d3522dcd363390 100644 (file)
@@ -44,15 +44,27 @@ RCSID("$Id$");
 #define KCMCACHE(X)    ((kcm_ccache)(X)->data.data)
 #define CACHENAME(X)   (KCMCACHE(X)->name)
 
-static const char *
+static krb5_error_code
 kcmss_get_name(krb5_context context,
-              krb5_ccache id)
+              krb5_ccache id,
+               const char **name,
+               const char **col,
+               const char **sub)
 {
-    return CACHENAME(id);
+    if (name)
+        *name = CACHENAME(id);
+    if (col)
+        *col = NULL;
+    if (name)
+        *sub = CACHENAME(id);
+    return 0;
 }
 
 static krb5_error_code
-kcmss_resolve(krb5_context context, krb5_ccache *id, const char *res)
+kcmss_resolve(krb5_context context,
+              krb5_ccache *id,
+              const char *res,
+              const char *sub)
 {
     return KRB5_FCC_INTERNAL;
 }
index d32be43160a7558d99d751e2f25745ce381b3d30..32d8a36c4179dd0b48a4cdd5c6912825b108836b 100644 (file)
@@ -1316,65 +1316,12 @@ get_princ_kt(krb5_context context,
     free(def_realm);
 }
 
-static krb5_error_code
-get_switched_ccache(krb5_context context,
-                   const char * type,
-                   krb5_principal principal,
-                   krb5_ccache *ccache)
-{
-    krb5_error_code ret;
-
-#ifdef _WIN32
-    if (strcmp(type, "API") == 0) {
-       /*
-        * Windows stores the default ccache name in the
-        * registry which is shared across multiple logon
-        * sessions for the same user.  The API credential
-        * cache provides a unique name space per logon
-        * session.  Therefore there is no need to generate
-        * a unique ccache name.  Instead use the principal
-        * name.  This provides a friendlier user experience.
-        */
-       char * unparsed_name;
-       char * cred_cache;
-
-       ret = krb5_unparse_name(context, principal,
-                               &unparsed_name);
-       if (ret)
-           krb5_err(context, 1, ret,
-                    N_("unparsing principal name", ""));
-
-       ret = asprintf(&cred_cache, "API:%s", unparsed_name);
-       krb5_free_unparsed_name(context, unparsed_name);
-       if (ret == -1 || cred_cache == NULL)
-           krb5_err(context, 1, ret,
-                     N_("building credential cache name", ""));
-
-       ret = krb5_cc_resolve(context, cred_cache, ccache);
-       free(cred_cache);
-    } else if (strcmp(type, "MSLSA") == 0) {
-       /*
-        * The Windows MSLSA cache when it is writeable
-        * stores tickets for multiple client principals
-        * in a single credential cache.
-        */
-       ret = krb5_cc_resolve(context, "MSLSA:", ccache);
-    } else {
-       ret = krb5_cc_new_unique(context, type, NULL, ccache);
-    }
-#else /* !_WIN32 */
-    ret = krb5_cc_new_unique(context, type, NULL, ccache);
-#endif /* _WIN32 */
-
-    return ret;
-}
-
 int
 main(int argc, char **argv)
 {
     krb5_error_code ret;
     krb5_context context;
-    krb5_ccache  ccache;
+    krb5_ccache  ccache = NULL;
     krb5_principal principal = NULL;
     int optidx = 0;
     krb5_deltat ticket_life = 0;
@@ -1477,42 +1424,8 @@ main(int argc, char **argv)
 
     if (cred_cache)
        ret = krb5_cc_resolve(context, cred_cache, &ccache);
-    else {
-       if (argc > 1) {
-           char s[1024];
-           ret = krb5_cc_new_unique(context, NULL, NULL, &ccache);
-           if (ret)
-               krb5_err(context, 1, ret, "creating cred cache");
-           snprintf(s, sizeof(s), "%s:%s",
-                    krb5_cc_get_type(context, ccache),
-                    krb5_cc_get_name(context, ccache));
-           setenv("KRB5CCNAME", s, 1);
-           unique_ccache = TRUE;
-       } else {
-           ret = krb5_cc_cache_match(context, principal, &ccache);
-           if (ret) {
-               const char *type;
-               ret = krb5_cc_default(context, &ccache);
-               if (ret)
-                   krb5_err(context, 1, ret,
-                            N_("resolving credentials cache", ""));
-
-               /*
-                * Check if the type support switching, and we do,
-                * then do that instead over overwriting the current
-                * default credential
-                */
-               type = krb5_cc_get_type(context, ccache);
-               if (krb5_cc_support_switch(context, type)) {
-                   krb5_cc_close(context, ccache);
-                   ret = get_switched_ccache(context, type, principal,
-                                             &ccache);
-                   if (ret == 0)
-                       unique_ccache = TRUE;
-               }
-           }
-       }
-    }
+    else
+        ret = krb5_cc_default_for(context, principal, &ccache);
     if (ret)
        krb5_err(context, 1, ret, N_("resolving credentials cache", ""));
 
index 4f3777228430812958f927346867a732c8f987c8..54e11ab4d157c111afb7ac5ade2c56a3968d4759 100644 (file)
@@ -1187,6 +1187,29 @@ gss_store_cred_into(
     gss_cred_usage_t * /* cred_usage_stored */
     );
 
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_store_cred_into2(
+    OM_uint32 * /* minor_status */,
+    gss_const_cred_id_t /* input_cred_handle */,
+    gss_cred_usage_t /* input_usage */,
+    const gss_OID /* desired_mech */,
+    OM_uint32 /* store_cred_flags */,
+    gss_const_key_value_set_t /* cred_store */,
+    gss_OID_set * /* elements_stored */,
+    gss_cred_usage_t * /* cred_usage_stored */,
+    gss_buffer_set_t * /* env */
+    );
+
+enum gss_store_cred_flags {
+    GSS_C_STORE_CRED_DEFAULT = 1,
+    GSS_C_STORE_CRED_OVERWRITE = 2,
+    GSS_C_STORE_CRED_SET_PROCESS = 4,
+};
+#define GSS_C_STORE_CRED_DEFAULT GSS_C_STORE_CRED_DEFAULT
+#define GSS_C_STORE_CRED_OVERWRITE GSS_C_STORE_CRED_OVERWRITE
+#define GSS_C_STORE_CRED_SET_PROCESS GSS_C_STORE_CRED_SET_PROCESS
+
+
 GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_CALLCONV
 gss_set_neg_mechs(
     OM_uint32 * /* minor_status */,
index 68732f7f77747cc9d72e6fae5e856804c923750e..1199bb6b8ebf00c12ec64f714d4e0c872d628bdf 100644 (file)
@@ -474,6 +474,17 @@ _gss_store_cred_into_t(OM_uint32 *minor_status,
                       gss_OID_set *elements_stored,
                       gss_cred_usage_t *cred_usage_stored);
 
+typedef OM_uint32 GSSAPI_CALLCONV
+_gss_store_cred_into2_t(OM_uint32 *minor_status,
+                       gss_const_cred_id_t input_cred_handle,
+                       gss_cred_usage_t input_usage,
+                       gss_OID desired_mech,
+                       OM_uint32 store_cred_flags,
+                       gss_const_key_value_set_t cred_store,
+                       gss_OID_set *elements_stored,
+                       gss_cred_usage_t *cred_usage_stored,
+                        gss_buffer_set_t *env);
+
 typedef OM_uint32 GSSAPI_CALLCONV
 _gss_set_neg_mechs_t(OM_uint32 *minor_status,
                     gss_cred_id_t cred_handle,
@@ -623,6 +634,7 @@ typedef struct gssapi_mech_interface_desc {
        _gss_query_mechanism_info_t     *gm_query_mechanism_info;
        _gss_query_meta_data_t          *gm_query_meta_data;
        _gss_exchange_meta_data_t       *gm_exchange_meta_data;
+       _gss_store_cred_into2_t         *gm_store_cred_into2;
         struct gss_mech_compat_desc_struct  *gm_compat;
 } gssapi_mech_interface_desc, *gssapi_mech_interface;
 
index d4680e9e8fb6e0ef346ec7248d950c994a409cf9..9100c71b971e45ee54baaf5ddd63b6a32ed5a23c 100644 (file)
@@ -169,8 +169,7 @@ gsskrb5_accept_delegated_token
     }
 
     *delegated_cred_handle = NULL;
-    kret = krb5_cc_new_unique (context, krb5_cc_type_memory,
-                               NULL, &ccache);
+    kret = krb5_cc_resolve(context, "MEMORY:anonymous", &ccache);
     if (kret) {
        ctx->flags &= ~GSS_C_DELEG_FLAG;
        goto out;
@@ -204,7 +203,7 @@ gsskrb5_accept_delegated_token
        gsskrb5_cred handle;
 
        ret = _gsskrb5_krb5_import_cred(minor_status,
-                                       ccache,
+                                       &ccache,
                                        NULL,
                                        NULL,
                                        delegated_cred_handle);
@@ -212,10 +211,7 @@ gsskrb5_accept_delegated_token
            goto out;
 
        handle = (gsskrb5_cred) *delegated_cred_handle;
-
        handle->cred_flags |= GSS_CF_DESTROY_CRED_ON_RELEASE;
-       krb5_cc_close(context, ccache);
-       ccache = NULL;
     }
 
 out:
index 14296bccd7e2df3109f700912a1add3a1a15b09c..f32eec70126ce27b41dd1b50c4e7b799d7b61c67 100644 (file)
@@ -62,9 +62,18 @@ gss_krb5_copy_ccache(OM_uint32 *minor_status,
 #endif
 
 
+/*
+ * WARNING: Takes ownership of `id'.  Because MEMORY:anonymous is now not
+ * linked into to the MEMORY ccache namespace, we can't use krb5_cc_resolve()
+ * with the cache's name anymore.  We need a krb5_cc_clone() or some such, with
+ * attendant new method for ccops.  Or we could create a new MEMORY:anonymous
+ * ccache and copy all the creds from `id' into it.  But we know callers of
+ * this function don't need `id' after calling it, so for now we'll just take
+ * ownershipd of it.
+ */
 OM_uint32
 _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
-                         krb5_ccache id,
+                         krb5_ccache *id,
                          krb5_principal keytab_principal,
                          krb5_keytab keytab,
                          gss_cred_id_t *cred)
@@ -88,14 +97,13 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
 
     handle->usage = 0;
 
-    if (id) {
+    if (*id) {
        time_t now;
        OM_uint32 left;
-       char *str;
 
        handle->usage |= GSS_C_INITIATE;
 
-       kret = krb5_cc_get_principal(context, id,
+       kret = krb5_cc_get_principal(context, *id,
                                     &handle->principal);
        if (kret) {
            free(handle);
@@ -121,7 +129,7 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
        krb5_timeofday(context, &now);
        ret = __gsskrb5_ccache_lifetime(minor_status,
                                        context,
-                                       id,
+                                       *id,
                                        handle->principal,
                                        &left);
        if (ret != GSS_S_COMPLETE) {
@@ -131,12 +139,8 @@ _gsskrb5_krb5_import_cred(OM_uint32 *minor_status,
        }
        handle->endtime = now + left;
 
-       kret = krb5_cc_get_full_name(context, id, &str);
-       if (kret)
-           goto out;
-
-       kret = krb5_cc_resolve(context, str, &handle->ccache);
-       free(str);
+        handle->ccache = *id;
+        *id = NULL;
        if (kret)
            goto out;
     }
index 71fc3974db7d66ec222965176f3dbb452157e80e..0af3e3e599be6f7711e06dc8a05f8398686b719c 100644 (file)
@@ -404,6 +404,7 @@ static gssapi_mech_interface_desc krb5_mech = {
     NULL, /* gm_query_mechanism_info */
     NULL, /* gm_query_meta_data */
     NULL, /* gm_exchange_meta_data */
+    _gsskrb5_store_cred_into2,
     NULL  /* gm_compat */
 };
 
index 1411f866563cc76ddccdccaba946b30fb35d374a..ef177a0ef473155ebc100f52a1ac9cc658d88eaa 100644 (file)
@@ -105,7 +105,7 @@ import_cred(OM_uint32 *minor_status,
     free(str);
     str = NULL;
 
-    major_stat = _gsskrb5_krb5_import_cred(minor_status, id, keytab_principal,
+    major_stat = _gsskrb5_krb5_import_cred(minor_status, &id, keytab_principal,
                                           keytab, cred_handle);
 out:
     if (id)
index c7c95b943ff4b4287f71d48caf4941b81344a7f9..f18003853387295c20dcb9180a7e6bcea3aaba7c 100644 (file)
@@ -51,64 +51,54 @@ same_princ(krb5_context context, krb5_ccache id1, krb5_ccache id2)
     return same;
 }
 
-/*
- * Like krb5_cc_cache_match(), but only looking in the default collection.
- *
- * We need this to avoid looking for MEMORY ccaches, which risks matching the
- * same credential that we're storing.  We could make sure that MEMORY ccaches
- * are searched for last in krb5_cc_cache_match(), then ignore any MEMORY
- * ccaches we find there, but, if we might then store in a ccache that will not
- * be found later as the default ccache, then it's not worth it.
- *
- * XXX In order to remove this, we'll first need to make sure that
- *     krb5_cc_default() searches all collections when KRB5CCNAME is not set,
- *     then we'll need to make sure that krb5_cc_cache_match() searches MEMORY
- *     ccaches last (or else introduce a new ccache type like MEMORY but which
- *     is never searched or searchable), then make sure that the caller below
- *     treat finding a MEMORY the same as not finding a ccache at all.
- */
-static krb5_error_code
-ccache_match(krb5_context context,
-             krb5_principal princ,
-             const char *cctype,
-             krb5_ccache *id)
+static OM_uint32
+add_env(OM_uint32 *minor,
+        gss_buffer_set_t *env,
+        const char *var,
+        const char *val)
 {
-    krb5_cc_cache_cursor cursor = NULL;
-    krb5_error_code ret;
+    OM_uint32 major;
+    gss_buffer_desc b;
+    char *varval = NULL;
 
-    *id = NULL;
-    ret = krb5_cc_cache_get_first(context, cctype, &cursor);
-    if (ret)
-        return ret;
+    if (asprintf(&varval, "%s=%s", var, val) == -1 || varval == NULL) {
+        *minor = ENOMEM;
+        return GSS_S_FAILURE;
+    }
 
-    while (krb5_cc_cache_next(context, cursor, id) == 0) {
-        krb5_principal p = NULL;
+    b.value = varval;
+    b.length = strlen(varval) + 1;
+    major = gss_add_buffer_set_member(minor, &b, env);
+    free(varval);
+    return major;
+}
 
-        ret = krb5_cc_get_principal(context, *id, &p);
-        if (ret == 0 &&
-            krb5_principal_compare(context, princ, p)) {
-            krb5_free_principal(context, p);
-            krb5_cc_cache_end_seq_get(context, cursor);
-            return 0;
-        }
-        if (*id)
-            krb5_cc_close(context, *id);
-        *id = NULL;
-    }
-    krb5_cc_cache_end_seq_get(context, cursor);
-    return KRB5_CC_END;
+static OM_uint32
+set_proc(OM_uint32 *minor, gss_buffer_set_t env)
+{
+    size_t i;
+
+    /*
+     * XXX On systems with setpag(), call setpag().  On WIN32... create a
+     * session, set the access token, ...?
+     */
+#ifndef WIN32
+    for (i = 0; i < env->count; i++)
+        putenv(env->elements[i].value);
+#endif
+    return GSS_S_COMPLETE;
 }
 
 OM_uint32 GSSAPI_CALLCONV
-_gsskrb5_store_cred_into(OM_uint32         *minor_status,
-                        gss_const_cred_id_t input_cred_handle,
-                        gss_cred_usage_t  cred_usage,
-                        const gss_OID     desired_mech,
-                        OM_uint32         overwrite_cred,
-                        OM_uint32         default_cred,
-                        gss_const_key_value_set_t cred_store,
-                        gss_OID_set       *elements_stored,
-                        gss_cred_usage_t  *cred_usage_stored)
+_gsskrb5_store_cred_into2(OM_uint32         *minor_status,
+                         gss_const_cred_id_t input_cred_handle,
+                         gss_cred_usage_t  cred_usage,
+                         const gss_OID     desired_mech,
+                         OM_uint32         store_cred_flags,
+                         gss_const_key_value_set_t cred_store,
+                         gss_OID_set       *elements_stored,
+                         gss_cred_usage_t  *cred_usage_stored,
+                          gss_buffer_set_t  *envp)
 {
     krb5_context context;
     krb5_error_code ret;
@@ -116,8 +106,11 @@ _gsskrb5_store_cred_into(OM_uint32         *minor_status,
     krb5_ccache id = NULL;
     time_t exp_current;
     time_t exp_new;
+    gss_buffer_set_t env = GSS_C_NO_BUFFER_SET;
     const char *cs_ccache_name = NULL;
-    OM_uint32 major_status;
+    OM_uint32 major_status, junk;
+    OM_uint32 overwrite_cred = store_cred_flags & GSS_C_STORE_CRED_OVERWRITE;
+    OM_uint32 default_cred = store_cred_flags & GSS_C_STORE_CRED_DEFAULT;
 
     *minor_status = 0;
 
@@ -168,45 +161,17 @@ _gsskrb5_store_cred_into(OM_uint32         *minor_status,
     }
 
     if (cs_ccache_name) {
-        /*
-         * Not the default ccache.
-         *
-         * Therefore not a collection type cache.
-         *
-         * Therefore there's no question of switching the primary ccache.
-         *
-         * Therefore we reset default_cred.
-         *
-         * XXX Perhaps we should fail in this case if default_cred is true.
-         */
         default_cred = 0;
-       ret = krb5_cc_resolve(context, cs_ccache_name, &id);
+        ret = krb5_cc_resolve(context, cs_ccache_name, &id);
     } else {
-        const char *cctype = NULL;
-
         /*
          * Use the default ccache, and if it's a collection, switch it if
          * default_cred is true.
          */
-        ret = krb5_cc_default(context, &id);
-        if (ret == 0) {
-           cctype = krb5_cc_get_type(context, id);
-            if (krb5_cc_support_switch(context, cctype)) {
-                /* The default ccache is a collection type */
-
-                krb5_cc_close(context, id);
-                id = NULL;
-
-                /* Find a matching ccache or create a new one */
-                ret = ccache_match(context, input_cred->principal,
-                                   cctype, &id);
-                if (ret || id == NULL) {
-                    /* Since the ccache is new, just store unconditionally */
-                    overwrite_cred = 1;
-                    ret = krb5_cc_new_unique(context, cctype, NULL, &id);
-                }
-            }
-        }
+        ret = krb5_cc_default_for(context, input_cred->principal, &id);
+        if (ret == 0 &&
+            krb5_cc_support_switch(context, krb5_cc_get_type(context, id)))
+            overwrite_cred = 1;
     }
 
     if (ret || id == NULL) {
@@ -215,14 +180,7 @@ _gsskrb5_store_cred_into(OM_uint32         *minor_status,
        return ret == 0 ? GSS_S_NO_CRED : GSS_S_FAILURE;
     }
 
-    /*
-     * If the new creds are for a different principal than we had before,
-     * overwrite.
-     */
-    if (!overwrite_cred && !same_princ(context, id, input_cred->ccache))
-        overwrite_cred = 1;
-
-    if (!overwrite_cred) {
+    if (!overwrite_cred && same_princ(context, id, input_cred->ccache)) {
         /*
          * If current creds are for the same princ as we already had creds for,
          * and the new creds live longer than the old, overwrite.
@@ -246,9 +204,47 @@ _gsskrb5_store_cred_into(OM_uint32         *minor_status,
                                    NULL);
     if (ret == 0 && default_cred)
         krb5_cc_switch(context, id);
+
+    if ((store_cred_flags & GSS_C_STORE_CRED_SET_PROCESS) && envp == NULL)
+        envp = &env;
+    if (envp != NULL) {
+        char *fullname = NULL;
+        
+        if ((ret = krb5_cc_get_full_name(context, id, &fullname)) == 0) {
+            major_status = add_env(minor_status, envp, "KRB5CCNAME", fullname);
+            free(fullname);
+            if (major_status)
+                ret = *minor_status;
+        }
+    }
     (void) krb5_cc_close(context, id);
 
     HEIMDAL_MUTEX_unlock(&input_cred->cred_id_mutex);
+    if (ret == 0 && (store_cred_flags & GSS_C_STORE_CRED_SET_PROCESS) &&
+        (major_status = set_proc(minor_status, *envp)) != GSS_S_COMPLETE)
+        ret = *minor_status;
+    (void) gss_release_buffer_set(&junk, &env);
     *minor_status = ret;
     return ret ? GSS_S_FAILURE : GSS_S_COMPLETE;
 }
+
+OM_uint32 GSSAPI_CALLCONV
+_gsskrb5_store_cred_into(OM_uint32         *minor_status,
+                        gss_const_cred_id_t input_cred_handle,
+                        gss_cred_usage_t  cred_usage,
+                        const gss_OID     desired_mech,
+                        OM_uint32         overwrite_cred,
+                        OM_uint32         default_cred,
+                        gss_const_key_value_set_t cred_store,
+                        gss_OID_set       *elements_stored,
+                        gss_cred_usage_t  *cred_usage_stored)
+{
+    OM_uint32 store_cred_flags =
+        (overwrite_cred ? GSS_C_STORE_CRED_OVERWRITE : 0) |
+        (default_cred ? GSS_C_STORE_CRED_DEFAULT : 0);
+
+    return _gsskrb5_store_cred_into2(minor_status, input_cred_handle,
+                                     cred_usage, desired_mech,
+                                     store_cred_flags, cred_store,
+                                     elements_stored, cred_usage_stored, NULL);
+}
index 992e8eb3fa170f2e824ffb67feffe87c2eb28e23..c699cad43ff2ce54398196a817c03a60a7fffe43 100644 (file)
@@ -96,6 +96,7 @@ EXPORTS
        gss_sign
        gss_store_cred
        gss_store_cred_into
+       gss_store_cred_into2
        gss_test_oid_set_member
        gss_unseal
        gss_unwrap
index 3009fa4a23ca6788098ced6c19461bc9f7819338..1c739b056703e300390c35cda15a44cb65f52ce0 100644 (file)
@@ -38,14 +38,23 @@ store_mech_cred(OM_uint32 *minor_status,
                gssapi_mech_interface m,
                const struct _gss_mechanism_cred *mc,
                gss_cred_usage_t input_usage,
-               OM_uint32 overwrite_cred,
-               OM_uint32 default_cred,
+               OM_uint32 store_cred_flags,
                gss_const_key_value_set_t cred_store,
-               gss_cred_usage_t *usage_stored)
+               gss_cred_usage_t *usage_stored,
+                gss_buffer_set_t *env)
 {
     OM_uint32 major_status;
-
-    if (m->gm_store_cred_into) 
+    OM_uint32 overwrite_cred =
+        !!(store_cred_flags & GSS_C_STORE_CRED_OVERWRITE);
+    OM_uint32 default_cred = !!(store_cred_flags & GSS_C_STORE_CRED_DEFAULT);
+
+    if (m->gm_store_cred_into2)
+       major_status = m->gm_store_cred_into2(minor_status, mc->gmc_cred,
+                                             input_usage, &m->gm_mech_oid,
+                                              store_cred_flags, cred_store,
+                                              NULL, usage_stored,
+                                              env);
+    else if (m->gm_store_cred_into)
        major_status = m->gm_store_cred_into(minor_status, mc->gmc_cred,
                                             input_usage, &m->gm_mech_oid,
                                             overwrite_cred, default_cred,
@@ -66,25 +75,28 @@ store_mech_cred(OM_uint32 *minor_status,
  * const key/value hashmap-like thing that specifies a credential store in a
  * mechanism- and implementation-specific way, though Heimdal and MIT agree on
  * at least the following keys for the Kerberos mechanism: ccache, keytab, and
- * client_keytab.
+ * client_keytab.  A set of environment variables may be output as well
  */
 GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
-gss_store_cred_into(OM_uint32 *minor_status,
-                   gss_const_cred_id_t input_cred_handle,
-                   gss_cred_usage_t input_usage,
-                   const gss_OID desired_mech,
-                   OM_uint32 overwrite_cred,
-                   OM_uint32 default_cred,
-                   gss_const_key_value_set_t cred_store,
-                   gss_OID_set *elements_stored,
-                   gss_cred_usage_t *cred_usage_stored)
+gss_store_cred_into2(OM_uint32 *minor_status,
+                     gss_const_cred_id_t input_cred_handle,
+                     gss_cred_usage_t input_usage,
+                     const gss_OID desired_mech,
+                     OM_uint32 store_cred_flags,
+                     gss_const_key_value_set_t cred_store,
+                     gss_OID_set *elements_stored,
+                     gss_cred_usage_t *cred_usage_stored,
+                     gss_buffer_set_t *env)
 {
-    struct _gss_cred *cred = (struct _gss_cred *) input_cred_handle;
+    struct _gss_cred *cred = (struct _gss_cred *)input_cred_handle;
     struct _gss_mechanism_cred *mc;
     OM_uint32 major_status;
     OM_uint32 minor;
     size_t successes;
 
+    if (env != NULL)
+        *env = NULL;
+
     if (input_cred_handle == NULL)
        return GSS_S_CALL_INACCESSIBLE_READ;
 
@@ -117,10 +129,9 @@ gss_store_cred_into(OM_uint32 *minor_status,
             !gss_oid_equal(&m->gm_mech_oid, desired_mech))
             continue;
 
-       major_status = store_mech_cred(minor_status, m, mc,
-                                      input_usage, overwrite_cred,
-                                      default_cred, cred_store,
-                                      cred_usage_stored);
+        major_status = store_mech_cred(minor_status, m, mc, input_usage,
+                                       store_cred_flags, cred_store,
+                                       cred_usage_stored, env);
        if (major_status == GSS_S_COMPLETE) {
             if (elements_stored && desired_mech != GSS_C_NO_OID)
                 gss_add_oid_set_member(&minor, desired_mech, elements_stored);
@@ -142,3 +153,29 @@ gss_store_cred_into(OM_uint32 *minor_status,
 
     return major_status;
 }
+
+/*
+ * See RFC5588 for gss_store_cred().  This function is a variant that takes a
+ * const key/value hashmap-like thing that specifies a credential store in a
+ * mechanism- and implementation-specific way, though Heimdal and MIT agree on
+ * at least the following keys for the Kerberos mechanism: ccache, keytab, and
+ * client_keytab.
+ */
+GSSAPI_LIB_FUNCTION OM_uint32 GSSAPI_LIB_CALL
+gss_store_cred_into(OM_uint32 *minor_status,
+                   gss_const_cred_id_t input_cred_handle,
+                   gss_cred_usage_t input_usage,
+                   const gss_OID desired_mech,
+                   OM_uint32 overwrite_cred,
+                   OM_uint32 default_cred,
+                   gss_const_key_value_set_t cred_store,
+                   gss_OID_set *elements_stored,
+                   gss_cred_usage_t *cred_usage_stored)
+{
+    OM_uint32 store_cred_flags =
+        (overwrite_cred ? GSS_C_STORE_CRED_OVERWRITE : 0) |
+        (default_cred ? GSS_C_STORE_CRED_DEFAULT : 0);
+    return gss_store_cred_into2(minor_status, input_cred_handle, input_usage,
+                                desired_mech, store_cred_flags, cred_store,
+                                elements_stored, cred_usage_stored, NULL);
+}
index 01162797537d64b5c711783ccadceeef1e22061c..02590c96d31c34a04072047107c90d0df92e715c 100644 (file)
@@ -130,6 +130,7 @@ static gssapi_mech_interface_desc ntlm_mech = {
     NULL, /* gm_query_mechanism_info */
     NULL, /* gm_query_meta_data */
     NULL, /* gm_exchange_meta_data */
+    NULL, /* gm_store_cred_into2 */
     NULL, /* gm_compat */
 };
 
index e0744f4b2079cf74789fb1d0d0ccf6b5350825ff..b1393f9d5c3e0219c733059efeea0b84e4c06791 100644 (file)
@@ -154,6 +154,7 @@ static gssapi_mech_interface_desc spnego_mech = {
     NULL, /* gm_query_mechanism_info */
     NULL, /* gm_query_meta_data */
     NULL, /* gm_exchange_meta_data */
+    NULL, /* gm_store_cred_into2 */
     NULL  /* gm_compat */
 };
 
index 95ef7440d4b2c5eed97c95f6d9f8b11fc429bb5d..03c3dc95a3eb202a86c8cc4a375f739ca1c250c3 100644 (file)
@@ -94,12 +94,21 @@ gss_err(int exitval, OM_uint32 major, OM_uint32 minor, gss_OID mech,
     exit(exitval);
 }
 
-static int version_flag = 0;
-static int help_flag    = 0;
+static int version_flag         = 0;
+static int help_flag            = 0;
+static int env_flag             = 0;
+static int def_flag             = 0;
+static int overwrite_flag       = 0;
 
 static struct getargs args[] = {
     {"version", 0,      arg_flag,       &version_flag, "print version", NULL },
-    {"help",    0,      arg_flag,       &help_flag,  NULL, NULL }
+    {"help",    0,      arg_flag,       &help_flag,  NULL, NULL },
+    {"env",     'e',    arg_flag,       &env_flag,
+     "output env settings", NULL },
+    {"default", 0,      arg_flag,       &def_flag,
+     "switch credential store default principal", NULL },
+    {"overwrite", 0,    arg_flag,       &overwrite_flag,
+     "overwrite matching credential", NULL },
 };
 
 static void
@@ -119,6 +128,8 @@ main(int argc, char **argv)
     gss_cred_id_t cred = GSS_C_NO_CREDENTIAL;
     gss_key_value_element_desc from_elements, to_elements;
     gss_key_value_set_desc from, to;
+    gss_buffer_set_t env = GSS_C_NO_BUFFER_SET;
+    OM_uint32 store_flags = 0;
     int optidx = 0;
 
     setprogname(argv[0]);
@@ -133,6 +144,11 @@ main(int argc, char **argv)
         exit(0);
     }
 
+    if (def_flag)
+        store_flags |= GSS_C_STORE_CRED_DEFAULT;
+    if (overwrite_flag)
+        store_flags |= GSS_C_STORE_CRED_OVERWRITE;
+
     argc -= optidx;
     argv += optidx;
 
@@ -159,12 +175,35 @@ main(int argc, char **argv)
         gss_err(1, major, minor, GSS_KRB5_MECHANISM,
                 "failed to acquire creds from %s", argv[0]);
 
-    major = gss_store_cred_into(&minor, from_cred, GSS_C_INITIATE,
-                               GSS_KRB5_MECHANISM, 1, 1, &to, NULL, NULL);
+    major = gss_store_cred_into2(&minor, from_cred, GSS_C_INITIATE,
+                                 GSS_KRB5_MECHANISM, store_flags, &to, NULL,
+                                 NULL, env_flag ? &env : NULL);
     if (major != GSS_S_COMPLETE)
         gss_err(1, major, minor, GSS_KRB5_MECHANISM,
                 "failed to store creds into %s", argv[1]);
 
+    if (env_flag) {
+        size_t i;
+        int got_krb5ccname = 0;
+
+        if (env == GSS_C_NO_BUFFER_SET)
+            warnx("No environment settings");
+
+        for (i = 0; env != GSS_C_NO_BUFFER_SET && i < env->count; i++) {
+            got_krb5ccname = got_krb5ccname ||
+                (env->elements[i].length > sizeof("KRB5CCNAME=") &&
+                 strncmp((const char *)env->elements[i].value, "KRB5CCNAME=",
+                         sizeof("KRB5CCNAME=") - 1) == 0);
+            printf("%.*s\n", (int)env->elements[i].length,
+                   (const char *)env->elements[i].value);
+        }
+        (void) gss_release_buffer_set(&minor, &env);
+
+        if (!got_krb5ccname)
+            errx(1, "KRB5CCNAME environment variable not set by "
+                 "gss_store_cred_into2()");
+    }
+
     (void) gss_release_cred(&minor, &from_cred);
     (void) gss_release_cred(&minor, &to_cred);
 
index 635a7b09d6ca56b6ead8d28e77b0189d5eba2863..03ef28e4a2d81ea7412a30cca3a561a2f3747b3b 100644 (file)
@@ -90,6 +90,7 @@ HEIMDAL_GSS_2.0 {
                gss_sign;
                gss_store_cred;
                gss_store_cred_into;
+               gss_store_cred_into2;
                gss_test_oid_set_member;
                gss_unseal;
                gss_unwrap;
index 4f6fa3c0b573578b98857d2c821bc7cc281c61f0..8d4f49c8ab2b8b21923289429352b8be0dbd6e0f 100644 (file)
@@ -49,6 +49,7 @@ static void *cc_handle;
 
 typedef struct krb5_acc {
     char *cache_name;
+    char *cache_subsidiary;
     cc_context_t context;
     cc_ccache_t ccache;
 } krb5_acc;
@@ -442,41 +443,51 @@ get_cc_name(krb5_acc *a)
 }
 
 
-static const char* KRB5_CALLCONV
+static krb5_error_code KRB5_CALLCONV
 acc_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **colname,
+             const char **subsidiary)
 {
+    krb5_error_code ret;
     krb5_acc *a = ACACHE(id);
     int32_t error;
 
-    if (a->cache_name == NULL) {
-       krb5_error_code ret;
-       krb5_principal principal;
-       char *name;
+    if (name)
+        *name = NULL;
+    if (colname)
+        *colname = NULL;
+    if (subsidiary)
+        *subsidiary = NULL;
+    if (a->cache_subsidiary == NULL) {
+       krb5_principal principal = NULL;
 
        ret = _krb5_get_default_principal_local(context, &principal);
-       if (ret)
-           return NULL;
-
-       ret = krb5_unparse_name(context, principal, &name);
+       if (ret == 0)
+            ret = krb5_unparse_name(context, principal, &a->cache_subsidiary);
        krb5_free_principal(context, principal);
        if (ret)
-           return NULL;
-
-       error = (*a->context->func->create_new_ccache)(a->context,
-                                                      cc_credentials_v5,
-                                                      name,
-                                                      &a->ccache);
-       krb5_xfree(name);
-       if (error)
-           return NULL;
-
-       error = get_cc_name(a);
-       if (error)
-           return NULL;
+           return ret;
     }
 
-    return a->cache_name;
+    if (a->cache_name == NULL) {
+        error = (*a->context->func->create_new_ccache)(a->context,
+                                                       cc_credentials_v5,
+                                                       a->cache_subsidiary,
+                                                       &a->ccache);
+        if (error == ccNoError)
+            error = get_cc_name(a);
+        if (error != ccNoError)
+            ret = translate_cc_error(context, error);
+    }
+    if (name)
+        *name = a->cache_name;
+    if (colname)
+        *colname = "";
+    if (subsidiary)
+    *subsidiary = a->cache_subsidiary;
+    return ret;
 }
 
 static krb5_error_code KRB5_CALLCONV
@@ -497,6 +508,10 @@ acc_alloc(krb5_context context, krb5_ccache *id)
     }
 
     a = ACACHE(*id);
+    a->cache_subsidiary = NULL;
+    a->cache_name = NULL;
+    a->context = NULL;
+    a->ccache = NULL;
 
     error = (*init_func)(&a->context, ccapi_version_3, NULL, NULL);
     if (error) {
@@ -504,17 +519,17 @@ acc_alloc(krb5_context context, krb5_ccache *id)
        return translate_cc_error(context, error);
     }
 
-    a->cache_name = NULL;
-
     return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
-acc_resolve(krb5_context context, krb5_ccache *id, const char *res)
+acc_resolve(krb5_context context, krb5_ccache *id, const char *res, const char *sub)
 {
     krb5_error_code ret;
+    cc_time_t offset;
     cc_int32 error;
     krb5_acc *a;
+    char *s = NULL;
 
     ret = acc_alloc(context, id);
     if (ret)
@@ -522,49 +537,44 @@ acc_resolve(krb5_context context, krb5_ccache *id, const char *res)
 
     a = ACACHE(*id);
 
-    error = (*a->context->func->open_ccache)(a->context, res, &a->ccache);
-    if (error == ccNoError) {
-       cc_time_t offset;
-       error = get_cc_name(a);
-       if (error != ccNoError) {
+    if (sub) {
+        if (asprintf(&s, "%s:%s", res, sub) == -1 || s == NULL ||
+            (a->cache_subsidiary = strdup(sub)) == NULL) {
            acc_close(context, *id);
-           *id = NULL;
-           return translate_cc_error(context, error);
-       }
-
-       error = (*a->ccache->func->get_kdc_time_offset)(a->ccache,
-                                                       cc_credentials_v5,
-                                                       &offset);
-       if (error == 0)
-           context->kdc_sec_offset = offset;
+            free(s);
+            return krb5_enomem(context);
+        }
+        res = s;
+        /*
+         * XXX With a bit of extra refactoring we could use the collection name
+         * as the path to the shared object implementing CCAPI...  For now we
+         * ignore the collection name.
+         */
+    }
 
-    } else if (error == ccErrCCacheNotFound) {
-       a->ccache = NULL;
-       a->cache_name = NULL;
-    } else {
-       *id = NULL;
-       return translate_cc_error(context, error);
+    error = (*a->context->func->open_ccache)(a->context, res, &a->ccache);
+    if (error == ccNoError)
+        error = get_cc_name(a);
+    if (error != ccNoError) {
+        acc_close(context, *id);
+        *id = NULL;
+        free(s);
+        return translate_cc_error(context, error);
     }
 
+    error = (*a->ccache->func->get_kdc_time_offset)(a->ccache,
+                                                    cc_credentials_v5,
+                                                    &offset);
+    if (error == 0)
+        context->kdc_sec_offset = offset;
+    free(s);
     return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
 acc_gen_new(krb5_context context, krb5_ccache *id)
 {
-    krb5_error_code ret;
-    krb5_acc *a;
-
-    ret = acc_alloc(context, id);
-    if (ret)
-       return ret;
-
-    a = ACACHE(*id);
-
-    a->ccache = NULL;
-    a->cache_name = NULL;
-
-    return 0;
+    return acc_alloc(context, id);
 }
 
 static krb5_error_code KRB5_CALLCONV
index 8a606529ad41037e9b1fae9a7840f9e7d4579d96..c46ef3b8eaf44402c35063145dc63ca01c5a541e 100644 (file)
@@ -104,7 +104,7 @@ main (int argc, char **argv)
  * Add a new ccache type with operations `ops', overwriting any
  * existing one if `override'.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param ops type of plugin symbol
  * @param override flag to select if the registration is to overide
  * an existing ops with the same name.
@@ -180,10 +180,11 @@ _krb5_cc_allocate(krb5_context context,
  */
 
 static krb5_error_code
-allocate_ccache (krb5_context context,
-                const krb5_cc_ops *ops,
-                const char *residual,
-                krb5_ccache *id)
+allocate_ccache(krb5_context context,
+                const krb5_cc_ops *ops,
+                const char *residual,
+                const char *subsidiary,
+                krb5_ccache *id)
 {
     krb5_error_code ret;
 #ifdef KRB5_USE_PATH_TOKENS
@@ -210,7 +211,7 @@ allocate_ccache (krb5_context context,
        return ret;
     }
 
-    ret = (*id)->ops->resolve(context, id, residual);
+    ret = (*id)->ops->resolve(context, id, residual, subsidiary);
     if(ret) {
        free(*id);
         *id = NULL;
@@ -247,7 +248,7 @@ is_possible_path_name(const char * name)
  * Find and allocate a ccache in `id' from the specification in `residual'.
  * If the ccache name doesn't contain any colon, interpret it as a file name.
  *
- * @param context a Keberos context.
+ * @param context a Kerberos context.
  * @param name string name of a credential cache.
  * @param id return pointer to a found credential cache.
  *
@@ -273,12 +274,12 @@ krb5_cc_resolve(krb5_context context,
        if(strncmp(context->cc_ops[i]->prefix, name, prefix_len) == 0
           && name[prefix_len] == ':') {
            return allocate_ccache (context, context->cc_ops[i],
-                                   name + prefix_len + 1,
+                                   name + prefix_len + 1, NULL,
                                    id);
        }
     }
     if (is_possible_path_name(name))
-       return allocate_ccache (context, &krb5_fcc_ops, name, id);
+       return allocate_ccache (context, &krb5_fcc_ops, name, NULL, id);
     else {
        krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,
                               N_("unknown ccache type %s", "name"), name);
@@ -286,6 +287,153 @@ krb5_cc_resolve(krb5_context context,
     }
 }
 
+static const char *
+get_default_cc_type(krb5_context context, int simple)
+{
+    const char *def_ccname;
+    const char *def_cctype =
+        krb5_config_get_string_default(context, NULL,
+                                       secure_getenv("KRB5CCTYPE"),
+                                       "libdefaults", "default_cc_type", NULL);
+
+    if (!simple &&
+        (def_ccname = krb5_cc_default_name(context))) {
+        size_t i;
+
+        for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
+            size_t prefix_len = strlen(context->cc_ops[i]->prefix);
+
+            if (!strncmp(context->cc_ops[i]->prefix, def_ccname, prefix_len) &&
+                def_ccname[prefix_len] == ':')
+                return context->cc_ops[i]->prefix;
+        }
+        if (is_possible_path_name(def_ccname))
+            return "FILE";
+    }
+    return def_cctype ? def_cctype : "DIR";
+}
+
+/**
+ * Find and allocate a ccache in `id' for the subsidiary cache named by
+ * `subsidiary' in the collection named by `collection'.
+ *
+ * @param context a Kerberos context.
+ * @param cctype string name of a credential cache collection type.
+ * @param collection string name of a credential cache collection.
+ * @param subsidiary string name of a credential cache in a collection.
+ * @param id return pointer to a found credential cache.
+ *
+ * @return Return 0 or an error code. In case of an error, id is set
+ * to NULL, see krb5_get_error_message().
+ *
+ * @ingroup krb5_ccache
+ */
+
+
+KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
+krb5_cc_resolve_sub(krb5_context context,
+                    const char *cctype,
+                    const char *collection,
+                    const char *subsidiary,
+                    krb5_ccache *id)
+{
+    size_t i;
+
+    *id = NULL;
+
+    if (!cctype && collection) {
+        /* Get the cctype from the collection, maybe */
+        for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
+            size_t plen = strlen(context->cc_ops[i]->prefix);
+
+            if ((strncmp(context->cc_ops[i]->prefix, collection, plen) ||
+                 collection[plen] != ':'))
+                continue;
+            cctype = context->cc_ops[i]->prefix;
+            collection += plen + 1;
+            break;
+        }
+    }
+
+
+    if (!cctype) {
+        const char *def_cctype = get_default_cc_type(context, 0);
+        int might_be_path = collection && is_possible_path_name(collection);
+
+        if (def_cctype)
+            cctype = def_cctype;
+        else if (might_be_path && subsidiary)
+            cctype = "DIR";     /* Default to DIR */
+        else if (might_be_path && !subsidiary)
+            cctype = "FILE";    /* Default to FILE */
+    }
+
+    /* If either `cctype' is not NULL or `collection' starts with TYPE: */
+    if (cctype || collection) {
+        for (i = 0; i < context->num_cc_ops && context->cc_ops[i]->prefix; i++) {
+            size_t plen = strlen(context->cc_ops[i]->prefix);
+
+            if (cctype && strcmp(context->cc_ops[i]->prefix, cctype))
+                continue;
+            if (!cctype &&
+                (strncmp(context->cc_ops[i]->prefix, collection, plen) ||
+                 collection[plen] != ':'))
+                continue;
+
+            return allocate_ccache(context, context->cc_ops[i],
+                                   cctype ? collection : collection + plen + 1,
+                                   subsidiary, id);
+        }
+    }
+
+    krb5_set_error_message(context, KRB5_CC_UNKNOWN_TYPE,
+                           N_("unknown ccache type %s", ""),
+                           cctype ? cctype : collection);
+    return KRB5_CC_UNKNOWN_TYPE;
+}
+
+
+/**
+ * Find and allocate a ccache in `id' from the specification in `residual', but
+ * specific to the given principal `principal' by using the principal name as
+ * the name of a "subsidiary" credentials cache in the collection named by
+ * `name'.  If the ccache name doesn't contain any colon, interpret it as a
+ * file name.
+ *
+ * @param context a Kerberos context.
+ * @param name string name of a credential cache.
+ * @param principal principal name of desired credentials.
+ * @param id return pointer to a found credential cache.
+ *
+ * @return Return 0 or an error code. In case of an error, id is set
+ * to NULL, see krb5_get_error_message().
+ *
+ * @ingroup krb5_ccache
+ */
+
+KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
+krb5_cc_resolve_for(krb5_context context,
+                    const char *cctype,
+                   const char *name,
+                    krb5_const_principal principal,
+                    krb5_ccache *id)
+{
+    krb5_error_code ret;
+    char *p, *s;
+
+    *id = NULL;
+
+    ret = krb5_unparse_name(context, principal, &p);
+    if (ret)
+        return ret;
+    /* Subsidiary components cannot have ':'s in them */
+    for (s = strchr(p, ':'); s; s = strchr(s + 1, ':'))
+        *s = '-';
+    ret = krb5_cc_resolve_sub(context, cctype, name, p, id);
+    free(p);
+    return ret;
+}
+
 /**
  * Generates a new unique ccache of `type` in `id'. If `type' is NULL,
  * the library chooses the default credential cache type. The supplied
@@ -334,7 +482,42 @@ KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
 krb5_cc_get_name(krb5_context context,
                 krb5_ccache id)
 {
-    return id->ops->get_name(context, id);
+    const char *name;
+
+    (void) id->ops->get_name(context, id, &name, NULL, NULL);
+    return name;
+}
+
+/**
+ * Return the name of the ccache collection associated with `id'
+ *
+ * @ingroup krb5_ccache
+ */
+
+
+KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
+krb5_cc_get_collection(krb5_context context, krb5_ccache id)
+{
+    const char *name;
+
+    (void) id->ops->get_name(context, id, NULL, &name, NULL);
+    return name;
+}
+
+/**
+ * Return the name of the subsidiary ccache of `id'
+ *
+ * @ingroup krb5_ccache
+ */
+
+
+KRB5_LIB_FUNCTION const char* KRB5_LIB_CALL
+krb5_cc_get_subsidiary(krb5_context context, krb5_ccache id)
+{
+    const char *name;
+
+    (void) id->ops->get_name(context, id, NULL, NULL, &name);
+    return name;
 }
 
 /**
@@ -354,7 +537,7 @@ krb5_cc_get_type(krb5_context context,
 /**
  * Return the complete resolvable name the cache
 
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param id return pointer to a found credential cache
  * @param str the returned name of a credential cache, free with krb5_xfree()
  *
@@ -620,8 +803,7 @@ krb5_cc_configured_default_name(krb5_context context)
     }
 
     /* Else try a configured default ccache type's default */
-    cfg = krb5_config_get_string(context, NULL, "libdefaults",
-                                 "default_cc_type", NULL);
+    cfg = get_default_cc_type(context, 1);
     if (cfg) {
         const krb5_cc_ops *ops;
 
@@ -687,18 +869,52 @@ krb5_cc_configured_default_name(krb5_context context)
  * @ingroup krb5_ccache
  */
 
-
 KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
 krb5_cc_default(krb5_context context,
                krb5_ccache *id)
 {
     const char *p = krb5_cc_default_name(context);
 
+    *id = NULL;
     if (p == NULL)
        return krb5_enomem(context);
     return krb5_cc_resolve(context, p, id);
 }
 
+/**
+ * Open the named subsidiary cache from the default ccache collection in `id'.
+ *
+ * @return Return an error code or 0, see krb5_get_error_message().
+ *
+ * @ingroup krb5_ccache
+ */
+
+KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
+krb5_cc_default_sub(krb5_context context,
+                    const char *subsidiary,
+                   krb5_ccache *id)
+{
+    return krb5_cc_resolve_sub(context, get_default_cc_type(context, 0), NULL,
+                               subsidiary, id);
+}
+
+/**
+ * Open the default ccache in `id' that corresponds to the given principal.
+ *
+ * @return Return an error code or 0, see krb5_get_error_message().
+ *
+ * @ingroup krb5_ccache
+ */
+
+KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL
+krb5_cc_default_for(krb5_context context,
+                    krb5_const_principal principal,
+                    krb5_ccache *id)
+{
+    return krb5_cc_resolve_for(context, get_default_cc_type(context, 0), NULL,
+                               principal, id);
+}
+
 /**
  * Create a new ccache in `id' for `primary_principal'.
  *
@@ -1424,7 +1640,7 @@ krb5_cc_cache_match (krb5_context context,
  * Move the content from one credential cache to another. The
  * operation is an atomic switch.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param from the credential cache to move the content from
  * @param to the credential cache to move the content to
 
@@ -1515,7 +1731,7 @@ build_conf_principals(krb5_context context, krb5_ccache id,
  * principal (generated part of krb5_cc_set_config()). Returns FALSE
  * (zero) if not a configuration principal.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param principal principal to check if it a configuration principal
  *
  * @ingroup krb5_ccache
@@ -1539,7 +1755,7 @@ krb5_is_config_principal(krb5_context context,
  * Store some configuration for the credential cache in the cache.
  * Existing configuration under the same name is over-written.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param id the credential cache to store the data for
  * @param principal configuration for a specific principal, if
  * NULL, global for the whole cache.
@@ -1586,7 +1802,7 @@ out:
 /**
  * Get some configuration for the credential cache in the cache.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param id the credential cache to store the data for
  * @param principal configuration for a specific principal, if
  * NULL, global for the whole cache.
@@ -1637,7 +1853,7 @@ struct krb5_cccol_cursor_data {
  * Get a new cache interation cursor that will interate over all
  * credentials caches independent of type.
  *
- * @param context a Keberos context
+ * @param context a Kerberos context
  * @param cursor passed into krb5_cccol_cursor_next() and free with krb5_cccol_cursor_free().
  *
  * @return Returns 0 or and error code, see krb5_get_error_message().
@@ -1711,7 +1927,7 @@ krb5_cccol_cursor_next(krb5_context context, krb5_cccol_cursor cursor,
        return KRB5_CC_END;
     }
 
-    return 0;
+    return ret;
 }
 
 /**
index 2a87f5b085d030936b645f2eed8443fab5301184..4d46070610b98e2b9329ee8251955c7ebc41f780 100644 (file)
 
 typedef struct krb5_dcache{
     krb5_ccache fcache;
-    char *dir;
     char *name;
+    char *dir;
+    char *sub;
+    unsigned int default_candidate:1;
 } krb5_dcache;
 
 #define DCACHE(X) ((krb5_dcache*)(X)->data.data)
@@ -46,7 +48,47 @@ typedef struct krb5_dcache{
 
 static krb5_error_code KRB5_CALLCONV dcc_close(krb5_context, krb5_ccache);
 static krb5_error_code KRB5_CALLCONV dcc_get_default_name(krb5_context, char **);
+static krb5_error_code KRB5_CALLCONV dcc_set_default(krb5_context, krb5_ccache);
 
+/*
+ * Make subsidiary filesystem safe by mapping / and : to -.  If the subsidiary
+ * is longer than 128 bytes, then truncate.
+ * In all cases, "tkt." is prefixed to be compatible with the DIR requirement
+ * that subsidiary ccache files be named tkt*.
+ *
+ * Thus host/foo.bar.baz@BAR.BAZ -> tkt.host-foo.bar.baz@BAR.BAZ.
+ *
+ * In particular, no filesystem component separators will be emitted, and . and
+ * .. will never be traversed.
+ */
+static krb5_error_code
+fs_encode_subsidiary(krb5_context context,
+                     krb5_dcache *dc,
+                     const char *subsidiary,
+                     char **res)
+{
+    size_t len = strlen(subsidiary);
+    size_t i;
+
+    *res = NULL;
+    if (asprintf(res, "tkt.%s", subsidiary) == -1 || *res == NULL)
+        return krb5_enomem(context);
+    for (i = sizeof("tkt.") - 1; i < len; i++) {
+        switch ((*res)[i]) {
+#ifdef WIN32
+        case '\\':  (*res)[0] = '-'; break;
+#endif
+        case '/':   (*res)[0] = '-'; break;
+        case ':':   (*res)[0] = '-'; break;
+        default:                     break;
+        }
+    }
+
+    /* Hopefully this will work on all filesystems */
+    if (len > 128 - sizeof("tkt.") - 1)
+        (*res)[127] = '\0';
+    return 0;
+}
 
 static char *
 primary_create(krb5_dcache *dc)
@@ -63,8 +105,14 @@ primary_create(krb5_dcache *dc)
 static int
 is_filename_cacheish(const char *name)
 {
-    return strncmp(name, "tkt", 3) == 0;
-       
+    size_t i;
+
+    if (strncmp(name, "tkt", sizeof("tkt") - 1))
+        return 0;
+    for (i = sizeof("tkt") - 1; name[i]; i++)
+        if (ISPATHSEP(name[i]))
+            return 0;
+    return 1;
 }
 
 static krb5_error_code
@@ -77,12 +125,6 @@ set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
     int fd = -1;
     int asprintf_ret;
 
-    if (!is_filename_cacheish(residual)) {
-       krb5_set_error_message(context, KRB5_CC_FORMAT,
-                              "name %s is not a cache (doesn't start with tkt)", residual);
-       return KRB5_CC_FORMAT;
-    }
-
     asprintf_ret = asprintf(&path, "%s/primary-XXXXXX", dc->dir);
     if (asprintf_ret == -1 || path == NULL) {
        return krb5_enomem(context);
@@ -141,14 +183,18 @@ set_default_cache(krb5_context context, krb5_dcache *dc, const char *residual)
 }
 
 static krb5_error_code
-get_default_cache(krb5_context context, krb5_dcache *dc, char **residual)
+get_default_cache(krb5_context context, krb5_dcache *dc,
+                  const char *subsidiary, char **residual)
 {
     krb5_error_code ret;
     char buf[MAXPATHLEN];
-    char *primary;
+    char *primary = NULL;
     FILE *f;
 
     *residual = NULL;
+    if (subsidiary)
+        return fs_encode_subsidiary(context, dc, subsidiary, residual);
+
     primary = primary_create(dc);
     if (primary == NULL)
        return krb5_enomem(context);
@@ -197,12 +243,22 @@ get_default_cache(krb5_context context, krb5_dcache *dc, char **residual)
 
 
 
-static const char* KRB5_CALLCONV
+static krb5_error_code KRB5_CALLCONV
 dcc_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **dir,
+             const char **sub)
 {
     krb5_dcache *dc = DCACHE(id);
-    return dc->name;
+
+    if (name)
+        *name = dc->name;
+    if (dir)
+        *dir = dc->dir;
+    if (sub)
+        *sub = dc->sub;
+    return 0;
 }
 
 
@@ -211,6 +267,12 @@ verify_directory(krb5_context context, const char *path)
 {
     struct stat sb;
 
+    if (!path[0]) {
+        krb5_set_error_message(context, EINVAL,
+                               N_("DIR empty directory component", ""));
+        return EINVAL;
+    }
+
     if (stat(path, &sb) != 0) {
        if (errno == ENOENT) {
            /* XXX should use mkdirx_np()  */
@@ -241,118 +303,176 @@ dcc_release(krb5_context context, krb5_dcache *dc)
 {
     if (dc->fcache)
        krb5_cc_close(context, dc->fcache);
-    if (dc->dir)
-       free(dc->dir);
-    if (dc->name)
-       free(dc->name);
+    free(dc->sub);
+    free(dc->dir);
+    free(dc->name);
     memset(dc, 0, sizeof(*dc));
     free(dc);
 }
 
-static krb5_error_code KRB5_CALLCONV
-dcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
+static krb5_error_code
+get_default_dir(krb5_context context, char **res)
 {
-    char *filename = NULL;
     krb5_error_code ret;
-    krb5_dcache *dc;
-    const char *p;
-    int asprintf_ret;
+    char *s;
 
-    p = res;
-    do {
-       p = strstr(p, "..");
-       if (p && (p == res || ISPATHSEP(p[-1])) && (ISPATHSEP(p[2]) || p[2] == '\0')) {
-           krb5_set_error_message(context, KRB5_CC_FORMAT,
-                                  N_("Path contains a .. component", ""));
-           return KRB5_CC_FORMAT;
-       }
-       if (p)
-           p += 3;
-    } while (p);
-
-    dc = calloc(1, sizeof(*dc));
-    if (dc == NULL) {
-       krb5_set_error_message(context, KRB5_CC_NOMEM,
-                              N_("malloc: out of memory", ""));
-       return KRB5_CC_NOMEM;
+    if ((ret = dcc_get_default_name(context, &s)))
+        return ret;
+    if (strncmp(s, "DIR:", sizeof("DIR:") - 1)) {
+        *res = s;
+        s = NULL;
+    } else if ((*res = strdup(s + sizeof("DIR:") - 1)) == NULL) {
+        ret = krb5_enomem(context);
     }
-    
-    /* check for explicit component */
-    if (res[0] == ':') {
-       char *q;
+    free(s);
+    return ret;
+}
 
-       dc->dir = strdup(&res[1]);
-#ifdef _WIN32
-       q = strrchr(dc->dir, '\\');
-       if (q == NULL)
+static krb5_error_code KRB5_CALLCONV
+dcc_resolve(krb5_context context,
+            krb5_ccache *id,
+            const char *res,
+            const char *sub)
+{
+    krb5_error_code ret;
+    krb5_dcache *dc = NULL;
+    char *filename = NULL;
+    size_t len;
+    int has_pathsep = 0;
+
+    if (sub) {
+        /*
+         * Here `res' has the directory name (or, if NULL, refers to the
+         * default DIR cccol), and `sub' has the "subsidiary" name, to which
+         * we'll prefix "tkt." (though we will insist only on "tkt" later).
+         */
+        if ((dc = calloc(1, sizeof(*dc))) == NULL ||
+            asprintf(&dc->sub, "tkt.%s", sub) == -1 || dc->sub == NULL) {
+            free(dc);
+            return krb5_enomem(context);
+        }
+        if (res && res[0] && (dc->dir = strdup(res)) == NULL) {
+            free(dc->sub);
+            free(dc);
+            return krb5_enomem(context);
+        } else if ((!res || !res[0]) && (ret = get_default_dir(context, &dc->dir))) {
+            free(dc->sub);
+            free(dc);
+            return ret;
+        }
+    } else {
+        const char *p;
+        int is_drive_letter_colon = 0;
+
+        /*
+         * Here `res' has whatever string followed "DIR:", and we need to parse
+         * it into `dc->dir' and `dc->sub'.
+         *
+         * Conventions we support for DIR cache naming:
+         *
+         *  - DIR:path:NAME     ---> FILE:path/tktNAME
+         *  - DIR::path/tktNAME ---> FILE:path/tktNAME
+         *  - DIR::NAME         ---> FILE:${default_DIR_cccol_path}/tktNAME
+         *                       \-> FILE:/tmp/krb5cc_${uid}_dir/tktNAME
+         *  - DIR:path          ---> FILE:path/$(cat primary) or FILE:path/tkt
+         *
+         */
+
+        if (*res == '\0' || (res[0] == ':' && res[1] == '\0')) {
+            /* XXX Why not? */
+            krb5_set_error_message(context, KRB5_CC_FORMAT,
+                                   N_("\"DIR:\" is not a valid ccache name", ""));
+            return KRB5_CC_FORMAT;
+        }
+
+#ifdef WIN32
+        has_pathsep = strchr(res, '\\') != NULL;
 #endif
-       q = strrchr(dc->dir, '/');
-       if (q) {
-           *q++ = '\0';
-       } else {
-           krb5_set_error_message(context, KRB5_CC_FORMAT, N_("Cache not an absolute path: %s", ""), dc->dir);
-           dcc_release(context, dc);
-           return KRB5_CC_FORMAT;
-       }
-
-       if (!is_filename_cacheish(q)) {
-           krb5_set_error_message(context, KRB5_CC_FORMAT,
-                                  N_("Name %s is not a cache (doesn't start with tkt)", ""), q);
-           dcc_release(context, dc);
-           return KRB5_CC_FORMAT;
-       }
-       
-       ret = verify_directory(context, dc->dir);
-       if (ret) {
-           dcc_release(context, dc);
-           return ret;
-       }
+        has_pathsep |= strchr(res, '/') != NULL;
 
-       dc->name = strdup(res);
-       if (dc->name == NULL) {
-           dcc_release(context, dc);
-           return krb5_enomem(context);
-       }
-
-    } else {
-       char *residual;
-       size_t len;
+        if ((dc = calloc(1, sizeof(*dc))) == NULL)
+            return krb5_enomem(context);
 
-       dc->dir = strdup(res);
-       if (dc->dir == NULL) {
-           dcc_release(context, dc);
-           return krb5_enomem(context);
-       }
+        p = strrchr(res, ':');
+#ifdef WIN32
+        is_drive_letter_colon =
+            p && ((res[0] == ':' && res[1] != ':' && p - res == 2) ||
+                  (res[0] != ':' && p - res == 1));
+#endif
 
-       len = strlen(dc->dir);
+        if (res[0] != ':' && p && !is_drive_letter_colon) {
+            /* DIR:path:NAME */
+            if ((dc->dir = strndup(res, (p - res))) == NULL ||
+                asprintf(&dc->sub, "tkt.%s", p + 1) < 0 || dc->sub == NULL) {
+                dcc_release(context, dc);
+                return krb5_enomem(context);
+            }
+        } else if (res[0] == ':' && has_pathsep) {
+            char *q;
+
+            /* DIR::path/tktNAME (the "tkt" must be there; we'll check) */
+            if ((dc->dir = strdup(&res[1])) == NULL) {
+                dcc_release(context, dc);
+                return krb5_enomem(context);
+            }
+#ifdef _WIN32
+            q = strrchr(dc->dir, '\\');
+            if (q == NULL || ((p = strrchr(dc->dir, '/')) && q < p))
+#endif
+                q = strrchr(dc->dir, '/');
+            *q++ = '\0';
+            if ((dc->sub = strdup(q)) == NULL) {
+                dcc_release(context, dc);
+                return krb5_enomem(context);
+            }
+        } else if (res[0] == ':') {
+            /* DIR::NAME -- no path component separators in NAME */
+            if ((ret = get_default_dir(context, &dc->dir))) {
+                dcc_release(context, dc);
+                return ret;
+            }
+            if (asprintf(&dc->sub, "tkt.%s", res + 1) < 0 || dc->sub == NULL) {
+                dcc_release(context, dc);
+                return krb5_enomem(context);
+            }
+        } else {
+            /* DIR:path */
+            if ((dc->dir = strdup(res)) == NULL) {
+                dcc_release(context, dc);
+                return krb5_enomem(context);
+            }
+
+            if ((ret = get_default_cache(context, dc, NULL, &dc->sub))) {
+                dcc_release(context, dc);
+                return ret;
+            }
+        }
+    }
 
-       if (ISPATHSEP(dc->dir[len - 1]))
-           dc->dir[len - 1] = '\0';
+    /* Strip off extra slashes on the end */
+    for (len = strlen(dc->dir);
+         len && ISPATHSEP(dc->dir[len - 1]);
+         len -= len ? 1 : 0)
+        dc->dir[len - 1] = '\0';
 
-       ret = verify_directory(context, dc->dir);
-       if (ret) {
-           dcc_release(context, dc);
-           return ret;
-       }
+    /* If we got here then `dc->dir' and `dc->sub' must both be set */
 
-       ret = get_default_cache(context, dc, &residual);
-       if (ret) {
-           dcc_release(context, dc);
-           return ret;
-       }
-       asprintf_ret = asprintf(&dc->name, ":%s/%s", dc->dir, residual);
-       free(residual);
-       if (asprintf_ret == -1 || dc->name == NULL) {
-           dc->name = NULL;
-           dcc_release(context, dc);
-           return krb5_enomem(context);
-       }
+    if ((ret = verify_directory(context, dc->dir))) {
+        dcc_release(context, dc);
+        return ret;
     }
-
-    asprintf_ret = asprintf(&filename, "FILE%s", dc->name);
-    if (asprintf_ret == -1 || filename == NULL) {
-       dcc_release(context, dc);
-       return krb5_enomem(context);
+    if (!is_filename_cacheish(dc->sub)) {
+        krb5_set_error_message(context, KRB5_CC_FORMAT,
+                               N_("Name %s is not a cache "
+                                  "(doesn't start with tkt)", ""), dc->sub);
+        dcc_release(context, dc);
+        return KRB5_CC_FORMAT;
+    }
+    if (asprintf(&dc->name, ":%s/%s", dc->dir, dc->sub) == -1 ||
+        dc->name == NULL ||
+        asprintf(&filename, "FILE%s", dc->name) == -1 || filename == NULL) {
+        dcc_release(context, dc);
+        return krb5_enomem(context);
     }
 
     ret = krb5_cc_resolve(context, filename, &dc->fcache);
@@ -362,86 +482,36 @@ dcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
        return ret;
     }
 
-
+    dc->default_candidate = 1;
     (*id)->data.data = dc;
     (*id)->data.length = sizeof(*dc);
     return 0;
 }
 
-static char *
-copy_default_dcc_cache(krb5_context context)
-{
-    const char *defname;
-    krb5_error_code ret;
-    char *name = NULL;
-    size_t len;
-
-    len = strlen(krb5_dcc_ops.prefix);
-
-    defname = krb5_cc_default_name(context);
-    if (defname == NULL ||
-       strncmp(defname, krb5_dcc_ops.prefix, len) != 0 ||
-       defname[len] != ':')
-    {
-       ret = dcc_get_default_name(context, &name);
-       if (ret)
-           return NULL;
-
-       return name;
-    } else {
-       return strdup(&defname[len + 1]);
-    }
-}
-
-
 static krb5_error_code KRB5_CALLCONV
 dcc_gen_new(krb5_context context, krb5_ccache *id)
 {
     krb5_error_code ret;
+    char *def_dir = NULL;
     char *name = NULL;
-    krb5_dcache *dc;
-    int fd;
-    size_t len;
-    int asprintf_ret;
-
-    name = copy_default_dcc_cache(context);
-    if (name == NULL) {
-       krb5_set_error_message(context, KRB5_CC_FORMAT,
-                              N_("Can't generate DIR caches unless its the default type", ""));
-       return KRB5_CC_FORMAT;
-    }
+    int fd = -1;
 
-    len = strlen(krb5_dcc_ops.prefix);
-    if (strncmp(name, krb5_dcc_ops.prefix, len) == 0 && name[len] == ':')
-       ++len;
-    else
-       len = 0;
+    ret = get_default_dir(context, &def_dir);
+    if (ret == 0)
+        ret = verify_directory(context, def_dir);
+    if (ret == 0 &&
+        (asprintf(&name, "DIR::%s/tktXXXXXX", def_dir) == -1 || name == NULL))
+       ret = krb5_enomem(context);
+    if (ret == 0 && (fd = mkstemp(name + sizeof("DIR::") - 1)) == -1)
+       ret = errno;
+    if (ret == 0)
+        ret = dcc_resolve(context, id, name + sizeof("DIR:") - 1, NULL);
 
-    ret = dcc_resolve(context, id, name + len);
+    free(def_dir);
     free(name);
-    name = NULL;
-    if (ret)
-       return ret;
-
-    dc = DCACHE((*id));
-
-    asprintf_ret = asprintf(&name, ":%s/tktXXXXXX", dc->dir);
-    if (asprintf_ret == -1 || name == NULL) {
-       dcc_close(context, *id);
-       return krb5_enomem(context);
-    }
-
-    fd = mkstemp(&name[1]);
-    if (fd < 0) {
-       dcc_close(context, *id);
-       return krb5_enomem(context);
-    }
-    close(fd);
-
-    free(dc->name);
-    dc->name = name;
-
-    return 0;
+    if (fd != -1)
+        close(fd);
+    return ret;
 }
 
 static krb5_error_code KRB5_CALLCONV
@@ -457,6 +527,25 @@ static krb5_error_code KRB5_CALLCONV
 dcc_close(krb5_context context,
          krb5_ccache id)
 {
+    krb5_dcache *dc = DCACHE(id);
+    krb5_principal p = NULL;
+    struct stat st;
+    char *primary = NULL;
+
+    /*
+     * If there's no default cache, but we're closing one, and the one we're
+     * closing has been initialized, then make it the default.  This makes the
+     * first cache created the default.
+     *
+     * FIXME We should check if `D2FCACHE(dc)' has live credentials.
+     */
+    if (dc->default_candidate && D2FCACHE(dc) &&
+        krb5_cc_get_principal(context, D2FCACHE(dc), &p) == 0 &&
+        (primary = primary_create(dc)) &&
+        (stat(primary, &st) == -1 || !S_ISREG(st.st_mode) || st.st_size == 0))
+        dcc_set_default(context, id);
+    krb5_free_principal(context, p);
+    free(primary);
     dcc_release(context, DCACHE(id));
     return 0;
 }
@@ -545,39 +634,61 @@ dcc_get_version(krb5_context context,
 }
 
 struct dcache_iter {
-    int first;
+    char *primary;
     krb5_dcache *dc;
+    DIR *d;
+    unsigned int first:1;
 };
 
 static krb5_error_code KRB5_CALLCONV
 dcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
 {
-    struct dcache_iter *iter;
-    krb5_error_code ret;
-    char *name;
+    struct dcache_iter *iter = NULL;
+    const char *name = krb5_cc_default_name(context);
+    size_t len;
+    char *p;
 
     *cursor = NULL;
-    iter = calloc(1, sizeof(*iter));
-    if (iter == NULL)
-       return krb5_enomem(context);
-    iter->first = 1;
 
-    name = copy_default_dcc_cache(context);
-    if (name == NULL) {
-        free(iter);
+    if (strncmp(name, "DIR:", sizeof("DIR:") - 1) != 0) {
        krb5_set_error_message(context, KRB5_CC_FORMAT,
-                              N_("Can't generate DIR caches unless its the default type", ""));
+                              N_("Can't list DIR caches unless its the default type", ""));
        return KRB5_CC_FORMAT;
     }
 
-    ret = dcc_resolve(context, NULL, name);
-    free(name);
-    if (ret) {
+    if ((iter = calloc(1, sizeof(*iter))) == NULL ||
+        (iter->dc = calloc(1, sizeof(iter->dc[0]))) == NULL ||
+        (iter->dc->dir = strdup(name + sizeof("DIR:") - 1)) == NULL) {
+        if (iter)
+            free(iter->dc);
         free(iter);
-        return ret;
+       return krb5_enomem(context);
+    }
+    iter->first = 1;
+    p = strrchr(iter->dc->dir, ':');
+#ifdef WIN32
+    if (p == iter->dc->dir + 1)
+        p = NULL;
+#endif
+    if (p)
+        *p = '\0';
+
+    /* Strip off extra slashes on the end */
+    for (len = strlen(iter->dc->dir);
+         len && ISPATHSEP(iter->dc->dir[len - 1]);
+         len -= len ? 1 : 0) {
+        iter->dc->dir[len - 1] = '\0';
     }
 
-    /* XXX We need to opendir() here */
+    if ((iter->d = opendir(iter->dc->dir)) == NULL) {
+        free(iter->dc->dir);
+        free(iter->dc);
+        free(iter);
+       krb5_set_error_message(context, KRB5_CC_FORMAT,
+                               N_("Can't open DIR %s: %s", ""),
+                               iter->dc->dir, strerror(errno));
+       return KRB5_CC_FORMAT;
+    }
 
     *cursor = iter;
     return 0;
@@ -587,18 +698,49 @@ static krb5_error_code KRB5_CALLCONV
 dcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id)
 {
     struct dcache_iter *iter = cursor;
+    krb5_error_code ret;
+    struct stat st;
+    struct dirent *dentry;
+    char *p = NULL;
 
+    *id = NULL;
     if (iter == NULL)
         return krb5_einval(context, 2);
 
-    if (!iter->first) {
-       krb5_clear_error_message(context);
-       return KRB5_CC_END;
+    /* Emit primary subsidiary first */
+    if (iter->first &&
+        (ret = get_default_cache(context, iter->dc, NULL, &iter->primary)) == 0 &&
+        is_filename_cacheish(iter->primary)) {
+        iter->first = 0;
+        ret = KRB5_CC_END;
+        if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, iter->primary) > -1 && p != NULL &&
+            stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
+            ret = krb5_cc_resolve(context, p, id);
+        if (p == NULL)
+            return krb5_enomem(context);
+        free(p);
+        if (ret == 0)
+            return ret;
+        p = NULL;
     }
 
-    /* XXX We need to readdir() here */
     iter->first = 0;
-
+    for (dentry = readdir(iter->d); dentry; dentry = readdir(iter->d)) {
+        if (!is_filename_cacheish(dentry->d_name) ||
+            (iter->primary && strcmp(dentry->d_name, iter->primary) == 0))
+            continue;
+        p = NULL;
+        ret = KRB5_CC_END;
+        if (asprintf(&p, "FILE:%s/%s", iter->dc->dir, dentry->d_name) > -1 &&
+            p != NULL &&
+            stat(p + sizeof("FILE:") - 1, &st) == 0 && S_ISREG(st.st_mode))
+            ret = krb5_cc_resolve(context, p, id);
+        free(p);
+        if (p == NULL)
+            return krb5_enomem(context);
+        if (ret == 0)
+            return ret;
+    }
     return KRB5_CC_END;
 }
 
@@ -610,9 +752,10 @@ dcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
     if (iter == NULL)
         return krb5_einval(context, 2);
 
-    /* XXX We need to closedir() here */
-    if (iter->dc)
-       dcc_release(context, iter->dc);
+    (void) closedir(iter->d);
+    free(iter->dc->dir);
+    free(iter->dc);
+    free(iter->primary);
     free(iter);
     return 0;
 }
@@ -622,14 +765,22 @@ dcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
 {
     krb5_dcache *dcfrom = DCACHE(from);
     krb5_dcache *dcto = DCACHE(to);
+
+    dcfrom->default_candidate = 0;
+    dcto->default_candidate = 1;
     return krb5_cc_move(context, D2FCACHE(dcfrom), D2FCACHE(dcto));
 }
 
 static krb5_error_code KRB5_CALLCONV
 dcc_get_default_name(krb5_context context, char **str)
 {
-    return _krb5_expand_default_cc_name(context,
-                                       KRB5_DEFAULT_CCNAME_DIR,
+    const char *def_cc_colname =
+        krb5_config_get_string_default(context, NULL, KRB5_DEFAULT_CCNAME_DIR,
+                                       "libdefaults", "default_cc_collection",
+                                       NULL);
+
+    /* What if def_cc_colname does not start with DIR:?  We tolerate it. */
+    return _krb5_expand_default_cc_name(context, def_cc_colname,
                                        str);
 }
 
@@ -637,13 +788,10 @@ static krb5_error_code KRB5_CALLCONV
 dcc_set_default(krb5_context context, krb5_ccache id)
 {
     krb5_dcache *dc = DCACHE(id);
-    const char *name;
 
-    name = krb5_cc_get_name(context, D2FCACHE(dc));
-    if (name == NULL)
+    if (dc->sub == NULL)
        return ENOENT;
-
-    return set_default_cache(context, dc, name);
+    return set_default_cache(context, dc, dc->sub);
 }
 
 static krb5_error_code KRB5_CALLCONV
index bb34ba72d3f073a3e380ddf75198c31164f1a8b9..f2ed1e2a49954366ea41372fdfc2d85e2941a6f2 100644 (file)
@@ -62,14 +62,23 @@ struct fcc_cursor {
 
 #define FCC_CURSOR(C) ((struct fcc_cursor*)(C))
 
-static const char* KRB5_CALLCONV
+static krb5_error_code KRB5_CALLCONV
 fcc_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **colname,
+             const char **sub)
 {
     if (FCACHE(id) == NULL)
-        return NULL;
-
-    return FILENAME(id);
+        return KRB5_CC_NOTFOUND;
+
+    if (name)
+        *name = FILENAME(id);
+    if (colname)
+        *colname = FILENAME(id);
+    if (sub)
+        *sub = NULL;
+    return 0;
 }
 
 KRB5_LIB_FUNCTION int KRB5_LIB_CALL
@@ -178,15 +187,32 @@ static krb5_error_code KRB5_CALLCONV
 fcc_lock(krb5_context context, krb5_ccache id,
         int fd, krb5_boolean exclusive)
 {
+    krb5_error_code ret;
+    const char *name;
+
     if (exclusive == FALSE)
         return 0;
-    return _krb5_xlock(context, fd, exclusive, fcc_get_name(context, id));
+    ret = fcc_get_name(context, id, &name, NULL, NULL);
+    if (ret == 0)
+        ret = _krb5_xlock(context, fd, exclusive, name);
+    return ret;
 }
 
 static krb5_error_code KRB5_CALLCONV
-fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
+fcc_resolve(krb5_context context,
+            krb5_ccache *id,
+            const char *res,
+            const char *sub)
 {
     krb5_fcache *f;
+
+    if (sub && *sub) {
+        krb5_set_error_message(context, KRB5_CC_NOSUPP,
+                               N_("FILE ccache type is not a collection "
+                                  "type", ""));
+        return KRB5_CC_NOSUPP;
+    }
+
     f = calloc(1, sizeof(*f));
     if(f == NULL) {
        krb5_set_error_message(context, KRB5_CC_NOMEM,
@@ -204,6 +230,7 @@ fcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
     f->version = 0;
     (*id)->data.data = f;
     (*id)->data.length = sizeof(*f);
+
     return 0;
 }
 
@@ -647,11 +674,8 @@ fcc_destroy(krb5_context context,
     if (FCACHE(id) == NULL)
         return krb5_einval(context, 2);
 
-    if (TMPFILENAME(id)) {
+    if (TMPFILENAME(id))
         (void) _krb5_erase_file(context, TMPFILENAME(id));
-        free(TMPFILENAME(id));
-        TMPFILENAME(id) = NULL;
-    }
     return _krb5_erase_file(context, FILENAME(id));
 }
 
index 6f48ff4ca262c780994ba1e4d0a874bbe6bf9db0..e7548a57788bba85ad85f8ba04ee0914bb188571 100644 (file)
@@ -136,7 +136,9 @@ krb5_kcm_storage_request(krb5_context context,
 }
 
 static krb5_error_code
-kcm_alloc(krb5_context context, const char *name, krb5_ccache *id)
+kcm_alloc(krb5_context context,
+          const char *name,
+          krb5_ccache *id)
 {
     krb5_kcmcache *k;
 
@@ -229,17 +231,42 @@ kcm_free(krb5_context context, krb5_ccache *id)
     }
 }
 
-static const char *
+static krb5_error_code KRB5_CALLCONV
 kcm_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **col,
+             const char **sub)
 {
-    return CACHENAME(id);
+    /*
+     * TODO:
+     *
+     *  - name should be <IPC-name>:<cache-name>
+     *  - col  should be <IPC-name>
+     *  - sub  should be <cache-name>
+     */
+    if (name)
+        *name = CACHENAME(id);
+    if (col)
+        *col = NULL;
+    if (sub)
+        *sub = CACHENAME(id);
+    return 0;
 }
 
 static krb5_error_code
-kcm_resolve(krb5_context context, krb5_ccache *id, const char *res)
+kcm_resolve(krb5_context context,
+            krb5_ccache *id,
+            const char *res,
+            const char *sub)
 {
-    return kcm_alloc(context, res, id);
+    /*
+     * For now, for KCM the `res' is the `sub'.
+     *
+     * TODO: We should use `res' as the IPC name instead of the one currently
+     *       hard-coded in `kcm_ipc_name'.
+     */
+    return kcm_alloc(context, sub && *sub ? sub : res, id);
 }
 
 /*
index 44553386a230c68ad830523b9e5ea64529bcf835..53257fa3774d4fad676d1d1a5eff4e4cfc4270b2 100644 (file)
@@ -491,13 +491,16 @@ typedef struct krb5_creds {
 
 typedef struct krb5_cc_cache_cursor_data *krb5_cc_cache_cursor;
 
-#define KRB5_CC_OPS_VERSION 3
+#define KRB5_CC_OPS_VERSION 4
 
 typedef struct krb5_cc_ops {
     int version;
     const char *prefix;
-    const char* (KRB5_CALLCONV * get_name)(krb5_context, krb5_ccache);
-    krb5_error_code (KRB5_CALLCONV * resolve)(krb5_context, krb5_ccache *, const char *);
+    krb5_error_code (KRB5_CALLCONV * get_name)(krb5_context, krb5_ccache,
+                                               const char **, const char **,
+                                               const char **);
+    krb5_error_code (KRB5_CALLCONV * resolve)(krb5_context, krb5_ccache *, const char *,
+                                              const char *);
     krb5_error_code (KRB5_CALLCONV * gen_new)(krb5_context, krb5_ccache *);
     krb5_error_code (KRB5_CALLCONV * init)(krb5_context, krb5_ccache, krb5_principal);
     krb5_error_code (KRB5_CALLCONV * destroy)(krb5_context, krb5_ccache);
index 1d425ca3ed5fe75b7f065f20582b26c83ea1b9ad..56be3567b3fcd3e2b8cbd59e14303968e1b42f52 100644 (file)
@@ -207,6 +207,8 @@ typedef union _krb5_krcache_and_princ_id {
  */
 typedef struct _krb5_krcache {
     char *krc_name;                    /* Name for this credentials cache */
+    char *krc_collection;
+    char *krc_subsidiary;
     krb5_timestamp krc_changetime;     /* update time, does not decrease (mutable) */
     krb5_krcache_and_princ_id krc_id;  /* cache and principal IDs (mutable) */
     #define krc_cache_and_principal_id krc_id.krcu_cache_and_princ_id
@@ -384,8 +386,6 @@ parse_residual(krb5_context context,
        collection_name = strdup(residual);
        if (collection_name == NULL)
            goto nomem;
-
-       subsidiary_name = NULL;
     } else {
        collection_name = strndup(residual, sep - residual);
        if (collection_name == NULL)
@@ -918,14 +918,18 @@ initialize_internal(krb5_context context,
 static krb5_error_code KRB5_CALLCONV
 krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
 {
+    krb5_krcache *data = KRCACHE(id);
     krb5_error_code ret;
 
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
     if (princ == NULL)
        return KRB5_CC_BADNAME;
 
     ret = initialize_internal(context, id, princ);
     if (ret == 0)
-       update_change_time(context, 0, KRCACHE(id));
+       update_change_time(context, 0, data);
 
     return ret;
 }
@@ -939,6 +943,8 @@ krcc_close(krb5_context context, krb5_ccache id)
     if (data == NULL)
        return krb5_einval(context, 2);
 
+    free(data->krc_subsidiary);
+    free(data->krc_collection);
     free(data->krc_name);
     krb5_data_free(&id->data);
 
@@ -1048,16 +1054,26 @@ make_cache(krb5_context context,
 
 /* Create a keyring ccache handle for the given residual string. */
 static krb5_error_code KRB5_CALLCONV
-krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
+krcc_resolve(krb5_context context,
+             krb5_ccache *id,
+             const char *residual,
+             const char *sub)
 {
     krb5_error_code ret;
     key_serial_t collection_id, cache_id;
     char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
 
     ret = parse_residual(context, residual, &anchor_name, &collection_name,
-                        &subsidiary_name);
+                         &subsidiary_name);
     if (ret)
        goto cleanup;
+    if (sub) {
+        free(subsidiary_name);
+        if ((subsidiary_name = strdup(sub)) == NULL) {
+            ret = krb5_enomem(context);
+            goto cleanup;
+        }
+    }
 
     ret = get_collection(context, anchor_name, collection_name, &collection_id);
     if (ret)
@@ -1243,8 +1259,16 @@ alloc_cache(krb5_context context,
 
     ret = make_subsidiary_residual(context, anchor_name, collection_name,
                                   subsidiary_name, &data->krc_name);
-    if (ret) {
+    if (ret ||
+        (data->krc_collection = strdup(collection_name)) == NULL ||
+        (data->krc_subsidiary = strdup(subsidiary_name)) == NULL) {
+        if (data) {
+            free(data->krc_collection);
+            free(data->krc_name);
+        }
        free(data);
+        if (ret == 0)
+            ret = krb5_enomem(context);
        return ret;
     }
 
@@ -1321,10 +1345,25 @@ cleanup:
 }
 
 /* Return an alias to the residual string of the cache. */
-static const char *KRB5_CALLCONV
-krcc_get_name(krb5_context context, krb5_ccache id)
+static krb5_error_code KRB5_CALLCONV
+krcc_get_name(krb5_context context,
+              krb5_ccache id,
+              const char **name,
+              const char **collection_name,
+              const char **subsidiary_name)
 {
-    return KRCACHE(id)->krc_name;
+    krb5_krcache *data = KRCACHE(id);
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    if (name)
+        *name = data->krc_name;
+    if (collection_name)
+        *collection_name = data->krc_collection;
+    if (subsidiary_name)
+        *subsidiary_name = data->krc_subsidiary;
+    return 0;
 }
 
 /* Retrieve a copy of the default principal, if the cache is initialized. */
@@ -1641,6 +1680,9 @@ krcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat offset)
     key_serial_t cache_id;
     krb5_error_code ret;
 
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
     heim_base_exchange_32(&cache_id, data->krc_cache_id);
  
     ret = save_time_offsets(context, cache_id, (int32_t)offset, 0);
index 561ca7ef82d3c494dbab062cb8853a08d0c166e9..341177550e487596e2d0a6c38ffe5b98f55f0b69 100644 (file)
@@ -87,6 +87,8 @@ EXPORTS
        krb5_cc_copy_creds      ;!
        krb5_cc_copy_match_f
        krb5_cc_default
+       krb5_cc_default_sub
+       krb5_cc_default_for
        krb5_cc_default_name
        krb5_cc_destroy
        krb5_cc_end_seq_get
@@ -111,6 +113,8 @@ EXPORTS
        krb5_cc_register
        krb5_cc_remove_cred
        krb5_cc_resolve
+       krb5_cc_resolve_sub
+       krb5_cc_resolve_for
        krb5_cc_retrieve_cred
        krb5_cc_set_config
        krb5_cc_set_default_name
index a8a638b169c1243e63c2bee74881c3a47445244e..1ce1df343f41c480170830c03c5510b4a36645b0 100644 (file)
@@ -38,7 +38,8 @@
 typedef struct krb5_mcache {
     char *name;
     unsigned int refcnt;
-    int dead;
+    unsigned int anonymous:1;
+    unsigned int dead:1;
     krb5_principal primary_principal;
     struct link {
        krb5_creds cred;
@@ -57,42 +58,89 @@ static struct krb5_mcache *mcc_head;
 
 #define MISDEAD(X)     ((X)->dead)
 
-static const char* KRB5_CALLCONV
+static krb5_error_code KRB5_CALLCONV
 mcc_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **col,
+             const char **sub)
 {
-    return MCACHE(id)->name;
+    if (name)
+        *name = MCACHE(id)->name;
+    if (col)
+        *col = NULL;
+    if (sub)
+        *sub = MCACHE(id)->name;
+    return 0;
 }
 
-static krb5_mcache * KRB5_CALLCONV
-mcc_alloc(const char *name)
+static krb5_error_code
+mcc_alloc(krb5_context context, const char *name, krb5_mcache **out)
 {
     krb5_mcache *m, *m_c;
+    size_t counter = 0;
     int ret = 0;
 
+    *out = NULL;
     ALLOC(m, 1);
     if(m == NULL)
-       return NULL;
+       return krb5_enomem(context);
+
+again:
+    if (counter > 3) {
+        free(m->name);
+        free(m);
+        return EAGAIN; /* XXX */
+    }
     if(name == NULL)
-       ret = asprintf(&m->name, "%p", m);
+       ret = asprintf(&m->name, "u%p-%llu", m, (unsigned long long)counter);
     else
        m->name = strdup(name);
     if(ret < 0 || m->name == NULL) {
        free(m);
-       return NULL;
+       return krb5_enomem(context);
+    }
+    if (strcmp(m->name, "anonymous") == 0) {
+        m->anonymous = 1;
+        m->dead = 0;
+        m->refcnt = 1;
+        m->primary_principal = NULL;
+        m->creds = NULL;
+        m->mtime = time(NULL);
+        m->kdc_offset = 0;
+        m->next = NULL;
+        *out = m;
+        return 0;
     }
+
     /* check for dups first */
     HEIMDAL_MUTEX_lock(&mcc_mutex);
     for (m_c = mcc_head; m_c != NULL; m_c = m_c->next)
-       if (strcmp(m->name, m_c->name) == 0)
-           break;
+        if (strcmp(m->name, m_c->name) == 0)
+            break;
     if (m_c) {
-       free(m->name);
-       free(m);
-       HEIMDAL_MUTEX_unlock(&mcc_mutex);
-       return NULL;
+        free(m->name);
+        free(m);
+        if (name) {
+            /* We raced with another thread to create this cache */
+            m = m_c;
+            HEIMDAL_MUTEX_lock(&(m->mutex));
+            m->refcnt++;
+            HEIMDAL_MUTEX_unlock(&(m->mutex));
+        } else {
+            /* How likely are we to conflict on new_unique anyways?? */
+            counter++;
+            free(m->name);
+            m->name = NULL;
+            HEIMDAL_MUTEX_unlock(&mcc_mutex);
+            goto again;
+        }
+        HEIMDAL_MUTEX_unlock(&mcc_mutex);
+        *out = m;
+        return 0;
     }
 
+    m->anonymous = 0;
     m->dead = 0;
     m->refcnt = 1;
     m->primary_principal = NULL;
@@ -103,35 +151,21 @@ mcc_alloc(const char *name)
     HEIMDAL_MUTEX_init(&(m->mutex));
     mcc_head = m;
     HEIMDAL_MUTEX_unlock(&mcc_mutex);
-    return m;
+    *out = m;
+    return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
-mcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
+mcc_resolve(krb5_context context,
+            krb5_ccache *id,
+            const char *res,
+            const char *sub)
 {
+    krb5_error_code ret;
     krb5_mcache *m;
 
-    HEIMDAL_MUTEX_lock(&mcc_mutex);
-    for (m = mcc_head; m != NULL; m = m->next)
-       if (strcmp(m->name, res) == 0)
-           break;
-    HEIMDAL_MUTEX_unlock(&mcc_mutex);
-
-    if (m != NULL) {
-       HEIMDAL_MUTEX_lock(&(m->mutex));
-       m->refcnt++;
-       HEIMDAL_MUTEX_unlock(&(m->mutex));
-       (*id)->data.data = m;
-       (*id)->data.length = sizeof(*m);
-       return 0;
-    }
-
-    m = mcc_alloc(res);
-    if (m == NULL) {
-       krb5_set_error_message(context, KRB5_CC_NOMEM,
-                              N_("malloc: out of memory", ""));
-       return KRB5_CC_NOMEM;
-    }
+    if ((ret = mcc_alloc(context, sub && *sub ? sub : res, &m)))
+        return ret;
 
     (*id)->data.data = m;
     (*id)->data.length = sizeof(*m);
@@ -143,15 +177,11 @@ mcc_resolve(krb5_context context, krb5_ccache *id, const char *res)
 static krb5_error_code KRB5_CALLCONV
 mcc_gen_new(krb5_context context, krb5_ccache *id)
 {
+    krb5_error_code ret;
     krb5_mcache *m;
 
-    m = mcc_alloc(NULL);
-
-    if (m == NULL) {
-       krb5_set_error_message(context, KRB5_CC_NOMEM,
-                              N_("malloc: out of memory", ""));
-       return KRB5_CC_NOMEM;
-    }
+    if ((ret = mcc_alloc(context, NULL, &m)))
+        return ret;
 
     (*id)->data.data = m;
     (*id)->data.length = sizeof(*m);
@@ -221,7 +251,7 @@ mcc_close_internal(krb5_mcache *m)
        return 0;
     }
     if (MISDEAD(m)) {
-       free (m->name);
+       free(m->name);
        HEIMDAL_MUTEX_unlock(&(m->mutex));
        return 1;
     }
@@ -248,6 +278,18 @@ mcc_destroy(krb5_context context,
 {
     krb5_mcache **n, *m = MCACHE(id);
 
+    if (m->anonymous) {
+        HEIMDAL_MUTEX_lock(&(m->mutex));
+        if (m->refcnt == 0) {
+            HEIMDAL_MUTEX_unlock(&(m->mutex));
+            krb5_abortx(context, "mcc_destroy: refcnt already 0");
+        }
+        if (!MISDEAD(m))
+            mcc_destroy_internal(context, m);
+        HEIMDAL_MUTEX_unlock(&(m->mutex));
+        return 0;
+    }
+
     HEIMDAL_MUTEX_lock(&mcc_mutex);
     HEIMDAL_MUTEX_lock(&(m->mutex));
     if (m->refcnt == 0)
@@ -290,12 +332,8 @@ mcc_store_cred(krb5_context context,
     }
 
     l = malloc (sizeof(*l));
-    if (l == NULL) {
-       krb5_set_error_message(context, KRB5_CC_NOMEM,
-                       N_("malloc: out of memory", ""));
-       HEIMDAL_MUTEX_unlock(&(m->mutex));
-       return KRB5_CC_NOMEM;
-    }
+    if (l == NULL)
+        return krb5_enomem(context);
     l->next = m->creds;
     m->creds = l;
     memset (&l->cred, 0, sizeof(l->cred));
index d3b9764c07900edbe73df41033a76e0b8c2cbdc9..d260cac270fbccc2e500fc0cd540ef8d5e9fea8d 100644 (file)
@@ -40,6 +40,7 @@
 typedef struct krb5_scache {
     char *name;
     char *file;
+    char *sub;
     sqlite3 *db;
 
     sqlite_uint64 cid;
@@ -66,7 +67,7 @@ typedef struct krb5_scache {
 #else
 #define KRB5_SCACHE_DB "/tmp/krb5scc_%{uid}"
 #endif
-#define KRB5_SCACHE_NAME       "SCC:"  SCACHE_DEF_NAME ":" KRB5_SCACHE_DB
+#define KRB5_SCACHE_NAME       "SCC:"   KRB5_SCACHE_DB ":" SCACHE_DEF_NAME
 
 #define SCACHE_INVALID_CID     ((sqlite_uint64)-1)
 
@@ -103,7 +104,8 @@ typedef struct krb5_scache {
 #define SQL_UCACHE_PRINCIPAL "UPDATE caches SET principal=? WHERE OID=?"
 #define SQL_DCACHE "DELETE FROM caches WHERE OID=?"
 #define SQL_SCACHE "SELECT principal,name FROM caches WHERE OID=?"
-#define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=?"
+#define SQL_SCACHE_NAME "SELECT oid FROM caches WHERE NAME=? OR "   \
+                        "(PRINCIPAL IS NOT NULL AND PRINCIPAL=?)"
 
 #define SQL_CCREDS ""                          \
        "CREATE TABLE credentials ("            \
@@ -153,8 +155,12 @@ free_krb5(void *str)
 static void
 scc_free(krb5_scache *s)
 {
+    if (!s)
+        return;
     if (s->file)
        free(s->file);
+    if (s->sub)
+       free(s->sub);
     if (s->name)
        free(s->name);
 
@@ -225,22 +231,53 @@ exec_stmt(krb5_context context, sqlite3 *db, const char *str,
 }
 
 static krb5_error_code
-default_db(krb5_context context, sqlite3 **db)
+default_db(krb5_context context, const char *name, sqlite3 **db, char **file)
 {
-    char *name;
+    char *s = NULL;
+    char *f = NULL;
     int ret;
 
-    ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &name);
-    if (ret)
-       return ret;
+    if (file)
+        *file = NULL;
+
+    if (name == NULL) {
+        if ((name = krb5_cc_default_name(context))) {
+            if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
+                name += sizeof("SCC:") - 1;
+        }
+        if (name == NULL) {
+            ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s);
+            if (ret)
+                return ret;
+            name = s;
+        }
+    }
 
-    ret = sqlite3_open_v2(name, db, SQLITE_OPEN_READWRITE, NULL);
-    free(name);
+    if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
+        name += sizeof("SCC:") - 1;
+
+    if ((f = strdup(name)) == NULL) {
+        free(s);
+        return krb5_enomem(context);
+    }
+    free(s);
+
+    if ((s = strrchr(f, ':')))
+        *s = '\0';
+
+    ret = sqlite3_open_v2(f, db, SQLITE_OPEN_READWRITE, NULL);
     if (ret != SQLITE_OK) {
+        sqlite3_close_v2(*db);
        krb5_clear_error_message(context);
+        free(f);
        return ENOENT;
     }
 
+    if (file)
+        *file = f;
+    else
+        free(f);
+
 #ifdef TRACEME
     sqlite3_trace(*db, trace, NULL);
 #endif
@@ -249,14 +286,14 @@ default_db(krb5_context context, sqlite3 **db)
 }
 
 static krb5_error_code
-get_def_name(krb5_context context, char **str)
+get_def_name(krb5_context context, char *filein, char **str, char **file)
 {
     krb5_error_code ret;
     sqlite3_stmt *stmt;
     const char *name;
     sqlite3 *db;
 
-    ret = default_db(context, &db);
+    ret = default_db(context, filein, &db, file);
     if (ret)
        return ret;
 
@@ -294,10 +331,15 @@ out:
 
 
 static krb5_scache * KRB5_CALLCONV
-scc_alloc(krb5_context context, const char *name)
+scc_alloc(krb5_context context,
+          const char *name,
+          const char *sub,
+          int new_unique)
 {
-    krb5_error_code ret;
+    krb5_error_code ret = 0;
     krb5_scache *s;
+    char *freeme = NULL;
+    char *subsidiary;
 
     ALLOC(s, 1);
     if(s == NULL)
@@ -305,41 +347,87 @@ scc_alloc(krb5_context context, const char *name)
 
     s->cid = SCACHE_INVALID_CID;
 
-    if (name) {
-       char *file;
-
-       if (*name == '\0') {
-           ret = get_def_name(context, &s->name);
-           if (ret)
-               s->name = strdup(SCACHE_DEF_NAME);
-       } else
-           s->name = strdup(name);
-
-       file = strrchr(s->name, ':');
-       if (file) {
-           *file++ = '\0';
-           s->file = strdup(file);
-           ret = 0;
-       } else {
-           ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file);
-       }
+    if (name && *name && sub && *sub) {
+        if ((s->sub = strdup(sub)) == NULL ||
+            (s->file = strdup(name)) == NULL) {
+            free(s->file);
+            free(s);
+            (void) krb5_enomem(context);
+            return NULL;
+        }
     } else {
-       _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB, &s->file);
-       ret = asprintf(&s->name, "unique-%p", s);
+        s->sub = NULL;
+        s->file = NULL;
+        s->name = NULL;
+
+        if (name == NULL)
+            name = krb5_cc_default_name(context);
+        if (name == NULL) {
+            ret = _krb5_expand_default_cc_name(context, KRB5_SCACHE_DB,
+                                               &freeme);
+            if (ret) {
+                free(s);
+                return NULL;
+            }
+            name = freeme;
+        }
+
+        if (strncmp(name, "SCC:", sizeof("SCC:") - 1) == 0)
+            name += sizeof("SCC:") - 1;
+
+        if ((s->file = strdup(name)) == NULL) {
+            (void) krb5_enomem(context);
+            scc_free(s);
+            free(freeme);
+            return NULL;
+        }
+
+        if ((subsidiary = strrchr(s->file, ':'))) {
+#ifdef WIN32
+            if (subsidiary == &s->file + 1)
+                subsidiary = NULL;
+            else
+#endif
+                *(subsidiary++) = '\0';
+        }
+
+        if (new_unique) {
+            ret = asprintf(&s->sub, "unique-%p", s) < 0 || s->sub == NULL ?
+                krb5_enomem(context) : 0;
+        } else if (subsidiary == NULL || *subsidiary == '\0') {
+            ret = get_def_name(context, s->file, &s->sub, NULL);
+            if (ret) {
+                if ((s->sub = strdup(SCACHE_DEF_NAME)) == NULL)
+                    ret = krb5_enomem(context);
+                else
+                    ret = 0;
+            }
+        } else if ((s->sub = strdup(subsidiary)) == NULL) {
+            ret = krb5_enomem(context);
+        }
     }
-    if (ret < 0 || s->file == NULL || s->name == NULL) {
+
+    if (ret == 0 && s->file && s->sub &&
+        (asprintf(&s->name, "%s:%s", s->file, s->sub) < 0 || s->name == NULL))
+        ret = krb5_enomem(context);
+    if (ret || s->file == NULL || s->sub == NULL || s->name == NULL) {
        scc_free(s);
+        free(freeme);
        return NULL;
     }
-
     return s;
 }
 
 static krb5_error_code
 open_database(krb5_context context, krb5_scache *s, int flags)
 {
+    struct stat st;
     int ret;
 
+
+    if (!(flags & SQLITE_OPEN_CREATE) && stat(s->file, &st) == 0 &&
+        st.st_size == 0)
+        return ENOENT;
     ret = sqlite3_open_v2(s->file, &s->db, SQLITE_OPEN_READWRITE|flags, NULL);
     if (ret) {
        if (s->db) {
@@ -361,7 +449,7 @@ create_cache(krb5_context context, krb5_scache *s)
 {
     int ret;
 
-    sqlite3_bind_text(s->icache, 1, s->name, -1, NULL);
+    sqlite3_bind_text(s->icache, 1, s->sub, -1, NULL);
     do {
        ret = sqlite3_step(s->icache);
     } while (ret == SQLITE_ROW);
@@ -477,20 +565,32 @@ bind_principal(krb5_context context,
  *
  */
 
-static const char* KRB5_CALLCONV
+static krb5_error_code KRB5_CALLCONV
 scc_get_name(krb5_context context,
-            krb5_ccache id)
+            krb5_ccache id,
+             const char **name,
+             const char **file,
+             const char **sub)
 {
-    return SCACHE(id)->name;
+    if (name)
+        *name = SCACHE(id)->name;
+    if (file)
+        *file = SCACHE(id)->file;
+    if (sub)
+        *sub = SCACHE(id)->sub;
+    return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
-scc_resolve(krb5_context context, krb5_ccache *id, const char *res)
+scc_resolve(krb5_context context,
+            krb5_ccache *id,
+            const char *res,
+            const char *sub)
 {
+    krb5_error_code ret;
     krb5_scache *s;
-    int ret;
 
-    s = scc_alloc(context, res);
+    s = scc_alloc(context, res, sub, 0);
     if (s == NULL) {
        krb5_set_error_message(context, KRB5_CC_NOMEM,
                               N_("malloc: out of memory", ""));
@@ -503,12 +603,12 @@ scc_resolve(krb5_context context, krb5_ccache *id, const char *res)
        return ret;
     }
 
-    ret = sqlite3_bind_text(s->scache_name, 1, s->name, -1, NULL);
+    ret = sqlite3_bind_text(s->scache_name, 1, s->sub, -1, NULL);
     if (ret != SQLITE_OK) {
-       krb5_set_error_message(context, ENOMEM,
-                              "bind name: %s", sqlite3_errmsg(s->db));
-       scc_free(s);
-       return ENOMEM;
+        krb5_set_error_message(context, ENOMEM,
+                               "bind principal: %s", sqlite3_errmsg(s->db));
+        scc_free(s);
+        return ENOMEM;
     }
 
     if (sqlite3_step(s->scache_name) == SQLITE_ROW) {
@@ -540,7 +640,7 @@ scc_gen_new(krb5_context context, krb5_ccache *id)
 {
     krb5_scache *s;
 
-    s = scc_alloc(context, NULL);
+    s = scc_alloc(context, NULL, NULL, 1);
 
     if (s == NULL) {
        krb5_set_error_message(context, KRB5_CC_NOMEM,
@@ -557,7 +657,7 @@ scc_gen_new(krb5_context context, krb5_ccache *id)
 static krb5_error_code KRB5_CALLCONV
 scc_initialize(krb5_context context,
               krb5_ccache id,
-              krb5_principal primary_principal)
+              krb5_principal principal)
 {
     krb5_scache *s = SCACHE(id);
     krb5_error_code ret;
@@ -589,7 +689,7 @@ scc_initialize(krb5_context context,
        }
     }
 
-    ret = bind_principal(context, s->db, s->ucachep, 1, primary_principal);
+    ret = bind_principal(context, s->db, s->ucachep, 1, principal);
     if (ret)
        goto rollback;
     sqlite3_bind_int(s->ucachep, 2, s->cid);
@@ -827,8 +927,8 @@ scc_get_principal(krb5_context context,
     if (sqlite3_step(s->scache) != SQLITE_ROW) {
        sqlite3_reset(s->scache);
        krb5_set_error_message(context, KRB5_CC_END,
-                              N_("No principal for cache SCC:%s:%s", ""),
-                              s->name, s->file);
+                              N_("No principal for cache SCC:%s", ""),
+                              s->name);
        return KRB5_CC_END;
     }
 
@@ -836,8 +936,8 @@ scc_get_principal(krb5_context context,
        sqlite3_reset(s->scache);
        krb5_set_error_message(context, KRB5_CC_END,
                               N_("Principal data of wrong type "
-                                 "for SCC:%s:%s", ""),
-                              s->name, s->file);
+                                 "for SCC:%s", ""),
+                              s->name);
        return KRB5_CC_END;
     }
 
@@ -845,8 +945,8 @@ scc_get_principal(krb5_context context,
     if (str == NULL) {
        sqlite3_reset(s->scache);
        krb5_set_error_message(context, KRB5_CC_END,
-                              N_("Principal not set for SCC:%s:%s", ""),
-                              s->name, s->file);
+                              N_("Principal not set for SCC:%s", ""),
+                              s->name);
        return KRB5_CC_END;
     }
 
@@ -1001,8 +1101,8 @@ next:
 
     if (sqlite3_column_type(ctx->credstmt, 0) != SQLITE_BLOB) {
        krb5_set_error_message(context, KRB5_CC_END,
-                              N_("credential of wrong type for SCC:%s:%s", ""),
-                              s->name, s->file);
+                              N_("credential of wrong type for SCC:%s", ""),
+                              s->name);
        sqlite3_reset(ctx->credstmt);
        return KRB5_CC_END;
     }
@@ -1079,8 +1179,8 @@ scc_remove_cred(krb5_context context,
            ret = KRB5_CC_END;
            krb5_set_error_message(context, ret,
                                   N_("Credential of wrong type "
-                                     "for SCC:%s:%s", ""),
-                                  s->name, s->file);
+                                     "for SCC:%s", ""),
+                                  s->name);
            break;
        }
 
@@ -1134,6 +1234,7 @@ scc_set_flags(krb5_context context,
 
 struct cache_iter {
     char *drop;
+    char *file;
     sqlite3 *db;
     sqlite3_stmt *stmt;
 };
@@ -1151,8 +1252,8 @@ scc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
     if (ctx == NULL)
        return krb5_enomem(context);
 
-    ret = default_db(context, &ctx->db);
-    if (ctx->db == NULL) {
+    ret = default_db(context, NULL, &ctx->db, &ctx->file);
+    if (ret) {
        free(ctx);
        return ret;
     }
@@ -1160,48 +1261,48 @@ scc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
     ret = asprintf(&name, "cacheIteration%pPid%d",
                    ctx, (int)getpid());
     if (ret < 0 || name == NULL) {
-       sqlite3_close(ctx->db);
-       free(ctx);
-       return krb5_enomem(context);
+       sqlite3_close(ctx->db);
+       free(ctx);
+       return krb5_enomem(context);
     }
 
     ret = asprintf(&ctx->drop, "DROP TABLE %s", name);
     if (ret < 0 || ctx->drop == NULL) {
-       sqlite3_close(ctx->db);
-       free(name);
-       free(ctx);
-       return krb5_enomem(context);
+       sqlite3_close(ctx->db);
+       free(name);
+       free(ctx);
+       return krb5_enomem(context);
     }
 
     ret = asprintf(&str, "CREATE TEMPORARY TABLE %s AS SELECT name FROM caches",
-            name);
+            name);
     if (ret < 0 || str == NULL) {
-       sqlite3_close(ctx->db);
-       free(name);
-       free(ctx->drop);
-       free(ctx);
-       return krb5_enomem(context);
+       sqlite3_close(ctx->db);
+       free(name);
+       free(ctx->drop);
+       free(ctx);
+       return krb5_enomem(context);
     }
 
     ret = exec_stmt(context, ctx->db, str, KRB5_CC_IO);
     free(str);
     str = NULL;
     if (ret) {
-       sqlite3_close(ctx->db);
-       free(name);
-       free(ctx->drop);
-       free(ctx);
-       return ret;
+       sqlite3_close(ctx->db);
+       free(name);
+       free(ctx->drop);
+       free(ctx);
+       return ret;
     }
 
     ret = asprintf(&str, "SELECT name FROM %s", name);
     if (ret < 0 || str == NULL) {
-       exec_stmt(context, ctx->db, ctx->drop, 0);
-       sqlite3_close(ctx->db);
-       free(name);
-       free(ctx->drop);
-       free(ctx);
-       return krb5_enomem(context);
+       exec_stmt(context, ctx->db, ctx->drop, 0);
+       sqlite3_close(ctx->db);
+       free(name);
+       free(ctx->drop);
+       free(ctx);
+       return krb5_enomem(context);
     }
     free(name);
 
@@ -1249,10 +1350,13 @@ again:
        goto again;
 
     ret = _krb5_cc_allocate(context, &krb5_scc_ops, id);
-    if (ret)
-       return ret;
-
-    return scc_resolve(context, id, name);
+    if (ret == 0)
+        ret = scc_resolve(context, id, ctx->file, name);
+    if (ret) {
+        free(*id);
+        *id = NULL;
+    }
+    return ret;
 }
 
 static krb5_error_code KRB5_CALLCONV
@@ -1263,6 +1367,7 @@ scc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
     exec_stmt(context, ctx->db, ctx->drop, 0);
     sqlite3_finalize(ctx->stmt);
     sqlite3_close(ctx->db);
+    free(ctx->file);
     free(ctx->drop);
     free(ctx);
     return 0;
@@ -1304,7 +1409,7 @@ scc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
        }
     }
 
-    sqlite3_bind_text(sfrom->ucachen, 1, sto->name, -1, NULL);
+    sqlite3_bind_text(sfrom->ucachen, 1, sto->sub, -1, NULL);
     sqlite3_bind_int(sfrom->ucachen, 2, sfrom->cid);
 
     do {
@@ -1334,20 +1439,8 @@ rollback:
 static krb5_error_code KRB5_CALLCONV
 scc_get_default_name(krb5_context context, char **str)
 {
-    krb5_error_code ret;
-    char *name;
-
     *str = NULL;
-
-    ret = get_def_name(context, &name);
-    if (ret)
-       return _krb5_expand_default_cc_name(context, KRB5_SCACHE_NAME, str);
-
-    ret = asprintf(str, "SCC:%s", name);
-    free(name);
-    if (ret < 0 || *str == NULL)
-       return krb5_enomem(context);
-    return 0;
+    return _krb5_expand_default_cc_name(context, KRB5_SCACHE_NAME, str);
 }
 
 static krb5_error_code KRB5_CALLCONV
@@ -1364,7 +1457,7 @@ scc_set_default(krb5_context context, krb5_ccache id)
        return KRB5_CC_IO;
     }
 
-    ret = sqlite3_bind_text(s->umaster, 1, s->name, -1, NULL);
+    ret = sqlite3_bind_text(s->umaster, 1, s->sub, -1, NULL);
     if (ret) {
        sqlite3_reset(s->umaster);
        krb5_set_error_message(context, KRB5_CC_IO,
index fce86c911ffaee30fcb54c30e9f6a7f9e8a558ed..0a8145da42957ad7e793562251bd9538df524cd2 100644 (file)
@@ -669,6 +669,176 @@ test_cc_config(krb5_context context, const char *cc_type,
     krb5_free_principal(context, p);
 }
 
+static krb5_error_code
+test_cccol(krb5_context context, const char *def_cccol, const char **what)
+{
+    krb5_cc_cache_cursor cursor;
+    krb5_error_code ret;
+    krb5_principal p1, p2;
+    krb5_ccache id, id1, id2;
+    krb5_creds cred1, cred2;
+    size_t match1 = 0;
+    size_t match2 = 0;
+
+    memset(&cred1, 0, sizeof(cred1));
+    memset(&cred2, 0, sizeof(cred2));
+
+    *what = "krb5_parse_name";
+    ret = krb5_parse_name(context, "krbtgt/SU.SE@SU.SE", &cred1.server);
+    if (ret) return ret;
+    ret = krb5_parse_name(context, "lha@SU.SE", &cred1.client);
+    if (ret) return ret;
+    ret = krb5_parse_name(context, "krbtgt/H5L.SE@H5L.SE", &cred2.server);
+    if (ret) return ret;
+    ret = krb5_parse_name(context, "lha@H5L.SE", &cred2.client);
+    if (ret) return ret;
+    *what = "krb5_cc_set_default_name";
+    ret = krb5_cc_set_default_name(context, def_cccol);
+    if (ret) return ret;
+    *what = "krb5_cc_default";
+    ret = krb5_cc_default(context, &id1);
+    if (ret) return ret;
+    *what = "krb5_cc_initialize";
+    ret = krb5_cc_initialize(context, id1, cred1.client);
+    if (ret) return ret;
+    *what = "krb5_cc_store_cred";
+    ret = krb5_cc_store_cred(context, id1, &cred1);
+    if (ret) return ret;
+    *what = "krb5_cc_resolve";
+    ret = krb5_cc_resolve_for(context, NULL, def_cccol, cred2.client, &id2);
+    if (ret) return ret;
+    *what = "krb5_cc_initialize";
+    ret = krb5_cc_initialize(context, id2, cred2.client);
+    if (ret) return ret;
+    *what = "krb5_cc_store_cred";
+    ret = krb5_cc_store_cred(context, id2, &cred2);
+    if (ret) return ret;
+
+    krb5_cc_close(context, id1);
+    krb5_cc_close(context, id2);
+    id1 = id2 = NULL;
+
+    *what = "krb5_cc_default";
+    ret = krb5_cc_default(context, &id1);
+    if (ret) return ret;
+    *what = "krb5_cc_resolve";
+    ret = krb5_cc_resolve_for(context, NULL, def_cccol, cred2.client, &id2);
+    if (ret) return ret;
+
+    *what = "krb5_cc_get_principal";
+    ret = krb5_cc_get_principal(context, id1, &p1);
+    if (ret) return ret;
+    ret = krb5_cc_get_principal(context, id2, &p2);
+    if (ret) return ret;
+
+    if (!krb5_principal_compare(context, p1, cred1.client)) {
+        char *u1 = NULL;
+        char *u2 = NULL;
+
+        (void) krb5_unparse_name(context, p1, &u1);
+        (void) krb5_unparse_name(context, cred1.client, &u2);
+        warnx("Inconsistent principals for ccaches in %s: %s vs %s "
+              "(expected lha@SU.SE)", def_cccol, u1, u2);
+        return EINVAL;
+    }
+    if (!krb5_principal_compare(context, p2, cred2.client)) {
+        char *u1 = NULL;
+        char *u2 = NULL;
+
+        (void) krb5_unparse_name(context, p2, &u1);
+        (void) krb5_unparse_name(context, cred2.client, &u2);
+        warnx("Inconsistent principals for ccaches in %s: %s and %s "
+              "(expected lha@H5L.SE)", def_cccol, u1, u2);
+        return EINVAL;
+    }
+    krb5_free_principal(context, p1);
+    krb5_free_principal(context, p2);
+
+    *what = "krb5_cc_cache_get_first";
+    ret = krb5_cc_cache_get_first(context, NULL, &cursor);
+    if (ret) return ret;
+    *what = "krb5_cc_cache_next";
+    while (krb5_cc_cache_next(context, cursor, &id) == 0) {
+        krb5_principal p;
+
+        *what = "krb5_cc_get_principal";
+        ret = krb5_cc_get_principal(context, id, &p);
+        if (ret) return ret;
+        if (krb5_principal_compare(context, p, cred1.client))
+            match1++;
+        else if (krb5_principal_compare(context, p, cred2.client))
+            match2++;
+       krb5_free_principal(context, p);
+        krb5_cc_close(context, id);
+    }
+    (void) krb5_cc_cache_end_seq_get(context, cursor);
+
+    *what = "cccol iteration inconsistency";
+    if (match1 != 1 || match2 != 1) return EINVAL;
+
+    krb5_cc_close(context, id1);
+    krb5_cc_close(context, id2);
+
+    krb5_free_cred_contents(context, &cred1);
+    krb5_free_cred_contents(context, &cred2);
+
+    return 0;
+}
+
+static void
+test_cccol_dcache(krb5_context context)
+{
+    krb5_error_code ret;
+    char template[sizeof("DIR:dcache-XXXXXX")];
+    char *s;
+    const char *what;
+
+    memcpy(template, "DIR:dcache-XXXXXX", sizeof("DIR:dcache-XXXXXX"));
+    if (mkdtemp(template + sizeof("DIR:") - 1) == NULL)
+        krb5_err(context, 1, errno, "mkdtemp");
+
+    ret = test_cccol(context, template, &what);
+
+    if (asprintf(&s, "%s/primary", template + sizeof("DIR:") - 1) > 0) {
+        (void) unlink(s);
+        free(s);
+    }
+    if (asprintf(&s, "%s/tkt", template + sizeof("DIR:") - 1) > 0) {
+        (void) unlink(s);
+        free(s);
+    }
+    if (asprintf(&s, "%s/tkt.lha@H5L.SE", template + sizeof("DIR:") - 1) > 0) {
+        (void) unlink(s);
+        free(s);
+    }
+    if (asprintf(&s, "%s/tkt.lha@SU.SE", template + sizeof("DIR:") - 1) > 0) {
+        (void) unlink(s);
+        free(s);
+    }
+    (void) rmdir(template + sizeof("DIR:") - 1); /* XXX Check that this succeeds */
+    if (ret)
+        krb5_err(context, 1, errno, "%s", what);
+}
+
+static void
+test_cccol_scache(krb5_context context)
+{
+    krb5_error_code ret;
+    char template[sizeof("SCC:scache-XXXXXX")];
+    const char *what;
+    int fd;
+
+    memcpy(template, "SCC:scache-XXXXXX", sizeof("SCC:scache-XXXXXX"));
+    if ((fd = mkstemp(template + sizeof("SCC:") - 1)) == -1)
+        krb5_err(context, 1, errno, "mkstemp");
+    (void) close(fd);
+
+    ret = test_cccol(context, template, &what);
+    (void) unlink(template + sizeof("SCC:") - 1);
+    if (ret)
+        krb5_err(context, 1, ret, "%s", what);
+}
+
 
 static struct getargs args[] = {
     {"debug",  'd',    arg_flag,       &debug_flag,
@@ -725,6 +895,10 @@ main(int argc, char **argv)
 
     test_default_name(context);
     test_mcache(context);
+    /*
+     * XXX Make sure to set default ccache names for each cc type!
+     * Otherwise we clobber the user's ccaches.
+     */
     test_init_vs_destroy(context, krb5_cc_type_memory);
     test_init_vs_destroy(context, krb5_cc_type_file);
 #if 0
@@ -753,6 +927,14 @@ main(int argc, char **argv)
     test_cache_find(context, "lha@SU.SE", 1);
     test_cache_find(context, "hulabundulahotentot@SU.SE", 0);
 
+    /*
+     * XXX We should compose and krb5_cc_set_default_name() a default ccache
+     * for each cc type that we test with test_cache_iter(), and we should do
+     * that inside test_cache_iter().
+     *
+     * Alternatively we should remove test_cache_iter() in favor of
+     * test_cccol(), which is a much more complete test.
+     */
     test_cache_iter(context, krb5_cc_type_memory, 0);
     test_cache_iter(context, krb5_cc_type_memory, 1);
     test_cache_iter(context, krb5_cc_type_memory, 0);
@@ -860,6 +1042,22 @@ main(int argc, char **argv)
     test_cc_config(context, "MEMORY", "bar", 1000);  /* 1000 because fast */
     test_cc_config(context, "FILE", "/tmp/foocc", 30); /* 30 because slower */
 
+    test_cccol_dcache(context);
+    test_cccol_scache(context);
+#ifdef HAVE_KEYUTILS_H
+    {
+        const char *what;
+
+        ret = test_cccol(context, "KEYRING:legacy:fooccol", &what);
+        if (ret)
+            krb5_err(context, 1, ret, "%s", what);
+
+        ret = test_cccol(context, "MEMORY:fooccol", &what);
+        if (ret)
+            krb5_err(context, 1, ret, "%s", what);
+    }
+#endif /* HAVE_KEYUTILS_H */
+
     krb5_free_context(context);
 
 #if 0
index 128ebb27093dba2630ed1caa71b95f64b4eb7dd1..1063ecdae0d373f55adf2d897a267d0979658fe0 100644 (file)
@@ -410,6 +410,7 @@ struct entry libdefaults_entries[] = {
     { "default_client_keytab_name", krb5_config_string, NULL, 0 },
     { "default_cc_name", krb5_config_string, NULL, 0 },
     { "default_cc_type", krb5_config_string, NULL, 0 },
+    { "default_cc_collection", krb5_config_string, NULL, 0 },
     { "default_etypes", krb5_config_string, NULL, 0 },
     { "default_etypes_des", krb5_config_string, NULL, 0 },
     { "default_keytab_modify_name", krb5_config_string, NULL, 0 },
index d35fb3c0469235a662036c7f2c261e9e875e4c77..18d27dc8b71a334c8a8d1e23fab848e69072edb3 100644 (file)
@@ -87,6 +87,8 @@ HEIMDAL_KRB5_2.0 {
                krb5_cc_copy_cache;
                krb5_cc_copy_match_f;
                krb5_cc_default;
+               krb5_cc_default_for;
+               krb5_cc_default_sub;
                krb5_cc_default_name;
                krb5_cc_destroy;
                krb5_cc_end_seq_get;
@@ -111,6 +113,8 @@ HEIMDAL_KRB5_2.0 {
                krb5_cc_register;
                krb5_cc_remove_cred;
                krb5_cc_resolve;
+               krb5_cc_resolve_for;
+               krb5_cc_resolve_sub;
                krb5_cc_retrieve_cred;
                krb5_cc_set_config;
                krb5_cc_set_default_name;
index bd219b5e06c84d6a776d0b2f489953cb0500ecf4..a4c58db7d07669d569db39554be0bd7b2564c05c 100644 (file)
@@ -105,7 +105,7 @@ echo "initial ticket"
 ${kinit} -c ${cache} --password-file=${objdir}/foopassword user@${R} || exitcode=1
 
 echo "copy ccache with gss_store_cred"
-${test_add_store_cred} ${cache} ${cache2} || exit 1
+${test_add_store_cred} --default --overwrite --env ${cache} ${cache2} || exit 1
 ${klist} -c ${cache2} || exit 1
 
 echo "keytab"
index a192ae1c4f825de9d39e76907d82b44f846b4457..2f215ca7d915d5b91d45ef081490b50d65f01116 100644 (file)
@@ -53,6 +53,7 @@ nokeytab="FILE:no-such-keytab"
 cache="FILE:krb5ccfile"
 
 kinit="${TESTS_ENVIRONMENT} ../../kuser/kinit -c $cache ${afs_no_afslog}"
+kdestroy="${TESTS_ENVIRONMENT} ../../kuser/kdestroy -c $cache"
 klist="${TESTS_ENVIRONMENT} ../../kuser/heimtools klist -c $cache"
 kgetcred="${TESTS_ENVIRONMENT} ../../kuser/kgetcred -c $cache"
 kadmin="${TESTS_ENVIRONMENT} ../../kadmin/kadmin -l -r $R"
@@ -121,17 +122,23 @@ trap "kill ${kdcpid}; echo signal killing kdc; exit 1;" EXIT
 testfailed="echo test failed; cat messages.log; exit 1"
 
 echo "Test gss_acquire_cred_with_password" ; > messages.log
+${kdestroy}
 ${context} --client-name=user1@${R} --client-password=u1 --mech-type=krb5 \
         host@lucid.test.h5l.se || { eval "$testfailed"; }
+${klist} && { eval "$testfailed"; }
 # These must fail (because wrong password)
 ${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \
         host@lucid.test.h5l.se && { eval "$testfailed"; }
+${klist} && { eval "$testfailed"; }
 ${context} --client-name=user1@${R} --client-password=u2 --mech-type='' \
         --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
+${klist} && { eval "$testfailed"; }
 ${context} --client-name=user1@${R} --client-password=u2 --mech-type=krb5 \
         --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
+${klist} && { eval "$testfailed"; }
 ${context} --client-name=user1@${R} --client-password=u2 --mech-type=all \
         --mech-types=krb5 host@lucid.test.h5l.se && { eval "$testfailed"; }
+${klist} && { eval "$testfailed"; }
 ${context} --client-name=user1@${R} --client-password=u2 \
         --mech-type=krb5,ntlm --mech-types=krb5 host@lucid.test.h5l.se \
         && { eval "$testfailed"; }
index 4bb5dacc845e04841a7e8565ddd00c1cb3155a42..7efc86b8fdf6f9fd4cf903cfd1659d130e185d58 100644 (file)
@@ -4,6 +4,7 @@ noinst_DATA = \
        an2ln-db.txt \
        kdc-tester4.json \
        krb5.conf \
+       krb5-cccol.conf \
        krb5-authz.conf \
        krb5-authz2.conf \
        krb5-canon.conf \
@@ -183,6 +184,13 @@ krb5.conf: krb5.conf.in Makefile
           -e 's,[@]kdc[@],,g' < $(srcdir)/krb5.conf.in > krb5.conf.tmp && \
        mv krb5.conf.tmp krb5.conf
 
+krb5-cccol.conf: krb5-cccol.conf.in Makefile
+       $(do_subst) \
+          -e 's,[@]WEAK[@],false,g' \
+          -e 's,[@]dk[@],,g' \
+          -e 's,[@]kdc[@],,g' < $(srcdir)/krb5-cccol.conf.in > krb5-cccol.conf.tmp && \
+       mv krb5-cccol.conf.tmp krb5-cccol.conf
+
 krb5-authz.conf: krb5-authz.conf.in Makefile
        $(do_subst) < $(srcdir)/krb5-authz.conf.in > krb5-authz.conf.tmp && \
        mv krb5-authz.conf.tmp krb5-authz.conf
index 1543db4f88198b4cd7b96ba8dfbd20aeb4c38e3e..e696215237e8aa9cb33853c104440b2db6d53092 100644 (file)
@@ -36,7 +36,7 @@ objdir="@objdir@"
 
 . ${env_setup}
 
-KRB5_CONFIG="${objdir}/krb5-cc.conf"
+KRB5_CONFIG="${objdir}/krb5.conf"
 export KRB5_CONFIG
 
 unset KRB5CCNAME
@@ -68,8 +68,6 @@ rm -f mkey.file*
 
 > messages.log
 
-cp "${objdir}/krb5.conf" "${objdir}/krb5-cc.conf"
-
 echo Creating database
 ${kadmin} \
     init \
@@ -95,12 +93,7 @@ trap "kill -9 ${kdcpid}; echo signal killing kdc; exit 1;" EXIT
 
 ec=0
 
-(cat ${objdir}/krb5.conf ;                     \
- echo '' ;                                     \
- echo '[libdefaults]' ;                        \
- echo "    default_cc_type = SCC" ;            \
- echo '' )                                     \
-    > ${objdir}/krb5-cc.conf
+export KRB5CCNAME=SCC:${objdir}/sdb
 
 ${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy}
 ${kswitch} -p foo@${R} 2>/dev/null && ${kdestroy}
@@ -114,8 +107,8 @@ ${klist} -l | grep foo@ >/dev/null || { ec=1 ; eval "${testfailed}"; }
 ${kdestroy}
 
 echo "getting both tickets"; > messages.log
-${kinit} -c SCC:1 foo@${R} || { ec=1 ; eval "${testfailed}"; }
-${kinit} -c SCC:2 bar@${R} || { ec=1 ; eval "${testfailed}"; }
+${kinit} -c ${KRB5CCNAME}:1 foo@${R} || { ec=1 ; eval "${testfailed}"; }
+${kinit} -c ${KRB5CCNAME}:2 bar@${R} || { ec=1 ; eval "${testfailed}"; }
 echo "switch foo"
 ${kswitch} -p foo@${R} || { ec=1 ; eval "${testfailed}"; }
 ${klist} | head -2 | grep foo@ >/dev/null || { ec=1 ; eval "${testfailed}"; }
@@ -140,6 +133,22 @@ ${klist} -l | grep foo@ >/dev/null && { ec=1 ; eval "${testfailed}"; }
 echo "check that bar is gone"
 ${klist} -l | grep bar@ >/dev/null && { ec=1 ; eval "${testfailed}"; }
 
+echo "getting tickets (DIR)"; > messages.log
+KRB5_CONFIG="${objdir}/krb5-cccol.conf"
+export KRB5_CONFIG
+unset KRB5CCNAME
+rm -rf ${objdir}/kt ${objdir}/cc_dir
+mkdir ${objdir}/cc_dir || { ec=1 ; eval "${testfailed}"; }
+${kinit} foo@${R} || { ec=1 ; eval "${testfailed}"; }
+${kinit} --no-change-default bar@${R} || { ec=1 ; eval "${testfailed}"; }
+primary=`cat ${objdir}/cc_dir/primary`
+[ "x$primary" = xtkt.foo@${R} ] || { ec=1 ; eval "${testfailed}"; }
+${klist} -l |
+    grep "foo@TEST.H5L.SE.*FILE:${objdir}/cc_dir/tkt.foo@TEST.H5L.SE" > /dev/null ||
+    { ec=1 ; eval "${testfailed}"; }
+${klist} -l |
+    grep "bar@TEST.H5L.SE.*FILE:${objdir}/cc_dir/tkt.bar@TEST.H5L.SE" > /dev/null ||
+    { ec=1 ; eval "${testfailed}"; }
 
 echo "killing kdc (${kdcpid})"
 sh ${leaks_kill} kdc $kdcpid || exit 1
diff --git a/tests/kdc/krb5-cccol.conf.in b/tests/kdc/krb5-cccol.conf.in
new file mode 100644 (file)
index 0000000..819de80
--- /dev/null
@@ -0,0 +1,165 @@
+[libdefaults]
+       default_realm = TEST.H5L.SE TEST2.H5L.SE
+        default_cc_collection = DIR:@objdir@/cc_dir/
+       no-addresses = TRUE
+       allow_weak_crypto = @WEAK@
+       dns_lookup_kdc = no
+       dns_lookup_realm = no
+
+
+[appdefaults]
+       pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt
+       reconnect-min = 2s
+       reconnect-backoff = 2s
+       reconnect-max = 10s
+
+[realms]
+       TEST.H5L.SE = {
+               kdc = localhost:@port@
+               admin_server = localhost:@admport@
+               kpasswd_server = localhost:@pwport@
+       }
+       SUB.TEST.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       TEST2.H5L.SE = {
+               kdc = localhost:@port@
+               kpasswd_server = localhost:@pwport@
+       }
+       TEST3.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       TEST4.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       SOME-REALM5.FR = {
+               kdc = localhost:@port@
+       }
+       SOME-REALM6.US = {
+               kdc = localhost:@port@
+       }
+       SOME-REALM7.UK = {
+               kdc = localhost:@port@
+       }
+       SOME-REALM8.UK = {
+               kdc = localhost:@port@
+       }
+       TEST-HTTP.H5L.SE = {
+               kdc = http/localhost:@port@
+       }
+       H1.TEST.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       H2.TEST.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       H3.H2.TEST.H5L.SE = {
+               kdc = localhost:@port@
+       }
+       H4.H2.TEST.H5L.SE = {
+               kdc = localhost:@port@
+       }
+
+[domain_realm]
+       .test.h5l.se = TEST.H5L.SE
+       .sub.test.h5l.se = SUB.TEST.H5L.SE
+       .h1.test.h5l.se = H1.TEST.H5L.SE
+       .h2.test.h5l.se = H2.TEST.H5L.SE
+       .h3.h2.test.h5l.se = H3.H2.TEST.H5L.SE
+       .h4.h2.test.h5l.se = H4.H2.TEST.H5L.SE
+       .example.com = TEST2.H5L.SE
+       localhost = TEST.H5L.SE
+       .localdomain = TEST.H5L.SE
+       localdomain = TEST.H5L.SE
+       .localdomain6 = TEST.H5L.SE
+       localdomain6 = TEST.H5L.SE
+       
+
+[kdc]
+       enable-digest = true
+       allow-anonymous = true
+       digests_allowed = chap-md5,digest-md5,ntlm-v1,ntlm-v1-session,ntlm-v2,ms-chap-v2
+        strict-nametypes = true
+
+       enable-http = true
+
+       enable-pkinit = true
+       pkinit_identity = FILE:@srcdir@/../../lib/hx509/data/kdc.crt,@srcdir@/../../lib/hx509/data/kdc.key
+       pkinit_anchors = FILE:@srcdir@/../../lib/hx509/data/ca.crt
+       pkinit_pool = FILE:@srcdir@/../../lib/hx509/data/sub-ca.crt
+#      pkinit_revoke = CRL:@srcdir@/../../lib/hx509/data/crl1.crl
+       pkinit_mappings_file = @srcdir@/pki-mapping
+       pkinit_allow_proxy_certificate = true
+
+       database = {
+               label = { 
+                       dbname = @db_type@:@objdir@/current-db@kdc@
+                       realm = TEST.H5L.SE
+                       mkey_file = @objdir@/mkey.file
+                       acl_file = @srcdir@/heimdal.acl
+                       log_file = @objdir@/current@kdc@.log
+               }
+               label2 = { 
+                       dbname = @db_type@:@objdir@/current-db@kdc@
+                       realm = TEST2.H5L.SE
+                       mkey_file = @objdir@/mkey.file
+                       acl_file = @srcdir@/heimdal.acl
+                       log_file = @objdir@/current@kdc@.log
+               }
+               label3 = { 
+                       dbname = sqlite:@objdir@/current-db@kdc@.sqlite3
+                       realm = SOME-REALM5.FR
+                       mkey_file = @objdir@/mkey.file
+                       acl_file = @srcdir@/heimdal.acl
+                       log_file = @objdir@/current@kdc@.log
+               }
+       }
+
+       signal_socket = @objdir@/signal
+       iprop-stats = @objdir@/iprop-stats
+       iprop-acl = @srcdir@/iprop-acl
+        log-max-size = 40000
+
+[hdb]
+       db-dir = @objdir@
+
+[logging]
+       kdc = 0-/FILE:@objdir@/messages.log
+       krb5 = 0-/FILE:@objdir@/messages.log
+       default = 0-/FILE:@objdir@/messages.log
+
+# If you are doing preformance measurements on OSX you want to change
+# the kdc LOG line from = to - below to keep the FILE open and avoid
+# open/write/close which is blocking (rdar:// ) on OSX.
+#      kdc = 0-/FILE=@objdir@/messages.log
+
+[kadmin]
+       save-password = true
+       default_key_rules = {
+               */des3-only@* = des3-cbc-sha1:pw-salt
+               */aes-only@* = aes256-cts-hmac-sha1-96:pw-salt
+       }
+       @dk@
+
+[capaths]
+       TEST.H5L.SE = {
+               TEST2.H5L.SE = .
+               SOME-REALM5.FR = 1
+               TEST3.H5L.SE = TEST2.H5L.SE
+               TEST4.H5L.SE = TEST2.H5L.SE
+               TEST4.H5L.SE = TEST3.H5L.SE
+               SOME-REALM6.US = SOME-REALM5.FR
+               SOME-REALM7.UK = SOME-REALM6.US
+               SOME-REALM7.UK = SOME-REALM5.FR
+               SOME-REALM8.UK = SOME-REALM6.US
+       }
+        H4.H2.TEST.H5L.SE = {
+                H1.TEST.H5L.SE = H3.H2.TEST.H5L.SE
+                H1.TEST.H5L.SE = H2.TEST.H5L.SE
+                H1.TEST.H5L.SE = TEST.H5L.SE
+
+                TEST.H5L.SE = H3.H2.TEST.H5L.SE
+                TEST.H5L.SE = H2.TEST.H5L.SE
+
+                H2.TEST.H5L.SE = H3.H2.TEST.H5L.SE
+        }