[krb5] Updated persistent-keyring changes, set as default

Nalin Dahyabhai nalin at fedoraproject.org
Wed Oct 2 19:01:20 UTC 2013


commit 494e7adbb0c26ae0ee51fe7ef887d28b48e7c413
Author: Nalin Dahyabhai <nalin at dahyabhai.net>
Date:   Wed Oct 2 14:46:20 2013 -0400

    Updated persistent-keyring changes, set as default
    
    - switch to the version of persistent-keyring that was just merged to
      master (RT#7711), along with related changes to kinit (RT#7689)
    - go back to setting default_ccache_name to a KEYRING type

 krb5-cccol-primary.patch      |   21 -
 krb5-master-kinit-cccol.patch |  314 +++++
 krb5.spec                     |   17 +-
 persistent_keyring.patch      | 2758 ++++++++++++++++++++++++-----------------
 4 files changed, 1971 insertions(+), 1139 deletions(-)
---
diff --git a/krb5-cccol-primary.patch b/krb5-cccol-primary.patch
index 5f1d2cf..f64b1f0 100644
--- a/krb5-cccol-primary.patch
+++ b/krb5-cccol-primary.patch
@@ -83,24 +83,3 @@ index cee21ac..b8231ed 100644
      /* Look for the next filename of the correct form, without repeating the
       * primary cache. */
      while ((ent = readdir(data->dir)) != NULL) {
-diff --git a/src/lib/krb5/ccache/t_cccol.py b/src/lib/krb5/ccache/t_cccol.py
-index acd2b6e..f0792e9 100644
---- a/src/lib/krb5/ccache/t_cccol.py
-+++ b/src/lib/krb5/ccache/t_cccol.py
-@@ -11,6 +11,7 @@ dccname = 'DIR:%s' % ccdir
- duser = 'DIR::%s/tkt1' % ccdir
- dalice = 'DIR::%s/tkt2' % ccdir
- dbob = 'DIR::%s/tkt3' % ccdir
-+dnoent = 'DIR::%s/noent' % ccdir
- realm.kinit('user', password('user'), flags=['-c', duser])
- realm.kinit('alice', password('alice'), flags=['-c', dalice])
- realm.kinit('bob', password('bob'), flags=['-c', dbob])
-@@ -30,6 +31,8 @@ cursor_test('file-default2', [realm.ccache], [fccname])
- cursor_test('file-default3', [fccname], [fccname])
- 
- cursor_test('dir', [dccname], [duser, dalice, dbob])
-+cursor_test('dir-subsidiary', [duser], [duser])
-+cursor_test('dir-nofile', [dnoent], [])
- 
- mfoo = 'MEMORY:foo'
- mbar = 'MEMORY:bar'
diff --git a/krb5-master-kinit-cccol.patch b/krb5-master-kinit-cccol.patch
new file mode 100644
index 0000000..58542fd
--- /dev/null
+++ b/krb5-master-kinit-cccol.patch
@@ -0,0 +1,314 @@
+commit d7b94742daae85329067b126d0a4bc5b2ea7e4a0
+Author: Greg Hudson <ghudson at mit.edu>
+Date:   Thu Sep 26 05:38:46 2013 -0400
+
+    Improve kinit output credential cache selection
+    
+    If kinit chooses a client principal based on anything other than the
+    current default ccache's principal name, apply collection rules if
+    possible.  When applying collection rules, if we don't find an
+    existing cache for the client principal, use the default cache if it
+    is uninitialized, instead of creating a new one.
+    
+    ticket: 7689
+
+diff --git a/src/clients/kinit/kinit.c b/src/clients/kinit/kinit.c
+index 5ceede8..d9033ec 100644
+--- a/src/clients/kinit/kinit.c
++++ b/src/clients/kinit/kinit.c
+@@ -466,9 +466,12 @@ k5_begin(opts, k5)
+     struct k5_data* k5;
+ {
+     krb5_error_code code = 0;
++    int success = 0;
+     int flags = opts->enterprise ? KRB5_PRINCIPAL_PARSE_ENTERPRISE : 0;
+-    krb5_ccache defcache;
+-    const char *deftype;
++    krb5_ccache defcache = NULL;
++    krb5_principal defcache_princ = NULL, princ;
++    const char *deftype = NULL;
++    char *defrealm, *name;
+ 
+     code = krb5_init_context(&k5->ctx);
+     if (code) {
+@@ -477,73 +480,153 @@ k5_begin(opts, k5)
+     }
+     errctx = k5->ctx;
+ 
+-    /* Parse specified principal name now if we got one. */
+-    if (opts->principal_name) {
+-        if ((code = krb5_parse_name_flags(k5->ctx, opts->principal_name,
+-                                          flags, &k5->me))) {
+-            com_err(progname, code, _("when parsing name %s"),
+-                    opts->principal_name);
+-            return 0;
+-        }
+-    }
+-
+     if (opts->k5_out_cache_name) {
+         code = krb5_cc_resolve(k5->ctx, opts->k5_out_cache_name, &k5->out_cc);
+         if (code != 0) {
+             com_err(progname, code, _("resolving ccache %s"),
+                     opts->k5_out_cache_name);
+-            return 0;
++            goto cleanup;
+         }
+         if (opts->verbose) {
+             fprintf(stderr, _("Using specified cache: %s\n"),
+                     opts->k5_out_cache_name);
+         }
+     } else {
+-        if ((code = krb5_cc_default(k5->ctx, &defcache))) {
++        /* Resolve the default ccache and get its type and default principal
++         * (if it is initialized). */
++        code = krb5_cc_default(k5->ctx, &defcache);
++        if (code) {
+             com_err(progname, code, _("while getting default ccache"));
+-            return 0;
++            goto cleanup;
+         }
+         deftype = krb5_cc_get_type(k5->ctx, defcache);
+-        if (k5->me != NULL && krb5_cc_support_switch(k5->ctx, deftype)) {
+-            /* Use an existing cache for the specified principal if we can. */
+-            code = krb5_cc_cache_match(k5->ctx, k5->me, &k5->out_cc);
+-            if (code != 0 && code != KRB5_CC_NOTFOUND) {
+-                com_err(progname, code, _("while searching for ccache for %s"),
+-                        opts->principal_name);
+-                krb5_cc_close(k5->ctx, defcache);
+-                return 0;
++        if (krb5_cc_get_principal(k5->ctx, defcache, &defcache_princ) != 0)
++            defcache_princ = NULL;
++    }
++
++    /* Choose a client principal name. */
++    if (opts->principal_name != NULL) {
++        /* Use the specified principal name. */
++        code = krb5_parse_name_flags(k5->ctx, opts->principal_name, flags,
++                                     &k5->me);
++        if (code) {
++            com_err(progname, code, _("when parsing name %s"),
++                    opts->principal_name);
++            goto cleanup;
++        }
++    } else if (opts->anonymous) {
++        /* Use the anonymous principal for the local realm. */
++        code = krb5_get_default_realm(k5->ctx, &defrealm);
++        if (code) {
++            com_err(progname, code, _("while getting default realm"));
++            goto cleanup;
++        }
++        code = krb5_build_principal_ext(k5->ctx, &k5->me,
++                                        strlen(defrealm), defrealm,
++                                        strlen(KRB5_WELLKNOWN_NAMESTR),
++                                        KRB5_WELLKNOWN_NAMESTR,
++                                        strlen(KRB5_ANONYMOUS_PRINCSTR),
++                                        KRB5_ANONYMOUS_PRINCSTR,
++                                        0);
++        krb5_free_default_realm(k5->ctx, defrealm);
++        if (code) {
++            com_err(progname, code, _("while building principal"));
++            goto cleanup;
++        }
++    } else if (opts->action == INIT_KT) {
++        /* Use the default host/service name. */
++        code = krb5_sname_to_principal(k5->ctx, NULL, NULL, KRB5_NT_SRV_HST,
++                                       &k5->me);
++        if (code) {
++            com_err(progname, code,
++                    _("when creating default server principal name"));
++            goto cleanup;
++        }
++        if (k5->me->realm.data[0] == 0) {
++            code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
++            if (code == 0) {
++                com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
++                        _("(principal %s)"), k5->name);
++            } else {
++                com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
++                        _("for local services"));
+             }
+-            if (code == KRB5_CC_NOTFOUND) {
+-                code = krb5_cc_new_unique(k5->ctx, deftype, NULL, &k5->out_cc);
+-                if (code) {
+-                    com_err(progname, code, _("while generating new ccache"));
+-                    krb5_cc_close(k5->ctx, defcache);
+-                    return 0;
+-                }
+-                if (opts->verbose) {
+-                    fprintf(stderr, _("Using new cache: %s\n"),
+-                            krb5_cc_get_name(k5->ctx, k5->out_cc));
+-                }
+-            } else if (opts->verbose) {
++            goto cleanup;
++        }
++    } else if (k5->out_cc != NULL) {
++        /* If the output ccache is initialized, use its principal. */
++        if (krb5_cc_get_principal(k5->ctx, k5->out_cc, &princ) == 0)
++            k5->me = princ;
++    } else if (defcache_princ != NULL) {
++        /* Use the default cache's principal, and use the default cache as the
++         * output cache. */
++        k5->out_cc = defcache;
++        defcache = NULL;
++        k5->me = defcache_princ;
++        defcache_princ = NULL;
++    }
++
++    /* If we still haven't chosen, use the local username. */
++    if (k5->me == NULL) {
++        name = get_name_from_os();
++        if (name == NULL) {
++            fprintf(stderr, _("Unable to identify user\n"));
++            goto cleanup;
++        }
++        code = krb5_parse_name_flags(k5->ctx, name, flags, &k5->me);
++        if (code) {
++            com_err(progname, code, _("when parsing name %s"),
++                    name);
++            goto cleanup;
++        }
++    }
++
++    if (k5->out_cc == NULL && krb5_cc_support_switch(k5->ctx, deftype)) {
++        /* Use an existing cache for the client principal if we can. */
++        code = krb5_cc_cache_match(k5->ctx, k5->me, &k5->out_cc);
++        if (code != 0 && code != KRB5_CC_NOTFOUND) {
++            com_err(progname, code, _("while searching for ccache for %s"),
++                    opts->principal_name);
++            goto cleanup;
++        }
++        if (code == 0) {
++            if (opts->verbose) {
+                 fprintf(stderr, _("Using existing cache: %s\n"),
+                         krb5_cc_get_name(k5->ctx, k5->out_cc));
+             }
+-            krb5_cc_close(k5->ctx, defcache);
+             k5->switch_to_cache = 1;
+-        } else {
+-            k5->out_cc = defcache;
++        } else if (defcache_princ != NULL) {
++            /* Create a new cache to avoid overwriting the initialized default
++             * cache. */
++            code = krb5_cc_new_unique(k5->ctx, deftype, NULL, &k5->out_cc);
++            if (code) {
++                com_err(progname, code, _("while generating new ccache"));
++                goto cleanup;
++            }
+             if (opts->verbose) {
+-                fprintf(stderr, _("Using default cache: %s\n"),
++                fprintf(stderr, _("Using new cache: %s\n"),
+                         krb5_cc_get_name(k5->ctx, k5->out_cc));
+             }
++            k5->switch_to_cache = 1;
++        }
++    }
++
++    /* Use the default cache if we haven't picked one yet. */
++    if (k5->out_cc == NULL) {
++        k5->out_cc = defcache;
++        defcache = NULL;
++        if (opts->verbose) {
++            fprintf(stderr, _("Using default cache: %s\n"),
++                    krb5_cc_get_name(k5->ctx, k5->out_cc));
+         }
+     }
++
+     if (opts->k5_in_cache_name) {
+         code = krb5_cc_resolve(k5->ctx, opts->k5_in_cache_name, &k5->in_cc);
+         if (code != 0) {
+             com_err(progname, code, _("resolving ccache %s"),
+                     opts->k5_in_cache_name);
+-            return 0;
++            goto cleanup;
+         }
+         if (opts->verbose) {
+             fprintf(stderr, _("Using specified input cache: %s\n"),
+@@ -551,80 +634,24 @@ k5_begin(opts, k5)
+         }
+     }
+ 
+-    if (!k5->me) {
+-        /* No principal name specified */
+-        if (opts->anonymous) {
+-            char *defrealm;
+-            code = krb5_get_default_realm(k5->ctx, &defrealm);
+-            if (code) {
+-                com_err(progname, code, _("while getting default realm"));
+-                return 0;
+-            }
+-            code = krb5_build_principal_ext(k5->ctx, &k5->me,
+-                                            strlen(defrealm), defrealm,
+-                                            strlen(KRB5_WELLKNOWN_NAMESTR),
+-                                            KRB5_WELLKNOWN_NAMESTR,
+-                                            strlen(KRB5_ANONYMOUS_PRINCSTR),
+-                                            KRB5_ANONYMOUS_PRINCSTR,
+-                                            0);
+-            krb5_free_default_realm(k5->ctx, defrealm);
+-            if (code) {
+-                com_err(progname, code, _("while building principal"));
+-                return 0;
+-            }
+-        } else {
+-            if (opts->action == INIT_KT) {
+-                /* Use the default host/service name */
+-                code = krb5_sname_to_principal(k5->ctx, NULL, NULL,
+-                                               KRB5_NT_SRV_HST, &k5->me);
+-                if (code) {
+-                    com_err(progname, code,
+-                            _("when creating default server principal name"));
+-                    return 0;
+-                }
+-                if (k5->me->realm.data[0] == 0) {
+-                    code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
+-                    if (code == 0) {
+-                        com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
+-                                _("(principal %s)"), k5->name);
+-                    } else {
+-                        com_err(progname, KRB5_ERR_HOST_REALM_UNKNOWN,
+-                                _("for local services"));
+-                    }
+-                    return 0;
+-                }
+-            } else {
+-                /* Get default principal from cache if one exists */
+-                code = krb5_cc_get_principal(k5->ctx, k5->out_cc,
+-                                             &k5->me);
+-                if (code) {
+-                    char *name = get_name_from_os();
+-                    if (!name) {
+-                        fprintf(stderr, _("Unable to identify user\n"));
+-                        return 0;
+-                    }
+-                    if ((code = krb5_parse_name_flags(k5->ctx, name,
+-                                                      flags, &k5->me))) {
+-                        com_err(progname, code, _("when parsing name %s"),
+-                                name);
+-                        return 0;
+-                    }
+-                }
+-            }
+-        }
+-    }
+ 
+     code = krb5_unparse_name(k5->ctx, k5->me, &k5->name);
+     if (code) {
+         com_err(progname, code, _("when unparsing name"));
+-        return 0;
++        goto cleanup;
+     }
+     if (opts->verbose)
+         fprintf(stderr, _("Using principal: %s\n"), k5->name);
+ 
+     opts->principal_name = k5->name;
+ 
+-    return 1;
++    success = 1;
++
++cleanup:
++    if (defcache != NULL)
++        krb5_cc_close(k5->ctx, defcache);
++    krb5_free_principal(k5->ctx, defcache_princ);
++    return success;
+ }
+ 
+ static void
diff --git a/krb5.spec b/krb5.spec
index 472e385..695fef8 100644
--- a/krb5.spec
+++ b/krb5.spec
@@ -35,13 +35,13 @@
 %endif
 %if 0%{?fedora} >= 20 || 0%{?rhel} > 6
 %global configure_default_ccache_name 1
-%global configured_default_ccache_name DIR:/run/user/%%{uid}/krb5cc
+%global configured_default_ccache_name KEYRING:persistent:%%{uid}
 %endif
 
 Summary: The Kerberos network authentication system
 Name: krb5
 Version: 1.11.3
-Release: 20%{?dist}
+Release: 21%{?dist}
 # Maybe we should explode from the now-available-to-everybody tarball instead?
 # http://web.mit.edu/kerberos/dist/krb5/1.11/krb5-1.11.3-signed.tar
 Source0: krb5-%{version}.tar.gz
@@ -113,6 +113,7 @@ Patch202: krb5-1.11.2-otp.patch
 
 # Patches for kernel-persistent-keyring support (backport)
 Patch301: persistent_keyring.patch
+Patch302: krb5-master-kinit-cccol.patch
 
 License: MIT
 URL: http://web.mit.edu/kerberos/www/
@@ -307,6 +308,9 @@ certificate.
 %setup -q -n %{name}-%{version} -a 3 -a 100
 ln -s NOTICE LICENSE
 
+%patch301 -p1 -b .persistent-keyring
+%patch302 -p1 -b .kinit-cccol
+
 %patch60 -p1 -b .pam
 
 %patch63 -p1 -b .selinux-label
@@ -349,8 +353,6 @@ ln -s NOTICE LICENSE
 %patch201 -p1 -b .keycheck
 %patch202 -p1 -b .otp
 
-%patch301 -p1 -b .persistent-keyring
-
 # Take the execute bit off of documentation.
 chmod -x doc/krb5-protocol/*.txt
 
@@ -653,7 +655,7 @@ if test -z "$tmpfile" ; then
 fi
 # Remove the default value we previously set.  Be very exact about it.
 if grep -q default_ccache_name /etc/krb5.conf ; then
-	sed -r '/^ default_ccache_name = KEYRING:persistent:%%\{uid\}$/d' /etc/krb5.conf > "$tmpfile"
+	sed -r '|^ default_ccache_name = DIR:/run/user/%%\{uid\}/krb5cc$|d' /etc/krb5.conf > "$tmpfile"
 	if test -s "$tmpfile" ; then
 		if touch -r /etc/krb5.conf "$tmpfile" ; then
 			cat "$tmpfile" > /etc/krb5.conf
@@ -992,6 +994,11 @@ exit 0
 %{_sbindir}/uuserver
 
 %changelog
+* Wed Oct  2 2013 Nalin Dahyabhai <nalin at redhat.com> - 1.11.3-21
+- switch to the version of persistent-keyring that was just merged to
+  master (RT#7711), along with related changes to kinit (RT#7689)
+- go back to setting default_ccache_name to a KEYRING type
+
 * Mon Sep 30 2013 Nalin Dahyabhai <nalin at redhat.com> - 1.11.3-20
 - pull up fix for not calling a kdb plugin's check-transited-path
   method before calling the library's default version, which only knows
diff --git a/persistent_keyring.patch b/persistent_keyring.patch
index 35d70ec..863774c 100644
--- a/persistent_keyring.patch
+++ b/persistent_keyring.patch
@@ -1,5 +1,8 @@
+Pared down from the git commits, with a local copy of k5memdup0() added in
+to cc_keyring, and a wrapper 'run' in to k5test.py.
+
 diff --git a/src/aclocal.m4 b/src/aclocal.m4
-index 2c17e46..abb3eb5 100644
+index 2c17e46..7be77c2 100644
 --- a/src/aclocal.m4
 +++ b/src/aclocal.m4
 @@ -89,6 +89,7 @@ KRB5_AC_INITFINI
@@ -10,53 +13,110 @@ index 2c17e46..abb3eb5 100644
  ])dnl
  
  dnl Maintainer mode, akin to what automake provides, 'cept we don't
-@@ -1664,6 +1665,15 @@ AC_DEFUN(KRB5_AC_KEYRING_CCACHE,[
+@@ -1664,3 +1659,12 @@ AC_DEFUN(KRB5_AC_KEYRING_CCACHE,[
        ]))
  ])dnl
  dnl
 +dnl If libkeyutils supports persistent keyrings, use them
 +AC_DEFUN(KRB5_AC_PERSISTENT_KEYRING,[
 +  AC_CHECK_HEADERS([keyutils.h],
-+    AC_CHECK_LIB(keyutils, keyctl_get_persistent, 
-+      [dnl Pre-reqs were found
-+       AC_DEFINE(HAVE_PERSISTENT_KEYRING, 1, [Define if persistent keyrings are supported])
++    AC_CHECK_LIB(keyutils, keyctl_get_persistent,
++      [AC_DEFINE(HAVE_PERSISTENT_KEYRING, 1,
++                 [Define if persistent keyrings are supported])
 +      ]))
 +])dnl
 +dnl
- dnl
- dnl Use PAM instead of local crypt() compare for checking local passwords,
- dnl and perform PAM account, session management, and password-changing where
+diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et
+index 98374ed..071b7f2 100644
+--- a/src/lib/krb5/error_tables/k5e1_err.et
++++ b/src/lib/krb5/error_tables/k5e1_err.et
+@@ -35,4 +35,7 @@ error_code KRB5_PLUGIN_BAD_MODULE_SPEC, "Invalid module specifier"
+ error_code KRB5_PLUGIN_NAME_NOTFOUND, "Plugin module name not found"
+ error_code KRB5KDC_ERR_DISCARD, "The KDC should discard this request"
+ error_code KRB5_DCC_CANNOT_CREATE, "Can't create new subsidiary cache"
++error_code KRB5_KCC_INVALID_ANCHOR, "Invalid keyring anchor name"
++error_code KRB5_KCC_UNKNOWN_VERSION, "Unknown keyring collection version"
++error_code KRB5_KCC_INVALID_UID, "Invalid UID in persistent keyring name"
+ end
 diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c
-index fd1bcec..f29c603 100644
+index fd1bcec..795ccd6 100644
 --- a/src/lib/krb5/ccache/cc_keyring.c
 +++ b/src/lib/krb5/ccache/cc_keyring.c
-@@ -66,7 +66,12 @@
-  *        not other keyrings
-  *      - Each Kerberos ticket will have its own key within the ccache keyring
-  *      - The principal information for the ccache is stored in a
+@@ -56,17 +56,42 @@
+  */
+ 
+ /*
+- * Implementation of a credentials cache stored in the Linux keyring facility
++ * This file implements a collection-enabled credential cache type where the
++ * credentials are stored in the Linux keyring facility.
+  *
+- * Some assumptions:
++ * A residual of this type can have three forms:
++ *    anchor:collection:subsidiary
++ *    anchor:collection
++ *    collection
+  *
+- *      - A credentials cache "file" == a keyring with separate keys
+- *        for the information in the ccache (see below)
+- *      - A credentials cache keyring will contain only keys,
+- *        not other keyrings
+- *      - Each Kerberos ticket will have its own key within the ccache keyring
+- *      - The principal information for the ccache is stored in a
 - *        special key, which is not counted in the 'numkeys' count
-+ *        special key
++ * The anchor name is "process", "thread", or "legacy" and determines where we
++ * search for keyring collections.  In the third form, the anchor name is
++ * presumed to be "legacy".  The anchor keyring for legacy caches is the
++ * session keyring.
++ *
++ * If the subsidiary name is present, the residual identifies a single cache
++ * within a collection.  Otherwise, the residual identifies the collection
++ * itself.  When a residual identifying a collection is resolved, the
++ * collection's primary key is looked up (or initialized, using the collection
++ * name as the subsidiary name), and the resulting cache's name will use the
++ * first name form and will identify the primary cache.
++ *
++ * Keyring collections are named "_krb_<collection>" and are linked from the
++ * anchor keyring.  The keys within a keyring collection are links to cache
++ * keyrings, plus a link to one user key named "krb_ccache:primary" which
++ * contains a serialized representation of the collection version (currently 1)
++ * and the primary name of the collection.
 + *
-+ * For collections:
-+ *      - A collection is a keyring containing multiple ccache keyrings
-+ *      - The primary ccache in use is referenced by a special key in the
-+ *        collection keyring (see below)
++ * Cache keyrings contain one user key per credential which contains a
++ * serialized representation of the credential.  There is also one user key
++ * named "__krb5_princ__" which contains a serialized representation of the
++ * cache's default principal.
++ *
++ * If the anchor name is "legacy", then the initial primary cache (the one
++ * named with the collection name) is also linked to the session keyring, and
++ * we look for a cache in that location when initializing the collection.  This
++ * extra link allows that cache to be visible to old versions of the KEYRING
++ * cache type, and allows us to see caches created by that code.
   */
  
  #include "cc-int.h"
-@@ -101,9 +106,10 @@ debug_print(char *fmt, ...)
+@@ -101,7 +126,20 @@ debug_print(char *fmt, ...)
  #endif
  
  /*
 - * We always use "user" key type
-+ * We use the "user" key type for labels and "big_key" for tickets
++ * We try to use the big_key key type for credentials except in legacy caches.
++ * We fall back to the user key type if the kernel does not support big_key.
++ * If the library doesn't support keyctl_get_persistent(), we don't even try
++ * big_key since the two features were added at the same time.
++ */
++#ifdef HAVE_PERSISTENT_KEYRING
++#define KRCC_CRED_KEY_TYPE "big_key"
++#else
++#define KRCC_CRED_KEY_TYPE "user"
++#endif
++
++/*
++ * We use the "user" key type for collection primary names, for cache principal
++ * names, and for credentials in legacy caches.
   */
  #define KRCC_KEY_TYPE_USER "user"
-+#define KRCC_KEY_TYPE_BIG_KEY "big_key"
  
- /*
-  * We create ccaches as separate keyrings
-@@ -117,20 +123,6 @@ debug_print(char *fmt, ...)
+@@ -117,20 +155,6 @@ debug_print(char *fmt, ...)
  #define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"
  
  /*
@@ -77,58 +137,49 @@ index fd1bcec..f29c603 100644
   * Special name for the key to communicate the name(s)
   * of credentials caches to be used for requests.
   * This should currently contain a single name, but
-@@ -139,26 +131,64 @@ debug_print(char *fmt, ...)
+@@ -139,26 +163,55 @@ debug_print(char *fmt, ...)
   */
  #define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"
  
 +/*
-+ * Special name that identifies the key that hold the reference to the
-+ * current primary ccache in the collection
++ * This name identifies the key containing the name of the current primary
++ * cache within a collection.
 + */
 +#define KRCC_COLLECTION_PRIMARY "krb_ccache:primary"
 +
 +/*
++ * If the library context does not specify a keyring collection, unique ccaches
++ * will be created within this collection.
++ */
++#define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__"
++
++/*
++ * Collection keyring names begin with this prefix.  We use a prefix so that a
++ * cache keyring with the collection name itself can be linked directly into
++ * the anchor, for legacy session keyring compatibility.
++ */
++#define KRCC_CCCOL_PREFIX "_krb_"
++
++/*
++ * For the "persistent" anchor type, we look up or create this fixed keyring
++ * name within the per-UID persistent keyring.
++ */
++#define KRCC_PERSISTENT_KEYRING_NAME "_krb"
++
++/*
 + * Keyring name prefix and length of random name part
 + */
 +#define KRCC_NAME_PREFIX "krb_ccache_"
 +#define KRCC_NAME_RAND_CHARS 8
 +
 +#define KRCC_COLLECTION_VERSION 1
-+#define KRCC_CCCOL_PREFIX "_krb_"
-+
-+#define KRCC_PERSIST_PREFIX "persistent:"
-+#define KRCC_USER_PREFIX "user:"
-+#define KRCC_SESSION_PREFIX "session:"
-+#define KRCC_PROCESS_PREFIX "process:"
-+#define KRCC_THREAD_PREFIX "thread:"
-+
-+#define KRCC_HAS_PERSIST_PREFIX(r) \
-+    (strncmp(r, KRCC_PERSIST_PREFIX, sizeof(KRCC_PERSIST_PREFIX)-1) == 0)
-+#define KRCC_HAS_USER_PREFIX(r) \
-+    (strncmp(r, KRCC_USER_PREFIX, sizeof(KRCC_USER_PREFIX)-1) == 0)
-+#define KRCC_HAS_SESSION_PREFIX(r) \
-+    (strncmp(r, KRCC_SESSION_PREFIX, sizeof(KRCC_SESSION_PREFIX)-1) == 0)
-+#define KRCC_HAS_PROCESS_PREFIX(r) \
-+    (strncmp(r, KRCC_PROCESS_PREFIX, sizeof(KRCC_PROCESS_PREFIX)-1) == 0)
-+#define KRCC_HAS_THREAD_PREFIX(r) \
-+    (strncmp(r, KRCC_THREAD_PREFIX, sizeof(KRCC_THREAD_PREFIX)-1) == 0)
-+
-+#define KRCC_IS_LEGACY_SESSION(r) \
-+    (!KRCC_HAS_PERSIST_PREFIX(r) && \
-+     !KRCC_HAS_USER_PREFIX(r) && \
-+     !KRCC_HAS_SESSION_PREFIX(r) && \
-+     !KRCC_HAS_PROCESS_PREFIX(r) && \
-+     !KRCC_HAS_THREAD_PREFIX(r))
-+
-+enum krcc_keyring_type {
-+    KRCC_LEGACY_SESSION = 0,
-+    KRCC_PERSIST,
-+    KRCC_USER,
-+    KRCC_SESSION,
-+    KRCC_PROCESS,
-+    KRCC_THREAD
-+};
 +
++#define KRCC_PERSISTENT_ANCHOR "persistent"
++#define KRCC_PROCESS_ANCHOR "process"
++#define KRCC_THREAD_ANCHOR "thread"
++#define KRCC_SESSION_ANCHOR "session"
++#define KRCC_USER_ANCHOR "user"
++#define KRCC_LEGACY_ANCHOR "legacy"
 +
  #define KRB5_OK 0
  
@@ -154,18 +205,32 @@ index fd1bcec..f29c603 100644
  typedef struct _krb5_krcc_cursor
  {
      int     numkeys;
-@@ -180,9 +210,8 @@ typedef struct _krb5_krcc_data
-     key_serial_t parent_id;     /* parent keyring of this ccache keyring */
-     key_serial_t ring_id;       /* keyring representing ccache */
+@@ -169,7 +222,7 @@ typedef struct _krb5_krcc_cursor
+ 
+ /*
+  * This represents a credentials cache "file"
+- * where ring_id is the keyring serial number for
++ * where cache_id is the keyring serial number for
+  * this credentials cache "file".  Each key
+  * in the keyring contains a separate key.
+  */
+@@ -177,12 +230,11 @@ typedef struct _krb5_krcc_data
+ {
+     char   *name;               /* Name for this credentials cache */
+     k5_cc_mutex lock;           /* synchronization */
+-    key_serial_t parent_id;     /* parent keyring of this ccache keyring */
+-    key_serial_t ring_id;       /* keyring representing ccache */
++    key_serial_t collection_id; /* collection containing this cache keyring */
++    key_serial_t cache_id;      /* keyring representing ccache */
      key_serial_t princ_id;      /* key holding principal info */
 -    int     numkeys;            /* # of keys in this ring
 -                                 * (does NOT include principal info) */
      krb5_timestamp changetime;
-+    const char *key_type;
++    krb5_boolean is_legacy_type;
  } krb5_krcc_data;
  
  /* Passed internally to assure we don't go past the bounds of our buffer */
-@@ -190,6 +219,7 @@ typedef struct _krb5_krcc_buffer_cursor
+@@ -190,6 +242,7 @@ typedef struct _krb5_krcc_buffer_cursor
  {
      char   *bpp;
      char   *endp;
@@ -173,7 +238,7 @@ index fd1bcec..f29c603 100644
  } krb5_krcc_bc;
  
  /* Global mutex */
-@@ -258,6 +288,18 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_lock
+@@ -258,6 +311,29 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_lock
  static krb5_error_code KRB5_CALLCONV krb5_krcc_unlock
  (krb5_context context, krb5_ccache id);
  
@@ -189,42 +254,38 @@ index fd1bcec..f29c603 100644
 +static krb5_error_code KRB5_CALLCONV krb5_krcc_switch_to
 +(krb5_context context, krb5_ccache cache);
 +
++/* Like k5memdup, but add a final null byte. */
++static inline void *
++k5memdup0(const void *in, size_t len, krb5_error_code *code)
++{
++    void *ptr = k5alloc(len + 1, code);
++
++    if (ptr != NULL && len > 0)
++        memcpy(ptr, in, len);
++    return ptr;
++}
++
  /*
   * Internal utility functions
   */
-@@ -275,103 +317,108 @@ static krb5_error_code krb5_krcc_save_principal
+@@ -266,8 +331,9 @@ static krb5_error_code krb5_krcc_clearcache
+ (krb5_context context, krb5_ccache id);
+ 
+ static krb5_error_code krb5_krcc_new_data
+-(const char *, key_serial_t ring, key_serial_t parent_ring,
+- krb5_krcc_data **);
++(const char *anchor_name, const char *collection_name,
++ const char *subsidiary_name, key_serial_t cache_id,
++ key_serial_t collection_id, krb5_krcc_data **datapp);
+ 
+ static krb5_error_code krb5_krcc_save_principal
+ (krb5_context context, krb5_ccache id, krb5_principal princ);
+@@ -275,100 +341,480 @@ static krb5_error_code krb5_krcc_save_principal
  static krb5_error_code krb5_krcc_retrieve_principal
  (krb5_context context, krb5_ccache id, krb5_principal * princ);
  
 -static int krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p);
-+static krb5_error_code krb5_krcc_resolve_internal
-+(key_serial_t ring_id, key_serial_t ccache_id, const char *residual,
-+ krb5_ccache *cache_out);
-+
-+static krb5_error_code krb5_krcc_get_keyring
-+(krb5_context context, const char *full_residual, char **name,
-+ krb5_boolean *subsidiary, key_serial_t *id);
-+
-+static krb5_error_code krb5_krcc_default_keyring
-+(krb5_context context, krb5_boolean *subsidiary, char **name,
-+ key_serial_t *id);
-+
-+static const char * krb5_krcc_get_ring_name
-+(const char *residual);
-+
-+static char * krb5_krcc_new_subsidiary
-+(const char *residual, const char *ring_name);
-+
-+static krb5_error_code krb5_krcc_unique_keyring
-+(krb5_context context, key_serial_t parent_id, char **retname,
-+ key_serial_t *keyring_id);
-+
-+static krb5_error_code krb5_krcc_set_primary
-+(krb5_context context, const char *name, key_serial_t ring_id);
-+
-+static krb5_error_code krb5_krcc_get_primary
-+(krb5_context context, key_serial_t ring_id, char **name);
- 
+-
  /* Routines to parse a key from a keyring into a cred structure */
  static krb5_error_code krb5_krcc_parse
 -(krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len,
@@ -342,34 +403,425 @@ index fd1bcec..f29c603 100644
  /*
 - * Determine how many keys exist in a ccache keyring.
 - * Subtracts out the "hidden" key holding the principal information.
-- */
++ * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back
++ * to the user keyring if uid matches the current effective uid.
++ */
++
++static key_serial_t
++get_persistent_fallback(uid_t uid)
++{
++    return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
++}
++
++#ifdef HAVE_PERSISTENT_KEYRING
++#define GET_PERSISTENT get_persistent_real
++static key_serial_t
++get_persistent_real(uid_t uid)
++{
++    key_serial_t key;
++
++    key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
++    return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) :
++        key;
++}
++#else
++#define GET_PERSISTENT get_persistent_fallback
++#endif
++
++/*
++ * Find or create a keyring within parent with the given name.  If possess is
++ * nonzero, also make sure the key is linked from possess.  This is necessary
++ * to ensure that we have possession rights on the key when the parent is the
++ * user or persistent keyring.
++ */
++static krb5_error_code
++find_or_create_keyring(key_serial_t parent, key_serial_t possess,
++                       const char *name, key_serial_t *key_out)
++{
++    key_serial_t key;
++
++    *key_out = -1;
++    key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
++    if (key == -1) {
++        if (possess != 0) {
++            key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
++            if (key == -1)
++                return errno;
++            if (keyctl_link(key, parent) == -1)
++                return errno;
++        } else {
++            key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
++            if (key == -1)
++                return errno;
++        }
++    }
++    *key_out = key;
++    return 0;
++}
++
++/* Parse a residual name into an anchor name, a collection name, and possibly a
++ * subsidiary name. */
++static krb5_error_code
++parse_residual(const char *residual, char **anchor_name_out,
++               char **collection_name_out, char **subsidiary_name_out)
++{
++    krb5_error_code ret;
++    char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
++    const char *sep;
++
++    *anchor_name_out = 0;
++    *collection_name_out = NULL;
++    *subsidiary_name_out = NULL;
++
++    /* Parse out the anchor name.  Use the legacy anchor if not present. */
++    sep = strchr(residual, ':');
++    if (sep == NULL) {
++        anchor_name = strdup(KRCC_LEGACY_ANCHOR);
++        if (anchor_name == NULL)
++            goto oom;
++    } else {
++        anchor_name = k5memdup0(residual, sep - residual, &ret);
++        if (anchor_name == NULL)
++            goto oom;
++        residual = sep + 1;
++    }
++
++    /* Parse out the collection and subsidiary name. */
++    sep = strchr(residual, ':');
++    if (sep == NULL) {
++        collection_name = strdup(residual);
++        if (collection_name == NULL)
++            goto oom;
++        subsidiary_name = NULL;
++    } else {
++        collection_name = k5memdup0(residual, sep - residual, &ret);
++        if (collection_name == NULL)
++            goto oom;
++        subsidiary_name = strdup(sep + 1);
++        if (subsidiary_name == NULL)
++            goto oom;
++    }
++
++    *anchor_name_out = anchor_name;
++    *collection_name_out = collection_name;
++    *subsidiary_name_out = subsidiary_name;
++    return 0;
++
++oom:
++    free(anchor_name);
++    free(collection_name);
++    free(subsidiary_name);
++    return ENOMEM;
++}
++
++/*
++ * Return true if residual identifies a subsidiary cache which should be linked
++ * into the anchor so it can be visible to old code.  This is the case if the
++ * residual has the legacy anchor and the subsidiary name matches the
++ * collection name.
++ */
++static krb5_boolean
++is_legacy_cache_name(const char *residual)
++{
++    const char *sep, *aname, *cname, *sname;
++    size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
++
++    /* Get pointers to the anchor, collection, and subsidiary names. */
++    aname = residual;
++    sep = strchr(residual, ':');
++    if (sep == NULL)
++        return FALSE;
++    alen = sep - aname;
++    cname = sep + 1;
++    sep = strchr(cname, ':');
++    if (sep == NULL)
++        return FALSE;
++    clen = sep - cname;
++    sname = sep + 1;
++
++    return alen == legacy_len && clen == strlen(sname) &&
++        strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
++        strncmp(cname, sname, clen) == 0;
++}
++
++/* If the default cache name for context is a KEYRING cache, parse its residual
++ * string.  Otherwise set all outputs to NULL. */
++static krb5_error_code
++get_default(krb5_context context, char **anchor_name_out,
++            char **collection_name_out, char **subsidiary_name_out)
++{
++    const char *defname;
++
++    *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
++    defname = krb5_cc_default_name(context);
++    if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
++        return 0;
++    return parse_residual(defname + 8, anchor_name_out, collection_name_out,
++                          subsidiary_name_out);
++}
++
++/* Create a residual identifying a subsidiary cache. */
++static krb5_error_code
++make_subsidiary_residual(const char *anchor_name, const char *collection_name,
++                         const char *subsidiary_name, char **residual_out)
++{
++    if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
++                 subsidiary_name) < 0) {
++        *residual_out = NULL;
++        return ENOMEM;
++    }
++    return 0;
++}
++
++/* Retrieve or create a keyring for collection_name within the anchor, and set
++ * *collection_id_out to its serial number. */
++static krb5_error_code
++get_collection(const char *anchor_name, const char *collection_name,
++               key_serial_t *collection_id_out)
++{
++    krb5_error_code ret;
++    key_serial_t persistent_id, anchor_id, possess_id = 0;
++    char *ckname;
++    long uidnum;
++
++    *collection_id_out = 0;
++
++    if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
++        /*
++         * The collection name is a uid (or empty for the current effective
++         * uid), and we look up a fixed keyring name within the persistent
++         * keyring for that uid.  We link it to the process keyring to ensure
++         * that we have possession rights on the collection key.
++         */
++        if (*collection_name != '\0') {
++            errno = 0;
++            uidnum = strtol(collection_name, NULL, 10);
++            if (errno)
++                return KRB5_KCC_INVALID_UID;
++        } else {
++            uidnum = geteuid();
++        }
++        persistent_id = GET_PERSISTENT(uidnum);
++        if (persistent_id == -1)
++            return KRB5_KCC_INVALID_UID;
++        return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
++                                      KRCC_PERSISTENT_KEYRING_NAME,
++                                      collection_id_out);
++    }
++
++    if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
++        anchor_id = KEY_SPEC_PROCESS_KEYRING;
++    } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
++        anchor_id = KEY_SPEC_THREAD_KEYRING;
++    } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
++        anchor_id = KEY_SPEC_SESSION_KEYRING;
++    } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
++        /* The user keyring does not confer possession, so we need to link the
++         * collection to the process keyring to maintain possession rights. */
++        anchor_id = KEY_SPEC_USER_KEYRING;
++        possess_id = KEY_SPEC_PROCESS_KEYRING;
++    } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
++        anchor_id = KEY_SPEC_SESSION_KEYRING;
++    } else {
++        return KRB5_KCC_INVALID_ANCHOR;
++    }
++
++    /* Look up the collection keyring name within the anchor keyring. */
++    if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
++        return ENOMEM;
++    ret = find_or_create_keyring(anchor_id, possess_id, ckname,
++                                 collection_id_out);
++    free(ckname);
++    return ret;
++}
++
++/* Store subsidiary_name into the primary index key for collection_id. */
++static krb5_error_code
++set_primary_name(krb5_context context, key_serial_t collection_id,
++                 const char *subsidiary_name)
++{
++    krb5_error_code ret;
++    key_serial_t key;
++    void *payload = NULL;
++    int payloadlen;
++
++    ret = krb5_krcc_unparse_index(context, KRCC_COLLECTION_VERSION,
++                                  subsidiary_name, &payload, &payloadlen);
++    if (ret)
++        return ret;
++    key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
++                  payload, payloadlen, collection_id);
++    free(payload);
++    return (key == -1) ? errno : 0;
++}
++
++/*
++ * Get or initialize the primary name within collection_id and set
++ * *subsidiary_out to its value.  If initializing a legacy collection, look
++ * for a legacy cache and add it to the collection.
++ */
++static krb5_error_code
++get_primary_name(krb5_context context, const char *anchor_name,
++                 const char *collection_name, key_serial_t collection_id,
++                 char **subsidiary_out)
++{
++    krb5_error_code ret;
++    key_serial_t primary_id, legacy;
++    void *payload = NULL;
++    int payloadlen;
++    krb5_int32 version;
++    char *subsidiary_name = NULL;
++
++    *subsidiary_out = NULL;
++
++    primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
++                               KRCC_COLLECTION_PRIMARY, 0);
++    if (primary_id == -1) {
++        /* Initialize the primary key using the collection name.  We can't name
++         * a key with the empty string, so map that to an arbitrary string. */
++        subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
++                                 collection_name);
++        if (subsidiary_name == NULL) {
++            ret = ENOMEM;
++            goto cleanup;
++        }
++        ret = set_primary_name(context, collection_id, subsidiary_name);
++        if (ret)
++            goto cleanup;
++
++        if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
++            /* Look for a cache created by old code.  If we find one, add it to
++             * the collection. */
++            legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
++                                   KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
++            if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
++                ret = errno;
++                goto cleanup;
++            }
++        }
++    } else {
++        /* Read, parse, and free the primary key's payload. */
++        payloadlen = keyctl_read_alloc(primary_id, &payload);
++        if (payloadlen == -1) {
++            ret = errno;
++            goto cleanup;
++        }
++        ret = krb5_krcc_parse_index(context, &version, &subsidiary_name,
++                                    payload, payloadlen);
++        if (ret)
++            goto cleanup;
++
++        if (version != KRCC_COLLECTION_VERSION) {
++            ret = KRB5_KCC_UNKNOWN_VERSION;
++            goto cleanup;
++        }
++    }
++
++    *subsidiary_out = subsidiary_name;
++    subsidiary_name = NULL;
++
++cleanup:
++    free(payload);
++    free(subsidiary_name);
++    return ret;
++}
++
++/*
++ * Create a keyring with a unique random name within collection_id.  Set
++ * *subsidiary to its name and *cache_id_out to its key serial number.
+  */
 -static int KRB5_CALLCONV
 -krb5_krcc_getkeycount(key_serial_t cred_ring)
--{
++static krb5_error_code
++unique_keyring(krb5_context context, key_serial_t collection_id,
++               char **subsidiary_out, key_serial_t *cache_id_out)
+ {
 -    int res, nkeys;
--
++    key_serial_t key;
++    krb5_error_code ret;
++    char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
++    int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
++    int tries;
++
++    *subsidiary_out = NULL;
++    *cache_id_out = 0;
++
++    memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
++    k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
++
++    /* Loop until we successfully create a new ccache keyring with
++     * a unique name, or we get an error. Limit to 100 tries. */
++    tries = 100;
++    while (tries-- > 0) {
++        ret = krb5int_random_string(context, uniquename + prefixlen,
++                                    KRCC_NAME_RAND_CHARS);
++        if (ret)
++            goto cleanup;
++
++        key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
++                            0);
++        if (key < 0) {
++            /* Name does not already exist.  Create it to reserve the name. */
++            key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0,
++                          collection_id);
++            if (key < 0) {
++                ret = errno;
++                goto cleanup;
++            }
++            break;
++        }
++    }
+ 
 -    res = keyctl_read(cred_ring, NULL, 0);
 -    if (res > 0)
 -        nkeys = (res / sizeof(key_serial_t)) - 1;
 -    else
 -        nkeys = 0;
 -    return(nkeys);
--}
--
--/*
-  * Modifies:
-  * id
-  *
-@@ -388,24 +435,81 @@ static krb5_error_code KRB5_CALLCONV
++    if (tries <= 0) {
++        ret = KRB5_CC_BADNAME;
++        goto cleanup;
++    }
++
++    *subsidiary_out = strdup(uniquename);
++    if (*subsidiary_out == NULL) {
++        ret = ENOMEM;
++        goto cleanup;
++    }
++    *cache_id_out = key;
++    ret = KRB5_OK;
++cleanup:
++    k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
++    return ret;
++}
++
++static krb5_error_code
++add_cred_key(const char *name, const void *payload, size_t plen,
++             key_serial_t cache_id, krb5_boolean legacy_type)
++{
++    key_serial_t key;
++
++    if (!legacy_type) {
++        /* Try the preferred cred key type; fall back if no kernel support. */
++        key = add_key(KRCC_CRED_KEY_TYPE, name, payload, plen, cache_id);
++        if (key != -1)
++            return 0;
++        else if (errno != EINVAL && errno != ENODEV)
++            return errno;
++    }
++    /* Use the user key type. */
++    key = add_key(KRCC_KEY_TYPE_USER, name, payload, plen, cache_id);
++    return (key == -1) ? errno : 0;
+ }
+ 
+ /*
+@@ -388,24 +834,40 @@ static krb5_error_code KRB5_CALLCONV
  krb5_krcc_initialize(krb5_context context, krb5_ccache id,
                       krb5_principal princ)
  {
 +    krb5_krcc_data *data = (krb5_krcc_data *)id->data;
      krb5_error_code kret;
-+    char *new_name = NULL;
-+    char *residual;
-+    const char *name;
-+    key_serial_t key;
++    const char *cache_name, *p;
  
      DEBUG_PRINT(("krb5_krcc_initialize: entered\n"));
  
@@ -382,58 +834,21 @@ index fd1bcec..f29c603 100644
      if (kret != KRB5_OK)
          goto out;
  
-+    if (data->ring_id == 0) {
-+        /* deferred initialization */
-+        name = krb5_krcc_get_ring_name(data->name);
-+
-+        if ((name[0] == '\0') || (strcmp(name, "tkt") == 0)) {
-+            /* empty initial primary key, geneate new unique one */
-+            kret = krb5_krcc_unique_keyring(context, data->parent_id,
-+                                            &new_name, &key);
-+            if (kret)
-+                goto out;
-+
-+            kret = krb5_krcc_set_primary(context, new_name, data->parent_id);
-+            if (kret)
-+                goto out;
-+
-+            residual = krb5_krcc_new_subsidiary(data->name, new_name);
-+            if (!residual) {
-+                kret = ENOMEM;
-+                goto out;
-+            }
-+            free(data->name);
-+            data->name = residual;
-+            name = new_name;
-+        } else {
-+            /* either dangling primary key or legacy session cache */
-+            key = keyctl_search(data->parent_id, KRCC_KEY_TYPE_KEYRING,
-+                                name, 0);
-+            if (key == -1) {
-+                key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0,
-+                              data->parent_id);
-+                if (key == -1) {
-+                    kret = errno;
-+                    DEBUG_PRINT(("krb5_krcc_initialize: Error adding new "
-+                                 "keyring '%s': %s\n", name, strerror(kret)));
-+                    goto out;
-+                }
-+                DEBUG_PRINT(("krb5_krcc_initialize: new keyring '%s', key %d, "
-+                             "added to keyring %d\n", name, key, ring_id));
-+            }
-+        }
-+        data->ring_id = key;
++    if (!data->cache_id) {
++        /* The key didn't exist at resolve time.  Check again and create the
++         * key if it still isn't there. */
++        p = strrchr(data->name, ':');
++        cache_name = (p != NULL) ? p + 1 : data->name;
++        kret = find_or_create_keyring(data->collection_id, 0, cache_name,
++                                      &data->cache_id);
++        if (kret)
++            goto out;
 +    }
 +
-+    /* If this is a legacy session make sure to link the ccache keyring
-+     * directly in the session keyring too */
-+    if (KRCC_IS_LEGACY_SESSION(data->name)) {
-+        /* legacy session */
-+        if (keyctl_link(data->ring_id, KEY_SPEC_SESSION_KEYRING) == -1) {
-+            DEBUG_PRINT(("krb5_krcc_initialize: failed to link ccache "
-+                         "keyring to session keyring %d\n", errno));
-+        }
-+    }
++    /* If this is the legacy cache in a legacy session collection, link it
++     * directly to the session keyring so that old code can see it. */
++    if (is_legacy_cache_name(data->name))
++        (void)keyctl_link(data->cache_id, KEY_SPEC_SESSION_KEYRING);
 +
      kret = krb5_krcc_save_principal(context, id, princ);
      if (kret == KRB5_OK)
@@ -442,24 +857,23 @@ index fd1bcec..f29c603 100644
  out:
 -    k5_cc_mutex_unlock(context, &((krb5_krcc_data *) id->data)->lock);
 +    k5_cc_mutex_unlock(context, &data->lock);
-+    free(new_name);
      return kret;
  }
  
-@@ -460,14 +564,14 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id)
+@@ -460,14 +922,14 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id)
  
      d = (krb5_krcc_data *) id->data;
  
 -    DEBUG_PRINT(("krb5_krcc_clearcache: ring_id %d, princ_id %d, "
 -                 "numkeys is %d\n", d->ring_id, d->princ_id, d->numkeys));
-+    DEBUG_PRINT(("krb5_krcc_clearcache: ring_id %d, princ_id %d\n",
-+                 d->ring_id, d->princ_id));
++    DEBUG_PRINT(("krb5_krcc_clearcache: cache_id %d, princ_id %d\n",
++                 d->cache_id, d->princ_id));
  
 -    res = keyctl_clear(d->ring_id);
 -    if (res != 0) {
 -        return errno;
-+    if (d->ring_id) {
-+        res = keyctl_clear(d->ring_id);
++    if (d->cache_id) {
++        res = keyctl_clear(d->cache_id);
 +        if (res != 0)
 +            return errno;
      }
@@ -467,7 +881,7 @@ index fd1bcec..f29c603 100644
      d->princ_id = 0;
      krb5_krcc_update_change_time(d);
  
-@@ -484,7 +588,7 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id)
+@@ -484,7 +946,7 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id)
  static krb5_error_code KRB5_CALLCONV
  krb5_krcc_destroy(krb5_context context, krb5_ccache id)
  {
@@ -476,7 +890,7 @@ index fd1bcec..f29c603 100644
      krb5_krcc_data *d;
      int     res;
  
-@@ -492,20 +596,20 @@ krb5_krcc_destroy(krb5_context context, krb5_ccache id)
+@@ -492,30 +954,67 @@ krb5_krcc_destroy(krb5_context context, krb5_ccache id)
  
      d = (krb5_krcc_data *) id->data;
  
@@ -486,202 +900,120 @@ index fd1bcec..f29c603 100644
 +    k5_cc_mutex_lock(context, &d->lock);
  
      krb5_krcc_clearcache(context, id);
-     free(d->name);
-     res = keyctl_unlink(d->ring_id, d->parent_id);
+-    free(d->name);
+-    res = keyctl_unlink(d->ring_id, d->parent_id);
 -    if (res < 0) {
 -        kret = errno;
 -        DEBUG_PRINT(("krb5_krcc_destroy: unlinking key %d from ring %d: %s",
 -                     d->ring_id, d->parent_id, error_message(errno)));
 -        goto cleanup;
-+    if (d->ring_id) {
-+        if (res == -1) {
++    if (d->cache_id) {
++        res = keyctl_unlink(d->cache_id, d->collection_id);
++        if (res < 0) {
 +            kret = errno;
-+            DEBUG_PRINT(("krb5_krcc_destroy: unlinking key %d from keyring "
-+                         "%d: %s\n", d->ring_id, d->parent_id,
-+                         error_message(errno)));
++            DEBUG_PRINT(("unlinking key %d from ring %d: %s",
++                         d->cache_id, d->collection_id, error_message(errno)));
 +        }
++        /* If this is a legacy cache, unlink it from the session anchor. */
++        if (is_legacy_cache_name(d->name))
++            (void)keyctl_unlink(d->cache_id, KEY_SPEC_SESSION_KEYRING);
      }
 -cleanup:
 +
      k5_cc_mutex_unlock(context, &d->lock);
      k5_cc_mutex_destroy(&d->lock);
++    free(d->name);
      free(d);
-@@ -513,9 +617,133 @@ cleanup:
+     free(id);
  
      krb5_change_cache();
  
 -    return KRB5_OK;
 +    return kret;
-+}
-+
-+static krb5_error_code
-+krb5_krcc_unique_keyring(krb5_context context, key_serial_t parent_id,
-+                         char **retname, key_serial_t *keyring_id)
-+{
-+    key_serial_t keyring;
-+    krb5_error_code kret;
-+    char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
-+    int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
-+    int tries;
-+
-+/* XXX This values is platform-specific and should not be here! */
-+/* XXX There is a bug in FC5 where this is not included in errno.h  */
-+#ifndef ENOKEY
-+#define ENOKEY          126     /* Required key not available */
-+#endif
-+
-+    memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
-+    /*
-+     * Loop until we successfully create a new ccache keyring with
-+     * a unique name, or we get an error. Limit to 100 tries.
-+     */
-+    k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
-+
-+    tries = 100;
-+    while (tries-- > 0) {
-+        kret = krb5int_random_string(context, uniquename + prefixlen,
-+                                     KRCC_NAME_RAND_CHARS);
-+        if (kret) goto done;
-+
-+        DEBUG_PRINT(("krb5_krcc_unique_keyring: searching for name '%s'\n",
-+                     uniquename));
-+        keyring = keyctl_search(parent_id,
-+                                KRCC_KEY_TYPE_KEYRING, uniquename, 0);
-+        /*XXX*/ DEBUG_PRINT(("krb5_krcc_unique_keyring: after searching for '%s', keyring = %d, errno = %d\n", uniquename, keyring, errno));
-+        if (keyring < 0 && errno == ENOKEY) {
-+            /* name does not already exist, create it to reserve the name */
-+            keyring = add_key(KRCC_KEY_TYPE_KEYRING,
-+                              uniquename, NULL, 0, parent_id);
-+            if (keyring < 0) {
-+                kret = errno;
-+                DEBUG_PRINT(("krb5_krcc_unique_keyring: '%s' trying to "
-+                             "create '%s'\n", strerror(errno), uniquename));
-+                goto done;
-+            }
-+            break;
-+        }
-+    }
-+
-+    if (tries <= 0) {
-+        kret = KRB5_CC_BADNAME;
-+        goto done;
-+    }
-+
-+    *retname = strdup(uniquename);
-+    if (!*retname) {
-+        kret = ENOMEM;
-+        goto done;
-+    }
-+    *keyring_id = keyring;
-+    kret = KRB5_OK;
-+done:
-+    k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
-+    return kret;
-+}
-+
+ }
+ 
++/* Create a cache handle for a cache ID. */
 +static krb5_error_code
-+krb5_krcc_resolve_internal(key_serial_t ring_id, key_serial_t ccache_id,
-+                           const char *residual, krb5_ccache *cache_out)
++make_cache(key_serial_t collection_id, key_serial_t cache_id,
++           const char *anchor_name, const char *collection_name,
++           const char *subsidiary_name, krb5_ccache *cache_out)
 +{
-+    krb5_error_code kret;
++    krb5_error_code ret;
 +    krb5_ccache ccache = NULL;
 +    krb5_krcc_data *d;
 +    key_serial_t pkey = 0;
 +
-+    /* Determine key containing principal information */
-+    pkey = keyctl_search(ccache_id, KRCC_KEY_TYPE_USER,
-+                         KRCC_SPEC_PRINC_KEYNAME, 0);
-+    if (pkey < 0) {
-+        DEBUG_PRINT(("krb5_krcc_resolve_internal: Error locating principal "
-+                     "info for existing ccache in ring %d: %s\n",
-+                     ccache_id, strerror(errno)));
++    /* Determine the key containing principal information, if present. */
++    pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
++                         0);
++    if (pkey < 0)
 +        pkey = 0;
-+    }
 +
 +    ccache = malloc(sizeof(struct _krb5_ccache));
 +    if (!ccache)
 +        return ENOMEM;
 +
-+    kret = krb5_krcc_new_data(residual, ccache_id, ring_id, &d);
-+    if (kret) {
-+        goto done;
++    ret = krb5_krcc_new_data(anchor_name, collection_name, subsidiary_name,
++                             cache_id, collection_id, &d);
++    if (ret) {
++        free(ccache);
++        return ret;
 +    }
 +
-+    DEBUG_PRINT(("krb5_krcc_resolve_internal: ring_id %d, princ_id %d, "
-+                 "nkeys %d\n", ccache_id, pkey, nkeys));
 +    d->princ_id = pkey;
 +    ccache->ops = &krb5_krcc_ops;
 +    ccache->data = d;
 +    ccache->magic = KV5M_CCACHE;
 +    *cache_out = ccache;
-+    kret = KRB5_OK;
-+
-+done:
-+    if (kret) {
-+        free(ccache);
-+    }
-+    return kret;
- }
- 
-+/* get the current ring_name, this is captured in the subsidiary part of the
-+ * residual or is the residual name if no subsidiary is present */
-+static const char *
-+krb5_krcc_get_ring_name(const char *residual)
-+{
-+    const char *name;
-+
-+    name = strrchr(residual, ':');
-+    if (name) {
-+        name += 1;
-+        return name;
-+    }
-+
-+    return residual;
++    return 0;
 +}
  
  /*
   * Requires:
-@@ -532,45 +760,28 @@ cleanup:
-  * A filled in krb5_ccache structure "id".
-  *
-  * Errors:
-- * KRB5_CC_NOMEM - there was insufficient memory to allocate the
-- *              krb5_ccache.  id is undefined.
-+ * EINVAL - the residual name is invalid.
-+ * ENOMEM - there was insufficient memory for allocations.
-+ *          id is undefined.
-  * permission errors
+@@ -538,101 +1037,42 @@ cleanup:
   */
  
  static krb5_error_code KRB5_CALLCONV
- krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_residual)
+-krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_residual)
++krb5_krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
  {
 -    krb5_ccache lid;
-     krb5_error_code kret;
+-    krb5_error_code kret;
 -    krb5_krcc_data *d;
-     key_serial_t key;
+-    key_serial_t key;
 -    key_serial_t pkey = 0;
 -    int     nkeys = 0;
 -    int     res;
 -    krb5_krcc_ring_ids_t ids;
-     key_serial_t ring_id;
+-    key_serial_t ring_id;
 -    const char *residual;
-+    char *residual;
-+    const char *name;
- 
-     DEBUG_PRINT(("krb5_krcc_resolve: entered with name '%s'\n",
-                  full_residual));
++    krb5_error_code ret;
++    key_serial_t collection_id, cache_id;
++    char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ 
+-    DEBUG_PRINT(("krb5_krcc_resolve: entered with name '%s'\n",
+-                 full_residual));
++    ret = parse_residual(residual, &anchor_name, &collection_name,
++                         &subsidiary_name);
++    if (ret)
++        goto cleanup;
++    ret = get_collection(anchor_name, collection_name, &collection_id);
++    if (ret)
++        goto cleanup;
  
 -    res = krb5_krcc_get_ring_ids(&ids);
 -    if (res) {
 -        kret = EINVAL;
 -        DEBUG_PRINT(("krb5_krcc_resolve: Error getting ring id values!\n"));
-+    kret = krb5_krcc_get_keyring(context, full_residual,
-+                                 &residual, NULL, &ring_id);
-+    if (kret)
-         return kret;
--    }
--
+-        return kret;
++    if (subsidiary_name == NULL) {
++        /* Retrieve or initialize the primary name for the collection. */
++        ret = get_primary_name(context, anchor_name, collection_name,
++                               collection_id, &subsidiary_name);
++        if (ret)
++            goto cleanup;
+     }
+ 
 -    if (strncmp(full_residual, "thread:", 7) == 0) {
 -        residual = full_residual + 7;
 -        ring_id = ids.thread;
@@ -692,13 +1024,26 @@ index fd1bcec..f29c603 100644
 -        residual = full_residual;
 -        ring_id = ids.session;
 -    }
++    /* Look up the cache keyring ID, if the cache is already initialized. */
++    cache_id = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING,
++                             subsidiary_name, 0);
++    if (cache_id < 0)
++        cache_id = 0;
  
-     DEBUG_PRINT(("krb5_krcc_resolve: searching ring %d for residual '%s'\n",
-                  ring_id, residual));
-@@ -584,55 +795,24 @@ krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_resid
-      * the process and session rings if not found in the thread ring?
-      *
-      */
+-    DEBUG_PRINT(("krb5_krcc_resolve: searching ring %d for residual '%s'\n",
+-                 ring_id, residual));
++    ret = make_cache(collection_id, cache_id, anchor_name, collection_name,
++                     subsidiary_name, id);
+ 
+-    /*
+-     * Use keyctl_search instead of request_key. If we're supposed
+-     * to be looking for a process ccache, we shouldn't find a
+-     * thread ccache.
+-     * XXX But should we look in the session ring if we don't find it
+-     * in the process ring?  Same goes for thread.  Should we look in
+-     * the process and session rings if not found in the thread ring?
+-     *
+-     */
 -    key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, residual, 0);
 -    if (key < 0) {
 -        key = add_key(KRCC_KEY_TYPE_KEYRING, residual, NULL, 0, ring_id);
@@ -711,17 +1056,9 @@ index fd1bcec..f29c603 100644
 -        DEBUG_PRINT(("krb5_krcc_resolve: new keyring '%s', "
 -                     "key %d, added to keyring %d\n",
 -                     residual, key, ring_id));
-+
-+    name = krb5_krcc_get_ring_name(residual);
-+
-+    key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, name, 0);
-+    if (key == -1) {
-+        DEBUG_PRINT(("krb5_krcc_resolve: primary ccache keyring %s "
-+                     "not found in keyring %d\n", name, ring_id));
-+        key = 0;
-     } else {
-         DEBUG_PRINT(("krb5_krcc_resolve: found existing "
-                      "key %d, with name '%s' in keyring %d\n",
+-    } else {
+-        DEBUG_PRINT(("krb5_krcc_resolve: found existing "
+-                     "key %d, with name '%s' in keyring %d\n",
 -                     key, residual, ring_id));
 -        /* Determine key containing principal information */
 -        pkey = keyctl_search(key, KRCC_KEY_TYPE_USER,
@@ -734,9 +1071,8 @@ index fd1bcec..f29c603 100644
 -        }
 -        /* Determine how many keys exist */
 -        nkeys = krb5_krcc_getkeycount(key);
-+                     key, name, ring_id));
-     }
- 
+-    }
+-
 -    lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
 -    if (lid == NULL)
 -        return KRB5_CC_NOMEM;
@@ -747,8 +1083,7 @@ index fd1bcec..f29c603 100644
 -        free(lid);
 -        return kret;
 -    }
-+    kret = krb5_krcc_resolve_internal(ring_id, key, residual, id);
- 
+-
 -    DEBUG_PRINT(("krb5_krcc_resolve: ring_id %d, princ_id %d, "
 -                 "nkeys %d\n", key, pkey, nkeys));
 -    d->princ_id = pkey;
@@ -758,18 +1093,19 @@ index fd1bcec..f29c603 100644
 -    lid->magic = KV5M_CCACHE;
 -    *id = lid;
 -    return KRB5_OK;
-+    free(residual);
-+    return kret;
++cleanup:
++    free(anchor_name);
++    free(collection_name);
++    free(subsidiary_name);
++    return ret;
  }
  
  /*
-@@ -652,48 +832,40 @@ static krb5_error_code KRB5_CALLCONV
- krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id,
+@@ -653,47 +1093,37 @@ krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id,
                          krb5_cc_cursor * cursor)
  {
--    krb5_krcc_cursor krcursor;
-     krb5_error_code kret;
-+    krb5_krcc_cursor krcursor;
+     krb5_krcc_cursor krcursor;
+-    krb5_error_code kret;
      krb5_krcc_data *d;
 -    unsigned int size;
 -    int     res;
@@ -792,20 +1128,13 @@ index fd1bcec..f29c603 100644
 +    k5_cc_mutex_lock(context, &d->lock);
  
 -    size = sizeof(*krcursor) + ((d->numkeys + 1) * sizeof(key_serial_t));
-+    if (!d->ring_id) {
-+        k5_cc_mutex_unlock(context, &d->lock);
-+        return KRB5_FCC_NOFILE;
-+    }
- 
+-
 -    krcursor = (krb5_krcc_cursor) malloc(size);
 -    if (krcursor == NULL) {
-+    size = keyctl_read_alloc(d->ring_id, &keys);
-+    if (size == -1) {
-+        kret = errno;
-+        DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
++    if (!d->cache_id) {
          k5_cc_mutex_unlock(context, &d->lock);
 -        return KRB5_CC_NOMEM;
-+        return kret;
++        return KRB5_FCC_NOFILE;
      }
  
 -    krcursor->keys = (key_serial_t *) ((char *) krcursor + sizeof(*krcursor));
@@ -815,23 +1144,29 @@ index fd1bcec..f29c603 100644
 -        DEBUG_PRINT(("Read %d bytes from keyring, numkeys %d: %s\n",
 -                     res, d->numkeys, strerror(errno)));
 -        free(krcursor);
-+    krcursor = calloc(1, sizeof(*krcursor));
-+    if (krcursor == NULL) {
-+        free(keys);
++    size = keyctl_read_alloc(d->cache_id, &keys);
++    if (size == -1) {
++        DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
          k5_cc_mutex_unlock(context, &d->lock);
--        return KRB5_CC_IO;
-+        return KRB5_CC_NOMEM;
+         return KRB5_CC_IO;
      }
  
 -    krcursor->numkeys = d->numkeys;
 -    krcursor->currkey = 0;
++    krcursor = calloc(1, sizeof(*krcursor));
++    if (krcursor == NULL) {
++        free(keys);
++        k5_cc_mutex_unlock(context, &d->lock);
++        return KRB5_CC_NOMEM;
++    }
++
      krcursor->princ_id = d->princ_id;
 +    krcursor->numkeys = size / sizeof(key_serial_t);
 +    krcursor->keys = keys;
  
      k5_cc_mutex_unlock(context, &d->lock);
      *cursor = (krb5_cc_cursor) krcursor;
-@@ -741,14 +913,14 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
+@@ -741,14 +1171,14 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
      memset(creds, 0, sizeof(krb5_creds));
  
      /* If we're pointing past the end of the keys array, there are no more */
@@ -848,7 +1183,7 @@ index fd1bcec..f29c603 100644
              return KRB5_CC_END;
      }
  
-@@ -763,7 +935,7 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
+@@ -763,7 +1193,7 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
      }
      krcursor->currkey++;
  
@@ -857,7 +1192,7 @@ index fd1bcec..f29c603 100644
  
  freepayload:
      if (payload) free(payload);
-@@ -787,13 +959,33 @@ static krb5_error_code KRB5_CALLCONV
+@@ -787,19 +1217,24 @@ static krb5_error_code KRB5_CALLCONV
  krb5_krcc_end_seq_get(krb5_context context, krb5_ccache id,
                        krb5_cc_cursor * cursor)
  {
@@ -874,121 +1209,79 @@ index fd1bcec..f29c603 100644
      return KRB5_OK;
  }
  
-+static const char *
-+krb5_krcc_use_key_type(const char *residual)
-+{
-+    if (KRCC_HAS_PERSIST_PREFIX(residual)
-+        || KRCC_HAS_USER_PREFIX(residual)
-+        || KRCC_HAS_SESSION_PREFIX(residual)
-+        || KRCC_HAS_PROCESS_PREFIX(residual)
-+        || KRCC_HAS_THREAD_PREFIX(residual)) {
-+        /* new methods, try to use big_key */
-+        return KRCC_KEY_TYPE_BIG_KEY;
-+    } else {
-+        /* legacy session type, always use user type */
-+        return KRCC_KEY_TYPE_USER;
-+    }
-+}
-+
  /* Utility routine: Creates the back-end data for a keyring cache.
  
     Call with the global list lock held.  */
-@@ -823,14 +1015,63 @@ krb5_krcc_new_data(const char *name, key_serial_t ring,
+-static  krb5_error_code
+-krb5_krcc_new_data(const char *name, key_serial_t ring,
+-                   key_serial_t parent_ring, krb5_krcc_data ** datapp)
++static krb5_error_code
++krb5_krcc_new_data(const char *anchor_name, const char *collection_name,
++                   const char *subsidiary_name, key_serial_t cache_id,
++                   key_serial_t collection_id, krb5_krcc_data **datapp)
+ {
+     krb5_error_code kret;
+     krb5_krcc_data *d;
+@@ -814,17 +1249,18 @@ krb5_krcc_new_data(const char *name, key_serial_t ring,
+         return kret;
+     }
+ 
+-    d->name = strdup(name);
+-    if (d->name == NULL) {
++    kret = make_subsidiary_residual(anchor_name, collection_name,
++                                    subsidiary_name, &d->name);
++    if (kret) {
+         k5_cc_mutex_destroy(&d->lock);
+         free(d);
+-        return KRB5_CC_NOMEM;
++        return kret;
+     }
      d->princ_id = 0;
-     d->ring_id = ring;
-     d->parent_id = parent_ring;
+-    d->ring_id = ring;
+-    d->parent_id = parent_ring;
 -    d->numkeys = 0;
++    d->cache_id = cache_id;
++    d->collection_id = collection_id;
      d->changetime = 0;
-+    d->key_type = krb5_krcc_use_key_type(name);
++    d->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
      krb5_krcc_update_change_time(d);
  
      *datapp = d;
-     return 0;
- }
- 
-+/* appends the new subsidiary name to the full name, sets subsidiary to
-+ * the default name of 'tkt' if ring_name is NULL */
-+static char *
-+krb5_krcc_new_subsidiary(const char *residual, const char *ring_name)
-+{
-+    char *r, *p;
-+    const char *resname;
-+    size_t rl, rn;
-+
-+    if (KRCC_HAS_PROCESS_PREFIX(residual)) {
-+        resname = residual + sizeof(KRCC_PROCESS_PREFIX);
-+    } else if (KRCC_HAS_THREAD_PREFIX(residual)) {
-+        resname = residual + sizeof(KRCC_THREAD_PREFIX);
-+    } else if (KRCC_HAS_PERSIST_PREFIX(residual)) {
-+        resname = residual + sizeof(KRCC_PERSIST_PREFIX);
-+    } else if (KRCC_HAS_USER_PREFIX(residual)) {
-+        resname = residual + sizeof(KRCC_USER_PREFIX);
-+    } else if (KRCC_HAS_SESSION_PREFIX(residual)) {
-+        resname = residual + sizeof(KRCC_SESSION_PREFIX);
-+    } else {
-+        /* For backwards compatibility the legacy session keyring must
-+         * be named after the residual when ring_name is not provided */
-+        if (ring_name && strcmp(ring_name, "tkt") != 0)
-+            return strdup(ring_name);
-+        else
-+            return strdup(residual);
-+    }
-+
-+    if (!ring_name)
-+        ring_name = "tkt";
-+
-+    /* the ':' after the type prefix is already skipped above */
-+    p = strrchr(resname, ':');
-+    if (p)
-+        rl = p - residual; /* excludes subsidiary ':' */
-+    else
-+        rl = strlen(residual);
-+    rn = strlen(ring_name) + 1; /* includes \0 */
-+
-+    r = malloc(rl + 1 + rn);
-+    if (!r)
-+        return NULL;
-+
-+    memcpy(r, residual, rl);
-+    r[rl++] = ':';
-+    memcpy(&r[rl], ring_name, rn);
-+    return r;
-+}
-+
- /*
-  * Effects:
-  * Creates a new keyring cred cache whose name is guaranteed to be
-@@ -846,82 +1087,67 @@ krb5_krcc_new_data(const char *name, key_serial_t ring,
+@@ -846,82 +1282,73 @@ krb5_krcc_new_data(const char *name, key_serial_t ring,
  static krb5_error_code KRB5_CALLCONV
  krb5_krcc_generate_new(krb5_context context, krb5_ccache * id)
  {
 -    krb5_ccache lid;
 -    char uniquename[8];
 +    krb5_ccache lid = NULL;
-+    char *uniquename = NULL;
-+    char *residual = NULL;
-+    char *new_res = NULL;
      krb5_error_code kret;
++    char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
++    char *new_subsidiary_name = NULL, *new_residual = NULL;
      krb5_krcc_data *d;
 -    key_serial_t ring_id = KEY_SPEC_SESSION_KEYRING;
 -    key_serial_t key;
-+    krb5_boolean subsidiary;
-+    key_serial_t ring_id;
-+    key_serial_t key = 0;
++    key_serial_t collection_id;
++    key_serial_t cache_id = 0;
  
      DEBUG_PRINT(("krb5_krcc_generate_new: entered\n"));
  
-+    kret = krb5_krcc_default_keyring(context, &subsidiary,
-+                                     &residual, &ring_id);
++    /* Determine the collection in which we will create the cache.*/
++    kret = get_default(context, &anchor_name, &collection_name,
++                       &subsidiary_name);
 +    if (kret)
 +        return kret;
-+
-+    if (subsidiary) {
++    if (anchor_name == NULL) {
++        kret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
++                              &collection_name, &subsidiary_name);
++        if (kret)
++            return kret;
++    }
++    if (subsidiary_name != NULL) {
 +        krb5_set_error_message(context, KRB5_DCC_CANNOT_CREATE,
 +                               _("Can't create new subsidiary cache because "
 +                                 "default cache is already a subsdiary"));
 +        kret = KRB5_DCC_CANNOT_CREATE;
-+        goto done;
++        goto cleanup;
 +    }
 +
      /* Allocate memory */
@@ -997,7 +1290,7 @@ index fd1bcec..f29c603 100644
 -        return KRB5_CC_NOMEM;
 +    if (lid == NULL) {
 +        kret = ENOMEM;
-+        goto done;
++        goto cleanup;
 +    }
  
      lid->ops = &krb5_krcc_ops;
@@ -1007,10 +1300,7 @@ index fd1bcec..f29c603 100644
 -        free(lid);
 -        return kret;
 -    }
-+    kret = krb5_krcc_unique_keyring(context, ring_id, &uniquename, &key);
-+    if (kret)
-+        goto done;
- 
+-
 -/* XXX These values are platform-specific and should not be here! */
 -/* XXX There is a bug in FC5 where these are not included in errno.h  */
 -#ifndef ENOKEY
@@ -1025,11 +1315,14 @@ index fd1bcec..f29c603 100644
 -#ifndef EKEYREJECTED
 -#define EKEYREJECTED    129     /* Key was rejected by service */
 -#endif
-+    new_res = krb5_krcc_new_subsidiary(residual, uniquename);
-+    if (!new_res) {
-+        kret = ENOMEM;
-+        goto done;
-+    }
++    /* Make a unique keyring within the chosen collection. */
++    kret = get_collection(anchor_name, collection_name, &collection_id);
++    if (kret)
++        goto cleanup;
++    kret = unique_keyring(context, collection_id, &new_subsidiary_name,
++                          &cache_id);
++    if (kret)
++        goto cleanup;
  
 -    /*
 -     * Loop until we successfully create a new ccache keyring with
@@ -1042,9 +1335,11 @@ index fd1bcec..f29c603 100644
 -            free(lid);
 -            return kret;
 -        }
-+    kret = krb5_krcc_new_data(new_res, key, ring_id, &d);
++    kret = krb5_krcc_new_data(anchor_name, collection_name,
++                              new_subsidiary_name, cache_id, collection_id,
++                              &d);
 +    if (kret)
-+        goto done;
++        goto cleanup;
  
 -        DEBUG_PRINT(("krb5_krcc_generate_new: searching for name '%s'\n",
 -                     uniquename));
@@ -1065,14 +1360,15 @@ index fd1bcec..f29c603 100644
 -    }
 +    lid->data = d;
 +    krb5_change_cache();
-+    kret = KRB5_OK;
  
 -    kret = krb5_krcc_new_data(uniquename, key, ring_id, &d);
 -    k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
-+done:
-+    free(uniquename);
-+    free(residual);
-+    free(new_res);
++cleanup:
++    free(anchor_name);
++    free(collection_name);
++    free(subsidiary_name);
++    free(new_subsidiary_name);
++    free(new_residual);
      if (kret) {
          free(lid);
          return kret;
@@ -1083,46 +1379,12 @@ index fd1bcec..f29c603 100644
      return KRB5_OK;
  }
  
-@@ -1001,8 +1227,9 @@ krb5_krcc_remove_cred(krb5_context context, krb5_ccache cache,
- static krb5_error_code KRB5_CALLCONV
- krb5_krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
- {
-+    krb5_krcc_data *d = (krb5_krcc_data *)id->data;
-     DEBUG_PRINT(("krb5_krcc_set_flags: entered\n"));
--
-+    if (d->ring_id == 0) return KRB5_FCC_NOFILE;
-     return KRB5_OK;
- }
- 
-@@ -1015,6 +1242,22 @@ krb5_krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags * flags)
-     return KRB5_OK;
- }
- 
-+static key_serial_t
-+krb5_krcc_add_key(const char *name, const void *payload, size_t plen,
-+                  krb5_krcc_data *data)
-+{
-+#ifdef HAVE_PERSISTENT_KEYRING
-+    key_serial_t key;
-+
-+    /* try with big_key and if it fails fall back to user key */
-+    errno = 0;
-+    key = add_key(data->key_type, name, payload, plen, data->ring_id);
-+    if (key != -1 || errno != EINVAL)
-+        return key;
-+#endif
-+    return add_key(KRCC_KEY_TYPE_USER, name, payload, plen, data->ring_id);
-+}
-+
- /* store: Save away creds in the ccache keyring.  */
- static krb5_error_code KRB5_CALLCONV
- krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
-@@ -1025,12 +1268,17 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
+@@ -1023,14 +1450,16 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
+     krb5_krcc_data *d = (krb5_krcc_data *) id->data;
+     char   *payload = NULL;
      unsigned int payloadlen;
-     key_serial_t newkey;
+-    key_serial_t newkey;
      char   *keyname = NULL;
-+    long timeout;
-+    long res;
  
      DEBUG_PRINT(("krb5_krcc_store: entered\n"));
  
@@ -1131,14 +1393,14 @@ index fd1bcec..f29c603 100644
 -        return kret;
 +    k5_cc_mutex_lock(context, &d->lock);
 +
-+    if (!d->ring_id) {
++    if (!d->cache_id) {
 +        k5_cc_mutex_unlock(context, &d->lock);
 +        return KRB5_FCC_NOFILE;
 +    }
  
      /* Get the service principal name and use it as the key name */
      kret = krb5_unparse_name(context, creds->server, &keyname);
-@@ -1040,25 +1288,33 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
+@@ -1040,24 +1469,19 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
      }
  
      /* Serialize credential into memory */
@@ -1149,37 +1411,29 @@ index fd1bcec..f29c603 100644
  
      /* Add new key (credentials) into keyring */
      DEBUG_PRINT(("krb5_krcc_store: adding new key '%s' to keyring %d\n",
-                  keyname, d->ring_id));
+-                 keyname, d->ring_id));
 -    newkey = add_key(KRCC_KEY_TYPE_USER, keyname, payload,
 -                     payloadlen, d->ring_id);
-+    newkey = krb5_krcc_add_key(keyname, payload, payloadlen, d);
-     if (newkey < 0) {
-         kret = errno;
-         DEBUG_PRINT(("Error adding user key '%s': %s\n",
-                      keyname, strerror(kret)));
+-    if (newkey < 0) {
+-        kret = errno;
+-        DEBUG_PRINT(("Error adding user key '%s': %s\n",
+-                     keyname, strerror(kret)));
 -    } else {
 -        d->numkeys++;
 -        kret = KRB5_OK;
 -        krb5_krcc_update_change_time(d);
+-    }
++                 keyname, d->cache_id));
++    kret = add_cred_key(keyname, payload, payloadlen, d->cache_id,
++                        d->is_legacy_type);
++    if (kret)
 +        goto errout;
-+    }
 +
-+    timeout = creds->times.endtime - time(NULL);
-+    /* if it wraps do not try to set timestamp */
-+    if (timeout > 0) {
-+        res = keyctl_set_timeout(d->ring_id, timeout);
-+        if (res != 0)
-+            DEBUG_PRINT(("krb5_krcc_store: Failed to set ccache timeout "
-+                         "[%ld]", timeout));
-     }
- 
-+    kret = KRB5_OK;
 +    krb5_krcc_update_change_time(d);
-+
+ 
  errout:
      if (keyname)
-         krb5_free_unparsed_name(context, keyname);
-@@ -1073,36 +1329,30 @@ static krb5_error_code KRB5_CALLCONV
+@@ -1073,36 +1497,30 @@ static krb5_error_code KRB5_CALLCONV
  krb5_krcc_last_change_time(krb5_context context, krb5_ccache id,
                             krb5_timestamp *change_time)
  {
@@ -1226,7 +1480,7 @@ index fd1bcec..f29c603 100644
  }
  
  
-@@ -1112,7 +1362,7 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
+@@ -1112,7 +1530,7 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
  {
      krb5_krcc_data *d;
      krb5_error_code kret;
@@ -1235,7 +1489,7 @@ index fd1bcec..f29c603 100644
      key_serial_t newkey;
      unsigned int payloadsize;
      krb5_krcc_bc bc;
-@@ -1121,14 +1371,19 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
+@@ -1121,14 +1539,19 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
  
      d = (krb5_krcc_data *) id->data;
  
@@ -1250,601 +1504,261 @@ index fd1bcec..f29c603 100644
 +    payload = malloc(bc.size);
      if (payload == NULL)
          return KRB5_CC_NOMEM;
--
-     bc.bpp = payload;
--    bc.endp = payload + GUESS_CRED_SIZE;
--
--    kret = krb5_krcc_unparse_principal(context, id, princ, &bc);
-+    bc.endp = payload + bc.size;
-+    kret = krb5_krcc_unparse_principal(context, princ, &bc);
-     CHECK_N_GO(kret, errout);
- 
-     /* Add new key into keyring */
-@@ -1172,11 +1427,9 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id,
-     int     psize;
-     krb5_krcc_bc bc;
- 
--    kret = k5_cc_mutex_lock(context, &d->lock);
--    if (kret)
--        return kret;
-+    k5_cc_mutex_lock(context, &d->lock);
- 
--    if (!d->princ_id) {
-+    if (!d->ring_id || !d->princ_id) {
-         princ = 0L;
-         kret = KRB5_FCC_NOFILE;
-         goto errout;
-@@ -1191,7 +1444,7 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id,
-     }
-     bc.bpp = payload;
-     bc.endp = (char *)payload + psize;
--    kret = krb5_krcc_parse_principal(context, id, princ, &bc);
-+    kret = krb5_krcc_parse_principal(context, princ, &bc);
- 
- errout:
-     if (payload)
-@@ -1200,57 +1453,542 @@ errout:
-     return kret;
- }
- 
--static int
--krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p)
-+/* stores the subsidiary name in the priary index key */
-+static krb5_error_code
-+krb5_krcc_set_primary(krb5_context context, const char *name,
-+                      key_serial_t ring_id)
- {
--    key_serial_t ids_key;
--    char ids_buf[128];
--    key_serial_t session, process, thread;
--    long val;
-+    krb5_error_code kret;
-+    key_serial_t key;
-+    void *payload = NULL;
-+    int payloadlen;
- 
--    DEBUG_PRINT(("krb5_krcc_get_ring_ids: entered\n"));
-+    kret = krb5_krcc_unparse_index(context, KRCC_COLLECTION_VERSION,
-+                                   name, &payload, &payloadlen);
-+    if (kret) {
-+        DEBUG_PRINT(("krb5_krcc_set_primary: Error creating primary with "
-+                     "content [%d:%s]", KRCC_COLLECTION_VERSION, name));
-+        return kret;
-+    }
- 
--    if (!p)
--        return EINVAL;
-+    key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
-+                  payload, payloadlen, ring_id);
-+    if (key == -1) {
-+        kret = errno;
-+        DEBUG_PRINT(("krb5_krcc_set_primary: Error setting primary key: "
-+                     "%s\n", strerror(kret)));
-+        goto done;
-+    }
- 
--    /* Use the defaults in case we find no ids key */
--    p->session = KEY_SPEC_SESSION_KEYRING;
--    p->process = KEY_SPEC_PROCESS_KEYRING;
--    p->thread = KEY_SPEC_THREAD_KEYRING;
-+    DEBUG_PRINT(("krb5_krcc_set_primary: created primary key %d, for "
-+                 "keyring %d (%s)\n", key, ring_id, name));
-+    kret = KRB5_OK;
- 
--    /*
--     * Note that in the "normal" case, this will not be found.
--     * The Linux gssd creates this key while creating a
--     * context to communicate the user's key serial numbers.
--     */
--    ids_key = request_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_IDS_KEYNAME, NULL, 0);
--    if (ids_key < 0)
--        goto out;
-+done:
-+    free(payload);
-+    return kret;
-+}
- 
--    DEBUG_PRINT(("krb5_krcc_get_ring_ids: processing '%s' key %d\n",
--                 KRCC_SPEC_IDS_KEYNAME, ids_key));
--    /*
--     * Read and parse the ids file
--     */
--    memset(ids_buf, '\0', sizeof(ids_buf));
--    val = keyctl_read(ids_key, ids_buf, sizeof(ids_buf));
--    if (val > sizeof(ids_buf))
--        goto out;
-+/* gets the primary index key if available */
-+static krb5_error_code
-+krb5_krcc_get_primary(krb5_context context, key_serial_t ring_id, char **name)
-+{
-+    krb5_error_code kret;
-+    key_serial_t primary;
-+    void *payload = NULL;
-+    int payloadlen;
-+    krb5_int32 version;
-+
-+    primary = keyctl_search(ring_id, KRCC_KEY_TYPE_USER,
-+                              KRCC_COLLECTION_PRIMARY, 0);
-+    if (primary == -1) {
-+        DEBUG_PRINT(("krb5_krcc_get_primary: No primary key available\n"));
-+        *name = NULL;
-+        return ENOENT;
-+    }
-+    payloadlen = keyctl_read_alloc(primary, &payload);
-+    if (payloadlen == -1) {
-+        DEBUG_PRINT(("krb5_krcc_get_primary: Error reading primary key\n"));
-+        kret = EINVAL;
-+        goto done;
-+    }
- 
--    val = sscanf(ids_buf, "%d:%d:%d", &session, &process, &thread);
--    if (val != 3)
--        goto out;
-+    kret = krb5_krcc_parse_index(context, &version, name, payload, payloadlen);
-+    if (kret) {
-+        DEBUG_PRINT(("krb5_krcc_get_primary: Error parsing primary key\n"));
-+        goto done;
-+    }
- 
--    p->session = session;
--    p->process = process;
--    p->thread = thread;
-+    if (version != KRCC_COLLECTION_VERSION) {
-+        DEBUG_PRINT(("krb5_krcc_get_primary: Invalid version\n"));
-+        kret = EINVAL;
-+        goto done;
-+    }
- 
--out:
--    DEBUG_PRINT(("krb5_krcc_get_ring_ids: returning %d:%d:%d\n",
--                 p->session, p->process, p->thread));
-+    DEBUG_PRINT(("krb5_krcc_get_primary: primary key %d, points to "
-+                 "keyring %s\n", primary, *name));
-+    kret = KRB5_OK;
-+
-+done:
-+    free(payload);
-+    return kret;
-+}
-+
-+/* find out and return the keyring we need according to the full residual */
-+static krb5_error_code
-+krb5_krcc_get_keyring(krb5_context context, const char *full_residual,
-+                      char **name, krb5_boolean *subsidiary, key_serial_t *id)
-+{
-+    const char *residual;
-+    krb5_error_code kret;
-+    key_serial_t ring_id = 0;
-+    key_serial_t cccol_id = 0;
-+    char *p = NULL;
-+    char *ccname = NULL;
-+    krb5_boolean sub = FALSE;
-+    key_serial_t parent;
-+    key_serial_t link = 0;
-+    enum krcc_keyring_type type;
-+    long long int luid;
-+    int len, ret;
-+
-+    if (KRCC_HAS_PROCESS_PREFIX(full_residual)) {
-+        residual = full_residual + (sizeof(KRCC_PROCESS_PREFIX) - 1);
-+        ring_id = KEY_SPEC_PROCESS_KEYRING;
-+        type = KRCC_PROCESS;
-+    } else if (KRCC_HAS_PERSIST_PREFIX(full_residual)) {
-+        residual = full_residual + (sizeof(KRCC_PERSIST_PREFIX) - 1);
-+        type = KRCC_PERSIST;
-+    } else if (KRCC_HAS_USER_PREFIX(full_residual)) {
-+        residual = full_residual + (sizeof(KRCC_USER_PREFIX) - 1);
-+        ring_id = KEY_SPEC_USER_KEYRING;
-+        link = KEY_SPEC_PROCESS_KEYRING;
-+        type = KRCC_USER;
-+    } else if (KRCC_HAS_SESSION_PREFIX(full_residual)) {
-+        residual = full_residual + (sizeof(KRCC_SESSION_PREFIX) - 1);
-+        ring_id = KEY_SPEC_SESSION_KEYRING;
-+        type = KRCC_SESSION;
-+    } else if (KRCC_HAS_THREAD_PREFIX(full_residual)) {
-+        residual = full_residual + (sizeof(KRCC_THREAD_PREFIX) - 1);
-+        ring_id = KEY_SPEC_THREAD_KEYRING;
-+        type = KRCC_THREAD;
-+    } else {
-+        /* legacy backwards compat */
-+        residual = full_residual;
-+        ring_id = KEY_SPEC_SESSION_KEYRING;
-+        type = KRCC_LEGACY_SESSION;
-+    }
-+
-+    /* we are transforming classic KEYRING caches into collection enabled
-+     * caches as well here, while maintaining compatibility with existing
-+     * practices.
-+     * We can't change semantics of the legacy session type as they may be
-+     * shared between a new and an old version of the library. So for the
-+     * legacy session keyring create the collection keyring using a prefixed
-+     * residual name in order ro leave the original name free for the first
-+     * ccache keyring which will be placed both in the collection keyring as
-+     * well as linked directly in the session keyring. */
-+    switch (type) {
-+    case KRCC_LEGACY_SESSION:
-+    case KRCC_THREAD:
-+    case KRCC_PROCESS:
-+    case KRCC_SESSION:
-+    case KRCC_USER:
-+        /* check if this is a plain name or includes a subsidiary */
-+        p = strchr(residual, ':');
-+        if (p) {
-+            sub = TRUE;
-+            len = p - residual;
-+        } else {
-+            len = strlen(residual);
-+        }
-+        ret = asprintf(&ccname, KRCC_CCCOL_PREFIX"%.*s", len, residual);
-+        if (ret == -1 || !ccname) {
-+            return ENOMEM;
-+        }
-+
-+        cccol_id = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, ccname, link);
-+        if (cccol_id == -1) {
-+            cccol_id = add_key(KRCC_KEY_TYPE_KEYRING,
-+                               ccname, NULL, 0, ring_id);
-+            if (cccol_id == -1) {
-+                kret = errno;
-+                DEBUG_PRINT(("krb5_krcc_get_keyring: Couldn't create [%s] "
-+                             "keyring, error %d (%s)\n",
-+                             ccname, kret, strerror(kret)));
-+                free(ccname);
-+                return kret;
-+            }
-+        }
-+        free(ccname);
-+        ccname = NULL;
-+        ring_id = cccol_id;
-+        break;
-+
-+    case KRCC_PERSIST:
-+
-+        switch (residual[0]) {
-+        case ':':
-+            /* residual present */
-+            sub = TRUE;
-+            /* fall through */
-+        case '\0':
-+            /* no uid specified, use user's own */
-+            luid = geteuid();
-+            break;
-+        default:
-+            errno = 0;
-+            luid = strtoll(residual, &p, 10);
-+            if (errno)
-+                return EINVAL;
-+            if (*p == ':')
-+                sub = TRUE;
-+            else if ((p == residual) || (*p != 0))
-+                return EINVAL;
-+        }
-+
-+#ifdef HAVE_PERSISTENT_KEYRING
-+        parent = keyctl_get_persistent(luid, KEY_SPEC_PROCESS_KEYRING);
-+        if (parent == -1) {
-+            /* if the kernel does not support persistent keyrings,
-+            * fall back to a standard user key ring */
-+            if (errno == ENOTSUP)
-+                parent = KEY_SPEC_USER_KEYRING;
-+            else
-+                return errno;
-+        }
-+#else
-+        parent = KEY_SPEC_USER_KEYRING;
-+#endif
-+        if ((parent == KEY_SPEC_USER_KEYRING) && (luid != geteuid()))
-+            return EINVAL;
-+        ring_id = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, "_krb", link);
-+        if (ring_id == -1) {
-+            ring_id = add_key(KRCC_KEY_TYPE_KEYRING, "_krb", NULL, 0, parent);
-+            if (ring_id == -1) {
-+                kret = errno;
-+                DEBUG_PRINT(("krb5_krcc_get_keyring: Couldn't create _krb "
-+                             "keyring for uid %ld, error %d (%s)\n",
-+                             (long)luid, kret, strerror(kret)));
-+                return kret;
-+            }
-+        }
-+        break;
-+    }
-+
-+    /* if a subsidiary is specified keep it */
-+    if (sub == TRUE) {
-+        *name = strdup(full_residual);
-+        if (*name == NULL)
-+            return ENOMEM;
-+    } else {
-+        /* Check to see if we have an index key by chance, if we do get the
-+         * ccache pointed by it */
-+        kret = krb5_krcc_get_primary(context, ring_id, &ccname);
-+        if (kret) {
-+            DEBUG_PRINT(("krb5_krcc_get_keyring: Error reading primary key "
-+                         "from keyring %d: %s\n", ring_id, strerror(kret)));
-+        }
-+
-+        *name = krb5_krcc_new_subsidiary(full_residual, ccname);
-+    }
-+
-+    /* always set a default primary like DIR type does */
-+    residual = krb5_krcc_get_ring_name(*name);
-+    kret = krb5_krcc_set_primary(context, residual, ring_id);
-+    if (kret) {
-+        DEBUG_PRINT(("krb5_kcc_set_keyring: Failed to set primary key "
-+                     "%d: %s\n", kret, strerror(kret)));
-+    }
-+
-+    if (subsidiary)
-+        *subsidiary = sub;
-+    *id = ring_id;
-+    free(ccname);
-+    return KRB5_OK;
-+}
-+
-+static krb5_error_code
-+krb5_krcc_default_keyring(krb5_context context, krb5_boolean *subsidiary,
-+                          char **name, key_serial_t *id)
-+{
-+    const char *defname;
-+
-+    *subsidiary = FALSE;
-+    if (name) *name = NULL;
-+    *id = 0;
-+
-+    defname = krb5_cc_default_name(context);
-+    if (!defname || strncmp(defname, "KEYRING:", 8) != 0) {
-+        /* return classic session type with empty name so that a
-+         * random name will actually be generate at initialization time */
-+        return krb5_krcc_get_keyring(context, "", name, subsidiary, id);
-+    }
-+    return krb5_krcc_get_keyring(context, &defname[8], name, subsidiary, id);
-+}
-+
-+/* check if the keyring type if the legacy session type, the behavior is
-+ * slightly different wrt keyring hierarchies and naming in that case */
-+static krb5_boolean
-+krb5_krcc_check_legacy_session(key_serial_t ring_id, const char *residual,
-+                               char **ccache_name, key_serial_t *ccache_id)
-+{
-+    key_serial_t key;
-+    char *name, *ep;
-+
-+    if (!KRCC_IS_LEGACY_SESSION(residual)) {
-+        return FALSE;
-+    }
-+
-+    /* a legacy session residual looks like this:
-+     * <name>:<subsidiary> and we need <name> */
-+    ep = strchr(residual, ':');
-+    if (!ep) /* something weird, just ignore */
-+        return FALSE;
-+
-+    name = strndup(residual, ep - residual);
-+    if (!name) /* too bad */
-+        return FALSE;
-+
-+    key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, name, 0);
-+    if (key == -1) {
-+        free(name);
-+        return FALSE;
-+    }
-+
-+    *ccache_name = name;
-+    *ccache_id = key;
-+    return TRUE;
-+}
-+
+-
+     bc.bpp = payload;
+-    bc.endp = payload + GUESS_CRED_SIZE;
+-
+-    kret = krb5_krcc_unparse_principal(context, id, princ, &bc);
++    bc.endp = payload + bc.size;
++    kret = krb5_krcc_unparse_principal(context, princ, &bc);
+     CHECK_N_GO(kret, errout);
+ 
+     /* Add new key into keyring */
+@@ -1140,14 +1563,14 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
+         rc = krb5_unparse_name(context, princ, &princname);
+         DEBUG_PRINT(("krb5_krcc_save_principal: adding new key '%s' "
+                      "to keyring %d for principal '%s'\n",
+-                     KRCC_SPEC_PRINC_KEYNAME, d->ring_id,
++                     KRCC_SPEC_PRINC_KEYNAME, d->cache_id,
+                      rc ? "<unknown>" : princname));
+         if (rc == 0)
+             krb5_free_unparsed_name(context, princname);
+     }
+ #endif
+     newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, payload,
+-                     payloadsize, d->ring_id);
++                     payloadsize, d->cache_id);
+     if (newkey < 0) {
+         kret = errno;
+         DEBUG_PRINT(("Error adding principal key: %s\n", strerror(kret)));
+@@ -1172,11 +1595,9 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id,
+     int     psize;
+     krb5_krcc_bc bc;
+ 
+-    kret = k5_cc_mutex_lock(context, &d->lock);
+-    if (kret)
+-        return kret;
++    k5_cc_mutex_lock(context, &d->lock);
+ 
+-    if (!d->princ_id) {
++    if (!d->cache_id || !d->princ_id) {
+         princ = 0L;
+         kret = KRB5_FCC_NOFILE;
+         goto errout;
+@@ -1191,7 +1612,7 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id,
+     }
+     bc.bpp = payload;
+     bc.endp = (char *)payload + psize;
+-    kret = krb5_krcc_parse_principal(context, id, princ, &bc);
++    kret = krb5_krcc_parse_principal(context, princ, &bc);
+ 
+ errout:
+     if (payload)
+@@ -1200,57 +1621,195 @@ errout:
+     return kret;
+ }
+ 
+-static int
+-krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p)
+-{
+-    key_serial_t ids_key;
+-    char ids_buf[128];
+-    key_serial_t session, process, thread;
+-    long val;
 +struct krcc_ptcursor_data {
-+    key_serial_t ring_id;
-+    char *name;
++    key_serial_t collection_id;
++    char *anchor_name;
++    char *collection_name;
++    char *subsidiary_name;
++    char *primary_name;
 +    krb5_boolean first;
-+    krb5_boolean subsidiary;
 +    long num_keys;
 +    long next_key;
 +    key_serial_t *keys;
 +};
-+
+ 
+-    DEBUG_PRINT(("krb5_krcc_get_ring_ids: entered\n"));
 +static krb5_error_code KRB5_CALLCONV
 +krb5_krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
 +{
 +    struct krcc_ptcursor_data *data;
-+    krb5_cc_ptcursor cursor = NULL;
-+    krb5_error_code kret;
++    krb5_cc_ptcursor cursor;
++    krb5_error_code ret;
 +    long size;
 +
 +    *cursor_out = NULL;
 +
-+    data = calloc(1, sizeof(struct krcc_ptcursor_data));
-+    if (!data)
-+        return ENOMEM;
-+    cursor = malloc(sizeof(struct krb5_cc_ptcursor_s));
-+    if (!cursor) {
-+        free(data);
++    cursor = k5alloc(sizeof(struct krb5_cc_ptcursor_s), &ret);
++    if (cursor == NULL)
 +        return ENOMEM;
-+    }
++    data = k5alloc(sizeof(struct krcc_ptcursor_data), &ret);
++    if (data == NULL)
++        goto error;
 +    cursor->ops = &krb5_krcc_ops;
 +    cursor->data = data;
 +    data->first = TRUE;
-+    data->subsidiary = FALSE;
-+
-+    /* If a keyring cannot be found or the default cache is a subsidiary
-+     * then return an empty data set with only the primary/subsidiary set
-+     * as the cache name */
-+    kret = krb5_krcc_default_keyring(context, &data->subsidiary,
-+                                     &data->name, &data->ring_id);
-+    if (kret || (data->ring_id == 0))
-+        goto done;
-+
-+    size = keyctl_read_alloc(data->ring_id, (void **)&data->keys);
-+    if (size == -1) {
-+        kret = errno;
-+        goto done;
-+    }
-+    data->num_keys = size / sizeof(key_serial_t);
 +
-+    kret = KRB5_OK;
++    ret = get_default(context, &data->anchor_name, &data->collection_name,
++                      &data->subsidiary_name);
++    if (ret)
++        goto error;
 +
-+done:
-+    if (kret) {
-+        free(cursor->data);
-+        free(cursor);
-+    } else {
++    /* If there is no default collection, return an empty cursor. */
++    if (data->anchor_name == NULL) {
 +        *cursor_out = cursor;
++        return 0;
 +    }
-+    return kret;
+ 
+-    if (!p)
+-        return EINVAL;
++    ret = get_collection(data->anchor_name, data->collection_name,
++                         &data->collection_id);
++    if (ret)
++        goto error;
++
++    if (data->subsidiary_name == NULL) {
++        ret = get_primary_name(context, data->anchor_name,
++                               data->collection_name, data->collection_id,
++                               &data->primary_name);
++        if (ret)
++            goto error;
++
++        size = keyctl_read_alloc(data->collection_id, (void **)&data->keys);
++        if (size == -1) {
++            ret = errno;
++            goto error;
++        }
++        data->num_keys = size / sizeof(key_serial_t);
++    }
+ 
+-    /* Use the defaults in case we find no ids key */
+-    p->session = KEY_SPEC_SESSION_KEYRING;
+-    p->process = KEY_SPEC_PROCESS_KEYRING;
+-    p->thread = KEY_SPEC_THREAD_KEYRING;
++    *cursor_out = cursor;
++    return 0;
+ 
+-    /*
+-     * Note that in the "normal" case, this will not be found.
+-     * The Linux gssd creates this key while creating a
+-     * context to communicate the user's key serial numbers.
+-     */
+-    ids_key = request_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_IDS_KEYNAME, NULL, 0);
+-    if (ids_key < 0)
+-        goto out;
++error:
++    krb5_krcc_ptcursor_free(context, &cursor);
++    return ret;
 +}
-+
+ 
+-    DEBUG_PRINT(("krb5_krcc_get_ring_ids: processing '%s' key %d\n",
+-                 KRCC_SPEC_IDS_KEYNAME, ids_key));
+-    /*
+-     * Read and parse the ids file
+-     */
+-    memset(ids_buf, '\0', sizeof(ids_buf));
+-    val = keyctl_read(ids_key, ids_buf, sizeof(ids_buf));
+-    if (val > sizeof(ids_buf))
+-        goto out;
 +static krb5_error_code KRB5_CALLCONV
 +krb5_krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
 +                        krb5_ccache *cache_out)
 +{
++    krb5_error_code ret;
 +    struct krcc_ptcursor_data *data;
-+    key_serial_t ccache_id = 0;
-+    krb5_error_code kret;
-+    const char *first_name;
-+    const char *pref;
-+    const char *type;
-+    size_t preflen;
-+    size_t typelen;
++    key_serial_t key, cache_id = 0;
++    const char *first_name, *keytype, *sep, *subsidiary_name;
++    size_t keytypelen;
 +    char *description = NULL;
-+    char *residual;
-+    char *name = NULL;
-+    long cur_key;
-+    long res;
 +
 +    *cache_out = NULL;
 +
 +    data = cursor->data;
 +
 +    /* No keyring available */
-+    if (data->ring_id == 0)
-+        return 0;
-+
-+    /* if no cccol is available name will be set to "" for compatibility
-+     * with legacy session keyrings. Return 0 in this case as we do not
-+     * want to try to enumerate all keys in the general session keyring */
-+    if (data->name[0] == '\0')
-+        return 0;
-+
-+    /* if we already returned the named subsidiary just stop */
-+    if (data->subsidiary && !data->first)
++    if (data->collection_id == 0)
 +        return 0;
 +
-+    first_name = krb5_krcc_get_ring_name(data->name);
-+
-+    if (data->first && first_name[0] != '\0') {
-+        /* first call in, search for a key matching data->name
-+         * which is the primary or the named subsidiary */
-+        ccache_id = keyctl_search(data->ring_id, KRCC_KEY_TYPE_KEYRING,
-+                                  first_name, 0);
-+        if (ccache_id == -1) {
-+            /* not found, try with regular search */
-+            ccache_id = 0;
-+            data->first = FALSE;
-+        }
-+        /* use description to hold 'name' so it will be properly freed */
-+        name = description = strdup(first_name);
-+        if (!name) {
-+            return ENOMEM;
-+        }
-+    } else {
-+        /* there is no first if the subsidiary is present but empty, this
-+         * happens when the default ccache is not of type KEYRING and a
-+         * program explicitly enumerates all cache collections */
++    if (data->first) {
++        /* Look for the primary cache for a collection cursor, or the
++         * subsidiary cache for a subsidiary cursor. */
 +        data->first = FALSE;
-+    }
-+
-+    if (data->first == FALSE) {
-+        pref = KRCC_NAME_PREFIX;
-+        preflen = strlen(pref);
-+        type = KRCC_KEY_TYPE_KEYRING ";";
-+        typelen = strlen(type);
-+
-+        for (cur_key = data->next_key; cur_key < data->num_keys; cur_key++) {
-+            /* free any previously returned description */
-+            free(description);
-+            description = NULL;
-+
-+            res = keyctl_describe_alloc(data->keys[cur_key], &description);
-+            if (res == -1) {
-+                DEBUG_PRINT(("krb5_krcc_ptcursor_next: Failed to get keyring "
-+                             "description for %ld\n", data->keys[cur_key]));
-+                /* try the next */
-+                continue;
-+            }
-+
-+            name = strrchr(description, ';');
-+            if (!name) {
-+                DEBUG_PRINT(("krb5_krcc_ptcursor_next: Keyring (%ld) "
-+                             "description [%s] has unknown format (missing "
-+                             "';')!\n", data->keys[cur_key], description));
-+                /* try the next */
-+                continue;
-+            }
-+            name++;
-+
-+            if (strncmp(type, description, typelen) != 0) {
-+                /* not a keyring, just skip */
-+                continue;
-+            }
-+
-+            if (strncmp(pref, name, preflen) == 0) {
-+                /* found, but skip first_name, handled earlier */
-+                if (strcmp(first_name, name) == 0)
-+                    continue;
-+
-+                /* a valid one */
-+                ccache_id = data->keys[cur_key];
-+                data->next_key = cur_key + 1;
-+                break;
-+            }
++        first_name = (data->primary_name != NULL) ? data->primary_name :
++            data->subsidiary_name;
++        cache_id = keyctl_search(data->collection_id, KRCC_KEY_TYPE_KEYRING,
++                                 first_name, 0);
++        if (cache_id != -1) {
++            return make_cache(data->collection_id, cache_id, data->anchor_name,
++                              data->collection_name, first_name, cache_out);
 +        }
 +    }
+ 
+-    val = sscanf(ids_buf, "%d:%d:%d", &session, &process, &thread);
+-    if (val != 3)
+-        goto out;
++    /* A subsidiary cursor yields at most the first cache. */
++    if (data->subsidiary_name != NULL)
++        return 0;
 +
-+    if (ccache_id == 0) {
-+        /* nothing found */
-+        free(description);
-+
-+        if (!krb5_krcc_check_legacy_session(data->ring_id, data->name,
-+                                            &name, &ccache_id))
-+            return 0;
-+
-+        /* legacy session and found original cache, point description at name
-+        *so that name will be properly freed later on */
-+        description = name;
-+    }
-+
-+    /* we found something */
++    keytype = KRCC_KEY_TYPE_KEYRING ";";
++    keytypelen = strlen(keytype);
 +
-+    if (data->first == TRUE) {
-+        /* we searched for the primary/subsidiary,
-+         * reset for the following searches */
-+        data->first = FALSE;
++    for (; data->next_key < data->num_keys; data->next_key++) {
++        /* Free any previously retrieved key description. */
++        free(description);
++        description = NULL;
++
++        /*
++         * Get the key description, which should have the form:
++         *   typename;UID;GID;permissions;description
++         */
++        key = data->keys[data->next_key];
++        if (keyctl_describe_alloc(key, &description) < 0)
++            continue;
++        sep = strrchr(description, ';');
++        if (sep == NULL)
++            continue;
++        subsidiary_name = sep + 1;
++
++        /* Skip this key if it isn't a keyring. */
++        if (strncmp(description, keytype, keytypelen) != 0)
++            continue;
++
++        /* Don't repeat the primary cache. */
++        if (strcmp(subsidiary_name, data->primary_name) == 0)
++            continue;
++
++        /* We found a valid key */
++        data->next_key++;
++        ret = make_cache(data->collection_id, key, data->anchor_name,
++                         data->collection_name, subsidiary_name, cache_out);
++        free(description);
++        return ret;
 +    }
-+
-+    residual = krb5_krcc_new_subsidiary(data->name, name);
-+    free(description); /* we don't need 'name' anymore */
-+    if (!residual)
-+        return ENOMEM;
-+
-+    kret = krb5_krcc_resolve_internal(data->ring_id, ccache_id,
-+                                      residual, cache_out);
-+
-+    free(residual);
-+    return kret;
+ 
+-    p->session = session;
+-    p->process = process;
+-    p->thread = thread;
++    free(description);
++    return 0;
 +}
-+
+ 
+-out:
+-    DEBUG_PRINT(("krb5_krcc_get_ring_ids: returning %d:%d:%d\n",
+-                 p->session, p->process, p->thread));
 +static krb5_error_code KRB5_CALLCONV
 +krb5_krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
 +{
 +    struct krcc_ptcursor_data *data = (*cursor)->data;
 +
-+    if (data) {
-+        free(data->name);
++    if (data != NULL) {
++        free(data->anchor_name);
++        free(data->collection_name);
++        free(data->subsidiary_name);
++        free(data->primary_name);
 +        free(data->keys);
 +        free(data);
 +    }
-+    (*cursor)->data = NULL;
 +    free(*cursor);
 +    *cursor = NULL;
      return 0;
@@ -1854,18 +1768,29 @@ index fd1bcec..f29c603 100644
 +krb5_krcc_switch_to(krb5_context context, krb5_ccache cache)
 +{
 +    krb5_krcc_data *data = cache->data;
-+    krb5_error_code kret;
-+    const char *ring_name;
-+
-+    ring_name = krb5_krcc_get_ring_name(data->name);
-+    kret = krb5_krcc_set_primary(context, ring_name, data->parent_id);
-+    return kret;
++    krb5_error_code ret;
++    char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
++    key_serial_t collection_id;
++
++    ret = parse_residual(data->name, &anchor_name, &collection_name,
++                         &subsidiary_name);
++    if (ret)
++        goto cleanup;
++    ret = get_collection(anchor_name, collection_name, &collection_id);
++    if (ret)
++        goto cleanup;
++    ret = set_primary_name(context, collection_id, subsidiary_name);
++cleanup:
++    free(anchor_name);
++    free(collection_name);
++    free(subsidiary_name);
++    return ret;
 +}
 +
  /*
   * ===============================================================
   * INTERNAL functions to parse a credential from a key payload
-@@ -1271,8 +2009,8 @@ out:
+@@ -1271,8 +1830,8 @@ out:
   * KRB5_CC_END - there were not len bytes available
   */
  static  krb5_error_code
@@ -1876,7 +1801,7 @@ index fd1bcec..f29c603 100644
  {
      DEBUG_PRINT(("krb5_krcc_parse: entered\n"));
  
-@@ -1290,8 +2028,8 @@ krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf,
+@@ -1290,8 +1849,8 @@ krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf,
   * and parse it into a credential structure.
   */
  static  krb5_error_code
@@ -1887,7 +1812,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_octet octet;
-@@ -1301,36 +2039,36 @@ krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds,
+@@ -1301,36 +1860,36 @@ krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds,
      /* Parse the pieces of the credential */
      bc.bpp = payload;
      bc.endp = bc.bpp + psize;
@@ -1934,7 +1859,7 @@ index fd1bcec..f29c603 100644
      CHECK_N_GO(kret, cleanticket);
  
      kret = KRB5_OK;
-@@ -1355,8 +2093,8 @@ out:
+@@ -1355,8 +1914,8 @@ out:
  }
  
  static  krb5_error_code
@@ -1945,7 +1870,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      register krb5_principal tmpprinc;
-@@ -1364,12 +2102,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
+@@ -1364,12 +1923,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
      int     i;
  
      /* Read principal type */
@@ -1960,7 +1885,7 @@ index fd1bcec..f29c603 100644
      if (kret != KRB5_OK)
          return kret;
  
-@@ -1380,12 +2118,7 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
+@@ -1380,12 +1939,7 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
      if (tmpprinc == NULL)
          return KRB5_CC_NOMEM;
      if (length) {
@@ -1974,7 +1899,7 @@ index fd1bcec..f29c603 100644
          if (tmpprinc->data == 0) {
              free(tmpprinc);
              return KRB5_CC_NOMEM;
-@@ -1396,15 +2129,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
+@@ -1396,15 +1950,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
      tmpprinc->length = length;
      tmpprinc->type = type;
  
@@ -1992,7 +1917,7 @@ index fd1bcec..f29c603 100644
          CHECK(kret);
      }
      *princ = tmpprinc;
-@@ -1412,16 +2142,16 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
+@@ -1412,16 +1963,16 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
  
  errout:
      while (--i >= 0)
@@ -2013,7 +1938,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_ui_2 ui2;
-@@ -1430,26 +2160,22 @@ krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id,
+@@ -1430,26 +1981,22 @@ krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id,
      keyblock->magic = KV5M_KEYBLOCK;
      keyblock->contents = 0;
  
@@ -2044,7 +1969,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
  
      return KRB5_OK;
-@@ -1460,25 +2186,25 @@ errout:
+@@ -1460,25 +2007,25 @@ errout:
  }
  
  static  krb5_error_code
@@ -2076,7 +2001,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
      t->renew_till = i;
  
-@@ -1488,8 +2214,8 @@ errout:
+@@ -1488,8 +2035,8 @@ errout:
  }
  
  static  krb5_error_code
@@ -2087,7 +2012,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_int32 len;
-@@ -1497,12 +2223,12 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
+@@ -1497,12 +2044,12 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
      data->magic = KV5M_DATA;
      data->data = 0;
  
@@ -2102,7 +2027,7 @@ index fd1bcec..f29c603 100644
          return KRB5_CC_NOMEM;
  
      if (data->length == 0) {
-@@ -1514,8 +2240,7 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
+@@ -1514,8 +2061,7 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
      if (data->data == NULL)
          return KRB5_CC_NOMEM;
  
@@ -2112,7 +2037,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
  
      data->data[data->length] = 0;       /* Null terminate, just in case.... */
-@@ -1527,13 +2252,12 @@ errout:
+@@ -1527,13 +2073,12 @@ errout:
  }
  
  static  krb5_error_code
@@ -2128,7 +2053,7 @@ index fd1bcec..f29c603 100644
      if (kret)
          return kret;
      *i = load_32_be(buf);
-@@ -1541,15 +2265,14 @@ krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i,
+@@ -1541,15 +2086,14 @@ krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i,
  }
  
  static  krb5_error_code
@@ -2148,7 +2073,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_int32 length;
-@@ -1559,18 +2282,17 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
+@@ -1559,18 +2103,17 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
      *addrs = 0;
  
      /* Read the number of components */
@@ -2171,7 +2096,7 @@ index fd1bcec..f29c603 100644
      if (*addrs == NULL)
          return KRB5_CC_NOMEM;
  
-@@ -1580,7 +2302,7 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
+@@ -1580,7 +2123,7 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
              krb5_free_addresses(context, *addrs);
              return KRB5_CC_NOMEM;
          }
@@ -2180,7 +2105,7 @@ index fd1bcec..f29c603 100644
          CHECK(kret);
      }
  
-@@ -1592,7 +2314,7 @@ errout:
+@@ -1592,7 +2135,7 @@ errout:
  }
  
  static  krb5_error_code
@@ -2189,7 +2114,7 @@ index fd1bcec..f29c603 100644
                       krb5_krcc_bc * bc)
  {
      krb5_error_code kret;
-@@ -1602,22 +2324,15 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
+@@ -1602,22 +2145,15 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
      addr->magic = KV5M_ADDRESS;
      addr->contents = 0;
  
@@ -2214,7 +2139,7 @@ index fd1bcec..f29c603 100644
      if (addr->length == 0)
          return KRB5_OK;
  
-@@ -1625,7 +2340,7 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
+@@ -1625,7 +2161,7 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
      if (addr->contents == NULL)
          return KRB5_CC_NOMEM;
  
@@ -2223,7 +2148,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
  
      return KRB5_OK;
-@@ -1636,8 +2351,8 @@ errout:
+@@ -1636,8 +2172,8 @@ errout:
  }
  
  static  krb5_error_code
@@ -2234,7 +2159,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_int32 length;
-@@ -1647,7 +2362,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
+@@ -1647,7 +2183,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
      *a = 0;
  
      /* Read the number of components */
@@ -2243,7 +2168,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
  
      if (length == 0)
-@@ -1657,11 +2372,10 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
+@@ -1657,11 +2193,10 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
       * Make *a able to hold length pointers to krb5_authdata structs
       * Add one extra for a null-terminated list
       */
@@ -2258,7 +2183,7 @@ index fd1bcec..f29c603 100644
      if (*a == NULL)
          return KRB5_CC_NOMEM;
  
-@@ -1672,7 +2386,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
+@@ -1672,7 +2207,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
              *a = NULL;
              return KRB5_CC_NOMEM;
          }
@@ -2267,7 +2192,7 @@ index fd1bcec..f29c603 100644
          CHECK(kret);
      }
  
-@@ -1686,8 +2400,8 @@ errout:
+@@ -1686,8 +2221,8 @@ errout:
  }
  
  static  krb5_error_code
@@ -2278,7 +2203,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_int32 int32;
-@@ -1696,21 +2410,14 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
+@@ -1696,21 +2231,14 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
      a->magic = KV5M_AUTHDATA;
      a->contents = NULL;
  
@@ -2302,7 +2227,7 @@ index fd1bcec..f29c603 100644
      if (a->length == 0)
          return KRB5_OK;
  
-@@ -1718,7 +2425,7 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
+@@ -1718,7 +2246,7 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
      if (a->contents == NULL)
          return KRB5_CC_NOMEM;
  
@@ -2311,7 +2236,7 @@ index fd1bcec..f29c603 100644
      CHECK(kret);
  
      return KRB5_OK;
-@@ -1730,13 +2437,12 @@ errout:
+@@ -1730,13 +2258,12 @@ errout:
  }
  
  static  krb5_error_code
@@ -2327,7 +2252,7 @@ index fd1bcec..f29c603 100644
      if (kret)
          return kret;
      *i = load_16_be(buf);
-@@ -1756,9 +2462,15 @@ krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i,
+@@ -1756,9 +2283,15 @@ krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i,
   * system errors
   */
  static  krb5_error_code
@@ -2345,7 +2270,7 @@ index fd1bcec..f29c603 100644
      if (bc->bpp + len > bc->endp)
          return KRB5_CC_WRITE;
  
-@@ -1769,29 +2481,26 @@ krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf,
+@@ -1769,29 +2302,26 @@ krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf,
  }
  
  static  krb5_error_code
@@ -2383,7 +2308,7 @@ index fd1bcec..f29c603 100644
          CHECK_OUT(kret);
      }
  
-@@ -1799,67 +2508,65 @@ krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id,
+@@ -1799,67 +2329,65 @@ krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id,
  }
  
  static  krb5_error_code
@@ -2472,7 +2397,7 @@ index fd1bcec..f29c603 100644
  {
      krb5_error_code kret;
      krb5_address **temp;
-@@ -1872,10 +2579,10 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
+@@ -1872,10 +2400,10 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
              length += 1;
      }
  
@@ -2485,7 +2410,7 @@ index fd1bcec..f29c603 100644
          CHECK_OUT(kret);
      }
  
-@@ -1883,21 +2590,21 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
+@@ -1883,21 +2411,21 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
  }
  
  static  krb5_error_code
@@ -2513,7 +2438,7 @@ index fd1bcec..f29c603 100644
                             krb5_authdata ** a, krb5_krcc_bc * bc)
  {
      krb5_error_code kret;
-@@ -1909,47 +2616,45 @@ krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id,
+@@ -1909,47 +2437,45 @@ krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id,
              length++;
      }
  
@@ -2572,7 +2497,7 @@ index fd1bcec..f29c603 100644
  }
  
  /*
-@@ -1965,11 +2670,55 @@ krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i,
+@@ -1965,11 +2491,55 @@ krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i,
   * Caller is responsible for freeing returned buffer.
   */
  static  krb5_error_code
@@ -2580,8 +2505,9 @@ index fd1bcec..f29c603 100644
 -                       krb5_creds * creds, char **datapp, unsigned int *lenptr)
 +krb5_krcc_unparse_cred(krb5_context context, krb5_creds * creds,
 +                       krb5_krcc_bc *bc)
-+{
-+    krb5_error_code kret;
+ {
+     krb5_error_code kret;
+-    char   *buf;
 +
 +    kret = krb5_krcc_unparse_principal(context, creds->client, bc);
 +    CHECK_OUT(kret);
@@ -2624,14 +2550,13 @@ index fd1bcec..f29c603 100644
 +static  krb5_error_code
 +krb5_krcc_unparse_cred_alloc(krb5_context context, krb5_creds * creds,
 +                             char **datapp, unsigned int *lenptr)
- {
-     krb5_error_code kret;
--    char   *buf;
++{
++    krb5_error_code kret;
 +    char *buf = NULL;
      krb5_krcc_bc bc;
  
      if (!creds || !datapp || !lenptr)
-@@ -1978,43 +2727,102 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
+@@ -1978,43 +2548,102 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
      *datapp = NULL;
      *lenptr = 0;
  
@@ -2758,7 +2683,7 @@ index fd1bcec..f29c603 100644
  
      /* Success! */
      *datapp = buf;
-@@ -2022,6 +2830,8 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
+@@ -2022,6 +2651,8 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
      kret = KRB5_OK;
  
  errout:
@@ -2767,7 +2692,7 @@ index fd1bcec..f29c603 100644
      return kret;
  }
  
-@@ -2065,15 +2875,15 @@ const krb5_cc_ops krb5_krcc_ops = {
+@@ -2065,15 +2696,15 @@ const krb5_cc_ops krb5_krcc_ops = {
      krb5_krcc_remove_cred,
      krb5_krcc_set_flags,
      krb5_krcc_get_flags,        /* added after 1.4 release */
@@ -2783,7 +2708,614 @@ index fd1bcec..f29c603 100644
      krb5_krcc_lock,
      krb5_krcc_unlock,
 -    NULL, /* switch_to */
-+    krb5_krcc_switch_to, /* switch_to */
++    krb5_krcc_switch_to,
  };
  
  #else /* !USE_KEYRING_CCACHE */
+diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c
+index e14ae7f..6069cab 100644
+--- a/src/lib/krb5/ccache/t_cc.c
++++ b/src/lib/krb5/ccache/t_cc.c
+@@ -25,6 +25,7 @@
+  */
+ 
+ #include "k5-int.h"
++#include "cc-int.h"
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include "autoconf.h"
+@@ -331,14 +332,14 @@ check_registered(krb5_context context, const char *prefix)
+     if(kret != KRB5_OK) {
+         if(kret == KRB5_CC_UNKNOWN_TYPE)
+             return 0;
+-        com_err("Checking on credential type", kret,prefix);
++        com_err("Checking on credential type", kret, "%s", prefix);
+         fflush(stderr);
+         return 0;
+     }
+ 
+     kret = krb5_cc_close(context, id);
+     if(kret != KRB5_OK) {
+-        com_err("Checking on credential type - closing", kret,prefix);
++        com_err("Checking on credential type - closing", kret, "%s", prefix);
+         fflush(stderr);
+     }
+ 
+@@ -425,8 +426,8 @@ main(void)
+     test_misc(context);
+     do_test(context, "");
+ 
+-    if(check_registered(context, "KEYRING:"))
+-        do_test(context, "KEYRING:");
++    if (check_registered(context, "KEYRING:process:"))
++        do_test(context, "KEYRING:process:");
+     else
+         printf("Skiping KEYRING: test - unregistered type\n");
+ 
+diff --git a/src/lib/krb5/ccache/t_cccol.c b/src/lib/krb5/ccache/t_cccol.c
+new file mode 100644
+index 0000000..444806e
+--- /dev/null
++++ b/src/lib/krb5/ccache/t_cccol.c
+@@ -0,0 +1,363 @@
++/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
++/* lib/krb5/ccache/t_cccol.py - Test ccache collection via API */
++/*
++ * Copyright (C) 2013 by the Massachusetts Institute of Technology.
++ * All rights reserved.
++ *
++ * Redistribution and use in source and binary forms, with or without
++ * modification, are permitted provided that the following conditions
++ * are met:
++ *
++ * * Redistributions of source code must retain the above copyright
++ *   notice, this list of conditions and the following disclaimer.
++ *
++ * * Redistributions in binary form must reproduce the above copyright
++ *   notice, this list of conditions and the following disclaimer in
++ *   the documentation and/or other materials provided with the
++ *   distribution.
++ *
++ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
++ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
++ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
++ * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
++ * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
++ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
++ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
++ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
++ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
++ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
++ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
++ * OF THE POSSIBILITY OF SUCH DAMAGE.
++ */
++
++#include <krb5.h>
++#include <stdio.h>
++#include <stdlib.h>
++#include <string.h>
++#include <assert.h>
++
++static krb5_context ctx;
++
++/* Check that code is 0.  Display an error message first if it is not. */
++static void
++check(krb5_error_code code)
++{
++    const char *errmsg;
++
++    if (code != 0) {
++        errmsg = krb5_get_error_message(ctx, code);
++        fprintf(stderr, "%s\n", errmsg);
++        krb5_free_error_message(ctx, errmsg);
++    }
++    assert(code == 0);
++}
++
++/* Construct a list of the names of each credential cache in the collection. */
++static void
++get_collection_names(char ***list_out, size_t *count_out)
++{
++    krb5_cccol_cursor cursor;
++    krb5_ccache cache;
++    char **list = NULL;
++    size_t count = 0;
++    char *name;
++
++    check(krb5_cccol_cursor_new(ctx, &cursor));
++    while (1) {
++        check(krb5_cccol_cursor_next(ctx, cursor, &cache));
++        if (cache == NULL)
++            break;
++        check(krb5_cc_get_full_name(ctx, cache, &name));
++        krb5_cc_close(ctx, cache);
++        list = realloc(list, (count + 1) * sizeof(*list));
++        assert(list != NULL);
++        list[count++] = name;
++    }
++    krb5_cccol_cursor_free(ctx, &cursor);
++    *list_out = list;
++    *count_out = count;
++}
++
++/* Return true if list contains name. */
++static krb5_boolean
++in_list(char **list, size_t count, const char *name)
++{
++    size_t i;
++
++    for (i = 0; i < count; i++) {
++        if (strcmp(list[i], name) == 0)
++            return TRUE;
++    }
++    return FALSE;
++}
++
++/* Release the memory for a list of credential cache names. */
++static void
++free_list(char **list, size_t count)
++{
++    size_t i;
++
++    for (i = 0; i < count; i++)
++        krb5_free_string(ctx, list[i]);
++    free(list);
++}
++
++/*
++ * Check that the cache names within the current collection begin with first
++ * (unless first is NULL), that the other elements match the remaining
++ * arguments in some order.  others must be the number of additional cache
++ * names.
++ */
++static void
++check_collection(const char *first, size_t others, ...)
++{
++    va_list ap;
++    char **list;
++    size_t count, i;
++    const char *name;
++
++    get_collection_names(&list, &count);
++    if (first != NULL) {
++        assert(strcmp(first, list[0]) == 0);
++        assert(count == others + 1);
++    } else {
++        assert(count == others);
++    }
++    va_start(ap, others);
++    for (i = 0; i < others; i++) {
++        name = va_arg(ap, const char *);
++        assert(in_list(list, count, name));
++    }
++    va_end(ap);
++    free_list(list, count);
++}
++
++/* Check that the name of cache matches expected_name. */
++static void
++check_name(krb5_ccache cache, const char *expected_name)
++{
++    char *name;
++
++    check(krb5_cc_get_full_name(ctx, cache, &name));
++    assert(strcmp(name, expected_name) == 0);
++    krb5_free_string(ctx, name);
++}
++
++/* Check that when collection_name is resolved, the resulting cache's name
++ * matches expected_name. */
++static void
++check_primary_name(const char *collection_name, const char *expected_name)
++{
++    krb5_ccache cache;
++
++    check(krb5_cc_resolve(ctx, collection_name, &cache));
++    check_name(cache, expected_name);
++    krb5_cc_close(ctx, cache);
++}
++
++/* Check that when name is resolved, the resulting cache's principal matches
++ * expected_princ, or has no principal if expected_princ is NULL. */
++static void
++check_princ(const char *name, krb5_principal expected_princ)
++{
++    krb5_ccache cache;
++    krb5_principal princ;
++
++    check(krb5_cc_resolve(ctx, name, &cache));
++    if (expected_princ != NULL) {
++        check(krb5_cc_get_principal(ctx, cache, &princ));
++        assert(krb5_principal_compare(ctx, princ, expected_princ));
++        krb5_free_principal(ctx, princ);
++    } else {
++        assert(krb5_cc_get_principal(ctx, cache, &princ) != 0);
++    }
++    krb5_cc_close(ctx, cache);
++}
++
++/* Check that krb5_cc_cache_match on princ returns a cache whose name matches
++ * expected_name, or that the match fails if expected_name is NULL. */
++static void
++check_match(krb5_principal princ, const char *expected_name)
++{
++    krb5_ccache cache;
++
++    if (expected_name != NULL) {
++        check(krb5_cc_cache_match(ctx, princ, &cache));
++        check_name(cache, expected_name);
++        krb5_cc_close(ctx, cache);
++    } else {
++        assert(krb5_cc_cache_match(ctx, princ, &cache) != 0);
++    }
++}
++
++int
++main(int argc, char **argv)
++{
++    krb5_ccache ccinitial, ccu1, ccu2;
++    krb5_principal princ1, princ2, princ3;
++    const char *collection_name, *typename;
++    char *initial_primary_name, *unique1_name, *unique2_name;
++
++    /*
++     * Get the collection name from the command line.  This is a ccache name
++     * with collection semantics, like DIR:/path/to/directory.  This test
++     * program assumes that the collection is empty to start with.
++     */
++    assert(argc == 2);
++    collection_name = argv[1];
++
++    /*
++     * Set the default ccache for the context to be the collection name, so the
++     * library can find the collection.
++     */
++    check(krb5_init_context(&ctx));
++    check(krb5_cc_set_default_name(ctx, collection_name));
++
++    /*
++     * Resolve the collection name.  Since the collection is empty, this should
++     * generate a subsidiary name of an uninitialized cache.  Getting the name
++     * of the resulting cache should give us the subsidiary name, not the
++     * collection name.  This resulting subsidiary name should be consistent if
++     * we resolve the collection name again, and the collection should still be
++     * empty since we haven't initialized the cache.
++     */
++    check(krb5_cc_resolve(ctx, collection_name, &ccinitial));
++    check(krb5_cc_get_full_name(ctx, ccinitial, &initial_primary_name));
++    assert(strcmp(initial_primary_name, collection_name) != 0);
++    check_primary_name(collection_name, initial_primary_name);
++    check_collection(NULL, 0);
++    check_princ(collection_name, NULL);
++    check_princ(initial_primary_name, NULL);
++
++    /*
++     * Before initializing the primary ccache, generate and initialize two
++     * unique caches of the collection's type.  Check that the cache names
++     * resolve to the generated caches and appear in the collection.  (They
++     * might appear before being initialized; that's not currently considered
++     * important).  The primary cache for the collection should remain as the
++     * unitialized cache from the previous step.
++     */
++    typename = krb5_cc_get_type(ctx, ccinitial);
++    check(krb5_cc_new_unique(ctx, typename, NULL, &ccu1));
++    check(krb5_cc_get_full_name(ctx, ccu1, &unique1_name));
++    check(krb5_parse_name(ctx, "princ1 at X", &princ1));
++    check(krb5_cc_initialize(ctx, ccu1, princ1));
++    check_princ(unique1_name, princ1);
++    check_match(princ1, unique1_name);
++    check_collection(NULL, 1, unique1_name);
++    check(krb5_cc_new_unique(ctx, typename, NULL, &ccu2));
++    check(krb5_cc_get_full_name(ctx, ccu2, &unique2_name));
++    check(krb5_parse_name(ctx, "princ2 at X", &princ2));
++    check(krb5_cc_initialize(ctx, ccu2, princ2));
++    check_princ(unique2_name, princ2);
++    check_match(princ1, unique1_name);
++    check_match(princ2, unique2_name);
++    check_collection(NULL, 2, unique1_name, unique2_name);
++    assert(strcmp(unique1_name, initial_primary_name) != 0);
++    assert(strcmp(unique1_name, collection_name) != 0);
++    assert(strcmp(unique2_name, initial_primary_name) != 0);
++    assert(strcmp(unique2_name, collection_name) != 0);
++    assert(strcmp(unique2_name, unique1_name) != 0);
++    check_primary_name(collection_name, initial_primary_name);
++
++    /*
++     * Initialize the initial primary cache.  Make sure it didn't change names,
++     * that the previously retrieved name and the collection name both resolve
++     * to the initialized cache, and that it now appears first in the
++     * collection.
++     */
++    check(krb5_parse_name(ctx, "princ3 at X", &princ3));
++    check(krb5_cc_initialize(ctx, ccinitial, princ3));
++    check_name(ccinitial, initial_primary_name);
++    check_princ(initial_primary_name, princ3);
++    check_princ(collection_name, princ3);
++    check_match(princ3, initial_primary_name);
++    check_collection(initial_primary_name, 2, unique1_name, unique2_name);
++
++    /*
++     * Switch the primary cache to each cache we have open.  One each switch,
++     * check the primary name, check that the collection resolves to the
++     * expected cache, and check that the new primary name appears first in the
++     * collection.
++     */
++    check(krb5_cc_switch(ctx, ccu1));
++    check_primary_name(collection_name, unique1_name);
++    check_princ(collection_name, princ1);
++    check_collection(unique1_name, 2, initial_primary_name, unique2_name);
++    check(krb5_cc_switch(ctx, ccu2));
++    check_primary_name(collection_name, unique2_name);
++    check_princ(collection_name, princ2);
++    check_collection(unique2_name, 2, initial_primary_name, unique1_name);
++    check(krb5_cc_switch(ctx, ccinitial));
++    check_primary_name(collection_name, initial_primary_name);
++    check_princ(collection_name, princ3);
++    check_collection(initial_primary_name, 2, unique1_name, unique2_name);
++
++    /*
++     * Temporarily set the context default ccache to a subsidiary name, and
++     * check that iterating over the collection yields that subsidiary cache
++     * and no others.
++     */
++    check(krb5_cc_set_default_name(ctx, unique1_name));
++    check_collection(unique1_name, 0);
++    check(krb5_cc_set_default_name(ctx, collection_name));
++
++    /*
++     * Destroy the primary cache.  Make sure this causes both the initial
++     * primary name and the collection name to resolve to an uninitialized
++     * cache.  Make sure the primary name doesn't change and doesn't appear in
++     * the collection any more.
++     */
++    check(krb5_cc_destroy(ctx, ccinitial));
++    check_princ(initial_primary_name, NULL);
++    check_princ(collection_name, NULL);
++    check_primary_name(collection_name, initial_primary_name);
++    check_match(princ1, unique1_name);
++    check_match(princ2, unique2_name);
++    check_match(princ3, NULL);
++    check_collection(NULL, 2, unique1_name, unique2_name);
++
++    /*
++     * Switch to the first unique cache after destroying the primary cache.
++     * Check that the collection name resolves to this cache and that the new
++     * primary name appears first in the collection.
++     */
++    check(krb5_cc_switch(ctx, ccu1));
++    check_primary_name(collection_name, unique1_name);
++    check_princ(collection_name, princ1);
++    check_collection(unique1_name, 1, unique2_name);
++
++    /*
++     * Destroy the second unique cache (which is not the current primary),
++     * check that it is on longer initialized, and check that it no longer
++     * appears in the collection.  Check that destroying the non-primary cache
++     * doesn't affect the primary name.
++     */
++    check(krb5_cc_destroy(ctx, ccu2));
++    check_princ(unique2_name, NULL);
++    check_match(princ2, NULL);
++    check_collection(unique1_name, 0);
++    check_primary_name(collection_name, unique1_name);
++    check_match(princ1, unique1_name);
++    check_princ(collection_name, princ1);
++
++    /*
++     * Destroy the first unique cache.  Check that the collection is empty and
++     * still has the same primary name.
++     */
++    check(krb5_cc_destroy(ctx, ccu1));
++    check_princ(unique1_name, NULL);
++    check_princ(collection_name, NULL);
++    check_primary_name(collection_name, unique1_name);
++    check_match(princ1, NULL);
++    check_collection(NULL, 0);
++
++    krb5_free_string(ctx, initial_primary_name);
++    krb5_free_string(ctx, unique1_name);
++    krb5_free_string(ctx, unique2_name);
++    krb5_free_principal(ctx, princ1);
++    krb5_free_principal(ctx, princ2);
++    krb5_free_principal(ctx, princ3);
++    krb5_free_context(ctx);
++    return 0;
++}
+diff --git a/src/lib/krb5/ccache/t_cccol.py b/src/lib/krb5/ccache/t_cccol.py
+index 8c459dd..e762625 100644
+--- a/src/lib/krb5/ccache/t_cccol.py
++++ b/src/lib/krb5/ccache/t_cccol.py
+@@ -1,6 +1,46 @@
+ #!/usr/bin/python
+ from k5test import *
+ 
++realm = K5Realm(create_kdb=False)
++
++keyctl = which('keyctl')
++out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1)
++test_keyring = (keyctl is not None and
++                'Unknown credential cache type' not in out)
++
++# Run the collection test program against each collection-enabled type.
++realm.run(['./t_cccol', 'DIR:' + os.path.join(realm.testdir, 'cc')])
++if test_keyring:
++    # Use the test directory as the collection name to avoid colliding
++    # with other build trees.
++    cname = realm.testdir
++
++    # Remove any keys left behind by previous failed test runs.
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
++    realm.run(['keyctl', 'purge', 'keyring', cname])
++    out = realm.run(['keyctl', 'list', '@u'])
++    if ('keyring: _krb_' + cname + '\n') in out:
++        id = realm.run(['keyctl', 'search', '@u', 'keyring', '_krb_' + cname])
++        realm.run(['keyctl', 'unlink', id.strip(), '@u'])
++
++    # Run test program over each subtype, cleaning up as we go.  Don't
++    # test the persistent subtype, since it supports only one
++    # collection and might be in actual use.
++    realm.run(['./t_cccol', 'KEYRING:' + cname])
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
++    realm.run(['./t_cccol', 'KEYRING:legacy:' + cname])
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
++    realm.run(['./t_cccol', 'KEYRING:session:' + cname])
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
++    realm.run(['./t_cccol', 'KEYRING:user:' + cname])
++    id = realm.run(['keyctl', 'search', '@u', 'keyring', '_krb_' + cname])
++    realm.run(['keyctl', 'unlink', id.strip(), '@u'])
++    realm.run(['./t_cccol', 'KEYRING:process:abcd'])
++    realm.run(['./t_cccol', 'KEYRING:thread:abcd'])
++
++realm.stop()
++
++# Test cursor semantics using real ccaches.
+ realm = K5Realm(create_host=False)
+ 
+ realm.addprinc('alice', password('alice'))
+@@ -11,12 +51,25 @@ dccname = 'DIR:%s' % ccdir
+ duser = 'DIR::%s/tkt1' % ccdir
+ dalice = 'DIR::%s/tkt2' % ccdir
+ dbob = 'DIR::%s/tkt3' % ccdir
++dnoent = 'DIR::%s/noent' % ccdir
+ realm.kinit('user', password('user'), flags=['-c', duser])
+ realm.kinit('alice', password('alice'), flags=['-c', dalice])
+ realm.kinit('bob', password('bob'), flags=['-c', dbob])
+ 
++if test_keyring:
++    cname = realm.testdir
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
++    krccname = 'KEYRING:session:' + cname
++    kruser = '%s:tkt1' % krccname
++    kralice = '%s:tkt2' % krccname
++    krbob = '%s:tkt3' % krccname
++    krnoent = '%s:noent' % krccname
++    realm.kinit('user', password('user'), flags=['-c', kruser])
++    realm.kinit('alice', password('alice'), flags=['-c', kralice])
++    realm.kinit('bob', password('bob'), flags=['-c', krbob])
++
+ def cursor_test(testname, args, expected):
+-    outlines = realm.run_as_client(['./t_cccursor'] + args).splitlines()
++    outlines = realm.run(['./t_cccursor'] + args).splitlines()
+     outlines.sort()
+     expected.sort()
+     if outlines != expected:
+@@ -30,21 +83,33 @@ cursor_test('file-default2', [realm.ccache], [fccname])
+ cursor_test('file-default3', [fccname], [fccname])
+ 
+ cursor_test('dir', [dccname], [duser, dalice, dbob])
++cursor_test('dir-subsidiary', [duser], [duser])
++cursor_test('dir-nofile', [dnoent], [])
++
++if test_keyring:
++    cursor_test('keyring', [krccname], [kruser, kralice, krbob])
++    cursor_test('keyring-subsidiary', [kruser], [kruser])
++    cursor_test('keyring-noent', [krnoent], [])
+ 
+ mfoo = 'MEMORY:foo'
+ mbar = 'MEMORY:bar'
+ cursor_test('filemem', [fccname, mfoo, mbar], [fccname, mfoo, mbar])
+ cursor_test('dirmem', [dccname, mfoo], [duser, dalice, dbob, mfoo])
++if test_keyring:
++    cursor_test('keyringmem', [krccname, mfoo], [kruser, kralice, krbob, mfoo])
+ 
+ # Test krb5_cccol_have_content.
+-realm.run_as_client(['./t_cccursor', dccname, 'CONTENT'])
+-realm.run_as_client(['./t_cccursor', fccname, 'CONTENT'])
+-realm.run_as_client(['./t_cccursor', realm.ccache, 'CONTENT'])
+-realm.run_as_client(['./t_cccursor', mfoo, 'CONTENT'], expected_code=1)
++realm.run(['./t_cccursor', dccname, 'CONTENT'])
++realm.run(['./t_cccursor', fccname, 'CONTENT'])
++realm.run(['./t_cccursor', realm.ccache, 'CONTENT'])
++realm.run(['./t_cccursor', mfoo, 'CONTENT'], expected_code=1)
++if test_keyring:
++    realm.run(['./t_cccursor', krccname, 'CONTENT'])
++    realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname])
+ 
+ # Make sure FILE doesn't yield a nonexistent default cache.
+-realm.run_as_client([kdestroy])
++realm.run([kdestroy])
+ cursor_test('noexist', [], [])
+-realm.run_as_client(['./t_cccursor', fccname, 'CONTENT'], expected_code=1)
++realm.run(['./t_cccursor', fccname, 'CONTENT'], expected_code=1)
+ 
+ success('Renewing credentials')
+diff --git a/src/util/k5test.py b/src/util/k5test.py
+index 3400154..aead832 100644
+--- a/src/util/k5test.py
++++ b/src/util/k5test.py
+@@ -142,6 +133,9 @@ Scripts may use the following functions and variables:
+   added newline) in testlog, and write it to stdout if running
+   verbosely.
+ 
++* which(progname): Return the location of progname in the executable
++  path, or None if it is not found.
++
+ * password(name): Return a weakly random password based on name.  The
+   password will be consistent across calls with the same name.
+ 
+@@ -388,6 +374,16 @@ def output(msg, force_verbose=False):
+         sys.stdout.write(msg)
+ 
+ 
++# Return the location of progname in the executable path, or None if
++# it is not found.
++def which(progname):
++    for dir in os.environ["PATH"].split(os.pathsep):
++        path = os.path.join(dir, progname)
++        if os.access(path, os.X_OK):
++            return path
++    return None
++
++
+ def password(name):
+     """Choose a weakly random password from name, consistent across calls."""
+     return name + str(os.getpid())
+@@ -880,6 +880,11 @@ class K5Realm(object):
+         env['KPROP_PORT'] = str(self.portbase + 3)
+         return env
+ 
++    def run(self, args, env=None, **keywords):
++        if env is None:
++            env = self.env_client
++        return _run_cmd(args, env, **keywords)
++
+     def run_as_client(self, args, **keywords):
+         return _run_cmd(args, self.env_client, **keywords)
+ 
+diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in
+index f64226b..ad53e65 100644
+--- a/src/lib/krb5/ccache/Makefile.in
++++ b/src/lib/krb5/ccache/Makefile.in
+@@ -71,6 +66,7 @@ SRCS=	$(srcdir)/ccbase.c \
+ 
+ EXTRADEPSRCS= \
+ 	$(srcdir)/t_cc.c \
++	$(srcdir)/t_cccol.c \
+ 	$(srcdir)/t_cccursor.c
+ 
+ ##DOS##OBJS=$(OBJS) $(OUTPRE)ccfns.$(OBJEXT)
+@@ -108,6 +104,10 @@ T_CC_OBJS=t_cc.o
+ t_cc: $(T_CC_OBJS) $(KRB5_BASE_DEPLIBS)
+ 	$(CC_LINK) -o t_cc $(T_CC_OBJS) $(KRB5_BASE_LIBS)
+ 
++T_CCCOL_OBJS = t_cccol.o
++t_cccol: $(T_CCCOL_OBJS) $(KRB5_BASE_DEPLIBS)
++	$(CC_LINK) -o $@ $(T_CCCOL_OBJS) $(KRB5_BASE_LIBS)
++
+ T_CCCURSOR_OBJS = t_cccursor.o
+ t_cccursor: $(T_CCCURSOR_OBJS) $(KRB5_BASE_DEPLIBS)
+ 	$(CC_LINK) -o $@ $(T_CCCURSOR_OBJS) $(KRB5_BASE_LIBS)
+@@ -116,11 +116,11 @@ check-unix:: t_cc
+ 	KRB5_CONFIG=$(srcdir)/t_krb5.conf ; export KRB5_CONFIG ;\
+ 	$(RUN_SETUP) $(VALGRIND) ./t_cc
+ 
+-check-pytests:: t_cccursor
++check-pytests:: t_cccursor t_cccol
+ 	$(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS)
+ 
+ clean-unix::
+-	$(RM) t_cc t_cc.o t_cccursor t_cccursor.o
++	$(RM) t_cc t_cc.o t_cccursor t_cccursor.o t_cccol t_cccol.o
+ 
+ ##WIN32## $(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(top_srcdir)/include/k5-int.h $(BUILDTOP)/include/krb5/osconf.h $(BUILDTOP)/include/krb5/autoconf.h $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS)
+ 


More information about the scm-commits mailing list