Blob Blame History Raw
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..7be77c2 100644
--- a/src/aclocal.m4
+++ b/src/aclocal.m4
@@ -89,6 +89,7 @@ KRB5_AC_INITFINI
 KRB5_AC_ENABLE_THREADS
 KRB5_AC_FIND_DLOPEN
 KRB5_AC_KEYRING_CCACHE
+KRB5_AC_PERSISTENT_KEYRING
 ])dnl
 
 dnl Maintainer mode, akin to what automake provides, 'cept we don't
@@ -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,
+      [AC_DEFINE(HAVE_PERSISTENT_KEYRING, 1,
+                 [Define if persistent keyrings are supported])
+      ]))
+])dnl
+dnl
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..795ccd6 100644
--- a/src/lib/krb5/ccache/cc_keyring.c
+++ b/src/lib/krb5/ccache/cc_keyring.c
@@ -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
+ * 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.
+ *
+ * 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,7 +126,20 @@ debug_print(char *fmt, ...)
 #endif
 
 /*
- * We always use "user" key type
+ * 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"
 
@@ -117,20 +155,6 @@ debug_print(char *fmt, ...)
 #define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__"
 
 /*
- * XXX The following two really belong in some external
- * header since outside programs will need to use these
- * same names.
- */
-/*
- * Special name for key to communicate key serial numbers
- * This is used by the Linux gssd process to pass the
- * user's keyring values it gets in an upcall.
- * The format of the contents should be
- *    <session_key>:<process_key>:<thread_key>
- */
-#define KRCC_SPEC_IDS_KEYNAME "_gssd_keyring_ids_"
-
-/*
  * 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 +163,55 @@ debug_print(char *fmt, ...)
  */
 #define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__"
 
+/*
+ * 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_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
 
 /* Hopefully big enough to hold a serialized credential */
-#define GUESS_CRED_SIZE 4096
-
-#define ALLOC(NUM,TYPE)                         \
-    (((NUM) <= (((size_t)0-1)/ sizeof(TYPE)))   \
-     ? (TYPE *) calloc((NUM), sizeof(TYPE))     \
-     : (errno = ENOMEM,(TYPE *) 0))
+#define MAX_CRED_SIZE (1024*1024)
 
 #define CHECK_N_GO(ret, errdest) if (ret != KRB5_OK) goto errdest
 #define CHECK(ret) if (ret != KRB5_OK) goto errout
 #define CHECK_OUT(ret) if (ret != KRB5_OK) return ret
 
-typedef struct krb5_krcc_ring_ids {
-    key_serial_t        session;
-    key_serial_t        process;
-    key_serial_t        thread;
-} krb5_krcc_ring_ids_t;
-
 typedef struct _krb5_krcc_cursor
 {
     int     numkeys;
@@ -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;
+    krb5_boolean is_legacy_type;
 } krb5_krcc_data;
 
 /* Passed internally to assure we don't go past the bounds of our buffer */
@@ -190,6 +242,7 @@ typedef struct _krb5_krcc_buffer_cursor
 {
     char   *bpp;
     char   *endp;
+    size_t  size;               /* For dry-run length calculation */
 } krb5_krcc_bc;
 
 /* Global mutex */
@@ -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);
 
+static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_new
+(krb5_context context, krb5_cc_ptcursor *cursor_out);
+
+static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_next
+(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *cache_out);
+
+static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_free
+(krb5_context context, krb5_cc_ptcursor *cursor);
+
+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
  */
@@ -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);
-
 /* 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,
- krb5_krcc_bc * bc);
+(krb5_context, krb5_pointer buf, unsigned int len, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_cred
-(krb5_context context, krb5_ccache id, krb5_creds * creds,
- char *payload, int psize);
+(krb5_context context, krb5_creds * creds, char *payload, int psize);
 static krb5_error_code krb5_krcc_parse_principal
-(krb5_context context, krb5_ccache id, krb5_principal * princ,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_principal * princ, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_keyblock
-(krb5_context context, krb5_ccache id, krb5_keyblock * keyblock,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_keyblock * keyblock, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_times
-(krb5_context context, krb5_ccache id, krb5_ticket_times * t,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_ticket_times * t, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_krb5data
-(krb5_context context, krb5_ccache id, krb5_data * data,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_data * data, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_int32
-(krb5_context context, krb5_ccache id, krb5_int32 * i, krb5_krcc_bc * bc);
+(krb5_context context, krb5_int32 * i, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_octet
-(krb5_context context, krb5_ccache id, krb5_octet * octet,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_octet * octet, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_addrs
-(krb5_context context, krb5_ccache id, krb5_address *** a,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_address *** a, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_addr
-(krb5_context context, krb5_ccache id, krb5_address * a,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_address * a, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_authdata
-(krb5_context context, krb5_ccache id, krb5_authdata *** ad,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_authdata *** ad, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_authdatum
-(krb5_context context, krb5_ccache id, krb5_authdata * ad,
- krb5_krcc_bc * bc);
+(krb5_context context, krb5_authdata * ad, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_parse_ui_2
-(krb5_context, krb5_ccache id, krb5_ui_2 * i, krb5_krcc_bc * bc);
+(krb5_context, krb5_ui_2 * i, krb5_krcc_bc * bc);
 
 /* Routines to unparse a cred structure into keyring key */
 static krb5_error_code krb5_krcc_unparse
-(krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len,
- krb5_krcc_bc * bc);
-static krb5_error_code krb5_krcc_unparse_cred
-(krb5_context context, krb5_ccache id, krb5_creds * creds,
+(krb5_context, krb5_pointer buf, unsigned int len, krb5_krcc_bc * bc);
+static krb5_error_code krb5_krcc_unparse_cred_alloc
+(krb5_context context, krb5_creds * creds,
  char **datapp, unsigned int *lenptr);
+static krb5_error_code krb5_krcc_unparse_cred
+(krb5_context context, krb5_creds * creds, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_principal
-(krb5_context, krb5_ccache id, krb5_principal princ, krb5_krcc_bc * bc);
+(krb5_context, krb5_principal princ, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_keyblock
-(krb5_context, krb5_ccache id, krb5_keyblock * keyblock,
- krb5_krcc_bc * bc);
+(krb5_context, krb5_keyblock * keyblock, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_times
-(krb5_context, krb5_ccache id, krb5_ticket_times * t, krb5_krcc_bc * bc);
+(krb5_context, krb5_ticket_times * t, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_krb5data
-(krb5_context, krb5_ccache id, krb5_data * data, krb5_krcc_bc * bc);
+(krb5_context, krb5_data * data, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_int32
-(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);
+(krb5_context, krb5_int32 i, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_octet
-(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);
+(krb5_context, krb5_int32 i, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_addrs
-(krb5_context, krb5_ccache, krb5_address ** a, krb5_krcc_bc * bc);
+(krb5_context, krb5_address ** a, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_addr
-(krb5_context, krb5_ccache, krb5_address * a, krb5_krcc_bc * bc);
+(krb5_context, krb5_address * a, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_authdata
-(krb5_context, krb5_ccache, krb5_authdata ** ad, krb5_krcc_bc * bc);
+(krb5_context, krb5_authdata ** ad, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_authdatum
-(krb5_context, krb5_ccache, krb5_authdata * ad, krb5_krcc_bc * bc);
+(krb5_context, krb5_authdata * ad, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_ui_4
-(krb5_context, krb5_ccache id, krb5_ui_4 i, krb5_krcc_bc * bc);
+(krb5_context, krb5_ui_4 i, krb5_krcc_bc * bc);
 static krb5_error_code krb5_krcc_unparse_ui_2
-(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc);
+(krb5_context, krb5_int32 i, krb5_krcc_bc * bc);
 static void krb5_krcc_update_change_time
 (krb5_krcc_data *);
 
+static krb5_error_code
+krb5_krcc_parse_index(krb5_context context, krb5_int32 *version,
+                      char **primary, void *payload, int psize);
+static krb5_error_code
+krb5_krcc_unparse_index(krb5_context context, krb5_int32 version,
+                        const char *primary, void **datapp, int *lenptr);
+
 /* Note the following is a stub function for Linux */
 extern krb5_error_code krb5_change_cache(void);
 
 /*
- * 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);
+    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;
+    const char *cache_name, *p;
 
     DEBUG_PRINT(("krb5_krcc_initialize: entered\n"));
 
-    kret = k5_cc_mutex_lock(context, &((krb5_krcc_data *) id->data)->lock);
-    if (kret)
-        return kret;
+    k5_cc_mutex_lock(context, &data->lock);
 
     kret = krb5_krcc_clearcache(context, id);
     if (kret != KRB5_OK)
         goto out;
 
+    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 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)
         krb5_change_cache();
 
 out:
-    k5_cc_mutex_unlock(context, &((krb5_krcc_data *) id->data)->lock);
+    k5_cc_mutex_unlock(context, &data->lock);
     return kret;
 }
 
@@ -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: 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->cache_id) {
+        res = keyctl_clear(d->cache_id);
+        if (res != 0)
+            return errno;
     }
-    d->numkeys = 0;
     d->princ_id = 0;
     krb5_krcc_update_change_time(d);
 
@@ -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)
 {
-    krb5_error_code kret;
+    krb5_error_code kret = 0;
     krb5_krcc_data *d;
     int     res;
 
@@ -492,30 +954,67 @@ krb5_krcc_destroy(krb5_context context, krb5_ccache id)
 
     d = (krb5_krcc_data *) id->data;
 
-    kret = k5_cc_mutex_lock(context, &d->lock);
-    if (kret)
-        return kret;
+    k5_cc_mutex_lock(context, &d->lock);
 
     krb5_krcc_clearcache(context, 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->cache_id) {
+        res = keyctl_unlink(d->cache_id, d->collection_id);
+        if (res < 0) {
+            kret = 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);
     free(id);
 
     krb5_change_cache();
 
-    return KRB5_OK;
+    return kret;
 }
 
+/* Create a cache handle for a cache ID. */
+static krb5_error_code
+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 ret;
+    krb5_ccache ccache = NULL;
+    krb5_krcc_data *d;
+    key_serial_t pkey = 0;
+
+    /* 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;
+
+    ret = krb5_krcc_new_data(anchor_name, collection_name, subsidiary_name,
+                             cache_id, collection_id, &d);
+    if (ret) {
+        free(ccache);
+        return ret;
+    }
+
+    d->princ_id = pkey;
+    ccache->ops = &krb5_krcc_ops;
+    ccache->data = d;
+    ccache->magic = KV5M_CCACHE;
+    *cache_out = ccache;
+    return 0;
+}
 
 /*
  * Requires:
@@ -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 *residual)
 {
-    krb5_ccache lid;
-    krb5_error_code kret;
-    krb5_krcc_data *d;
-    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;
-    const char *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"));
-        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;
-    } else if (strncmp(full_residual, "process:", 8) == 0) {
-        residual = full_residual + 8;
-        ring_id = ids.process;
-    } else {
-        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));
+    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);
-        if (key < 0) {
-            kret = errno;
-            DEBUG_PRINT(("krb5_krcc_resolve: Error adding new "
-                         "keyring '%s': %s\n", residual, strerror(errno)));
-            return kret;
-        }
-        DEBUG_PRINT(("krb5_krcc_resolve: new keyring '%s', "
-                     "key %d, added to keyring %d\n",
-                     residual, key, ring_id));
-    } 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,
-                             KRCC_SPEC_PRINC_KEYNAME, 0);
-        if (pkey < 0) {
-            DEBUG_PRINT(("krb5_krcc_resolve: Error locating principal "
-                         "info for existing ccache in ring %d: %s\n",
-                         key, strerror(errno)));
-            pkey = 0;
-        }
-        /* Determine how many keys exist */
-        nkeys = krb5_krcc_getkeycount(key);
-    }
-
-    lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
-    if (lid == NULL)
-        return KRB5_CC_NOMEM;
-
-
-    kret = krb5_krcc_new_data(residual, key, ring_id, &d);
-    if (kret) {
-        free(lid);
-        return kret;
-    }
-
-    DEBUG_PRINT(("krb5_krcc_resolve: ring_id %d, princ_id %d, "
-                 "nkeys %d\n", key, pkey, nkeys));
-    d->princ_id = pkey;
-    d->numkeys = nkeys;
-    lid->ops = &krb5_krcc_ops;
-    lid->data = d;
-    lid->magic = KV5M_CCACHE;
-    *id = lid;
-    return KRB5_OK;
+cleanup:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+    return ret;
 }
 
 /*
@@ -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_data *d;
-    unsigned int size;
-    int     res;
+    void *keys;
+    long size;
 
     DEBUG_PRINT(("krb5_krcc_start_seq_get: entered\n"));
 
     d = id->data;
-    kret = k5_cc_mutex_lock(context, &d->lock);
-    if (kret)
-        return kret;
-
-    /*
-     * Determine how many keys currently exist and update numkeys.
-     * We cannot depend on the current value of numkeys because
-     * the ccache may have been updated elsewhere
-     */
-    d->numkeys = krb5_krcc_getkeycount(d->ring_id);
+    k5_cc_mutex_lock(context, &d->lock);
 
-    size = sizeof(*krcursor) + ((d->numkeys + 1) * sizeof(key_serial_t));
-
-    krcursor = (krb5_krcc_cursor) malloc(size);
-    if (krcursor == NULL) {
+    if (!d->cache_id) {
         k5_cc_mutex_unlock(context, &d->lock);
-        return KRB5_CC_NOMEM;
+        return KRB5_FCC_NOFILE;
     }
 
-    krcursor->keys = (key_serial_t *) ((char *) krcursor + sizeof(*krcursor));
-    res = keyctl_read(d->ring_id, (char *) krcursor->keys,
-                      ((d->numkeys + 1) * sizeof(key_serial_t)));
-    if (res < 0 || res > ((d->numkeys + 1) * sizeof(key_serial_t))) {
-        DEBUG_PRINT(("Read %d bytes from keyring, numkeys %d: %s\n",
-                     res, d->numkeys, strerror(errno)));
-        free(krcursor);
+    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;
     }
 
-    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 +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 */
-    if (krcursor->currkey > krcursor->numkeys)
+    if (krcursor->currkey >= krcursor->numkeys)
         return KRB5_CC_END;
 
     /* If we're pointing at the entry with the principal, skip it */
     if (krcursor->keys[krcursor->currkey] == krcursor->princ_id) {
         krcursor->currkey++;
         /* Check if we have now reached the end */
-        if (krcursor->currkey > krcursor->numkeys)
+        if (krcursor->currkey >= krcursor->numkeys)
             return KRB5_CC_END;
     }
 
@@ -763,7 +1193,7 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id,
     }
     krcursor->currkey++;
 
-    kret = krb5_krcc_parse_cred(context, id, creds, payload, psize);
+    kret = krb5_krcc_parse_cred(context, creds, payload, psize);
 
 freepayload:
     if (payload) free(payload);
@@ -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)
 {
+    krb5_krcc_cursor krcursor = (krb5_krcc_cursor)*cursor;
     DEBUG_PRINT(("krb5_krcc_end_seq_get: entered\n"));
 
-    free(*cursor);
-    *cursor = 0L;
+    if (krcursor != NULL) {
+        free(krcursor->keys);
+        free(krcursor);
+    }
+    *cursor = NULL;
     return KRB5_OK;
 }
 
 /* Utility routine: Creates the back-end data for a keyring cache.
 
    Call with the global list lock held.  */
-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->numkeys = 0;
+    d->cache_id = cache_id;
+    d->collection_id = collection_id;
     d->changetime = 0;
+    d->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
     krb5_krcc_update_change_time(d);
 
     *datapp = d;
@@ -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;
     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;
+    key_serial_t collection_id;
+    key_serial_t cache_id = 0;
 
     DEBUG_PRINT(("krb5_krcc_generate_new: entered\n"));
 
+    /* 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 (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 cleanup;
+    }
+
     /* Allocate memory */
     lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache));
-    if (lid == NULL)
-        return KRB5_CC_NOMEM;
+    if (lid == NULL) {
+        kret = ENOMEM;
+        goto cleanup;
+    }
 
     lid->ops = &krb5_krcc_ops;
 
-    kret = k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
-    if (kret) {
-        free(lid);
-        return kret;
-    }
-
-/* 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
-#define ENOKEY          126     /* Required key not available */
-#endif
-#ifndef EKEYEXPIRED
-#define EKEYEXPIRED     127     /* Key has expired */
-#endif
-#ifndef EKEYREVOKED
-#define EKEYREVOKED     128     /* Key has been revoked */
-#endif
-#ifndef EKEYREJECTED
-#define EKEYREJECTED    129     /* Key was rejected by service */
-#endif
+    /* 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
-     * a unique name, or we get an error.
-     */
-    while (1) {
-        kret = krb5int_random_string(context, uniquename, sizeof(uniquename));
-        if (kret) {
-            k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
-            free(lid);
-            return kret;
-        }
+    kret = krb5_krcc_new_data(anchor_name, collection_name,
+                              new_subsidiary_name, cache_id, collection_id,
+                              &d);
+    if (kret)
+        goto cleanup;
 
-        DEBUG_PRINT(("krb5_krcc_generate_new: searching for name '%s'\n",
-                     uniquename));
-        key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0);
-        /*XXX*/ DEBUG_PRINT(("krb5_krcc_generate_new: after searching for '%s', key = %d, errno = %d\n", uniquename, key, errno));
-        if (key < 0 && errno == ENOKEY) {
-            /* name does not already exist, create it to reserve the name */
-            key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, ring_id);
-            if (key < 0) {
-                kret = errno;
-                DEBUG_PRINT(("krb5_krcc_generate_new: '%s' trying to "
-                             "create '%s'\n", strerror(errno), uniquename));
-                k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
-                return kret;
-            }
-            break;
-        }
-    }
+    lid->data = d;
+    krb5_change_cache();
 
-    kret = krb5_krcc_new_data(uniquename, key, ring_id, &d);
-    k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
+cleanup:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+    free(new_subsidiary_name);
+    free(new_residual);
     if (kret) {
         free(lid);
         return kret;
     }
-    lid->data = d;
     *id = lid;
-    krb5_change_cache();
     return KRB5_OK;
 }
 
@@ -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;
     char   *keyname = NULL;
 
     DEBUG_PRINT(("krb5_krcc_store: entered\n"));
 
-    kret = k5_cc_mutex_lock(context, &d->lock);
-    if (kret)
-        return kret;
+    k5_cc_mutex_lock(context, &d->lock);
+
+    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,24 +1469,19 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds)
     }
 
     /* Serialize credential into memory */
-    kret = krb5_krcc_unparse_cred(context, id, creds, &payload, &payloadlen);
+    kret = krb5_krcc_unparse_cred_alloc(context, creds, &payload, &payloadlen);
     if (kret != KRB5_OK)
         goto errout;
 
     /* Add new key (credentials) into keyring */
     DEBUG_PRINT(("krb5_krcc_store: adding new key '%s' to keyring %d\n",
-                 keyname, d->ring_id));
-    newkey = add_key(KRCC_KEY_TYPE_USER, keyname, payload,
-                     payloadlen, d->ring_id);
-    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;
+
+    krb5_krcc_update_change_time(d);
 
 errout:
     if (keyname)
@@ -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)
 {
-    krb5_error_code ret = 0;
     krb5_krcc_data *data = (krb5_krcc_data *) id->data;
 
-    *change_time = 0;
-
-    ret = k5_cc_mutex_lock(context, &data->lock);
-    if (!ret) {
-        *change_time = data->changetime;
-        k5_cc_mutex_unlock(context, &data->lock);
-    }
-
-    return ret;
+    k5_cc_mutex_lock(context, &data->lock);
+    *change_time = data->changetime;
+    k5_cc_mutex_unlock(context, &data->lock);
+    return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
 krb5_krcc_lock(krb5_context context, krb5_ccache id)
 {
-    krb5_error_code ret = 0;
     krb5_krcc_data *data = (krb5_krcc_data *) id->data;
-    ret = k5_cc_mutex_lock(context, &data->lock);
-    return ret;
+
+    k5_cc_mutex_lock(context, &data->lock);
+    return 0;
 }
 
 static krb5_error_code KRB5_CALLCONV
 krb5_krcc_unlock(krb5_context context, krb5_ccache id)
 {
-    krb5_error_code ret = 0;
     krb5_krcc_data *data = (krb5_krcc_data *) id->data;
-    ret = k5_cc_mutex_unlock(context, &data->lock);
-    return ret;
+
+    k5_cc_mutex_unlock(context, &data->lock);
+    return 0;
 }
 
 
@@ -1112,7 +1530,7 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
 {
     krb5_krcc_data *d;
     krb5_error_code kret;
-    char   *payload;
+    char *payload = NULL;
     key_serial_t newkey;
     unsigned int payloadsize;
     krb5_krcc_bc bc;
@@ -1121,14 +1539,19 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id,
 
     d = (krb5_krcc_data *) id->data;
 
-    payload = malloc(GUESS_CRED_SIZE);
+    /* Do a dry run first to calculate the size. */
+    bc.bpp = bc.endp = NULL;
+    bc.size = 0;
+    kret = krb5_krcc_unparse_principal(context, princ, &bc);
+    CHECK_N_GO(kret, errout);
+
+    /* Allocate a buffer and serialize for real. */
+    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 */
@@ -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 collection_id;
+    char *anchor_name;
+    char *collection_name;
+    char *subsidiary_name;
+    char *primary_name;
+    krb5_boolean first;
+    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;
+    krb5_error_code ret;
+    long size;
+
+    *cursor_out = NULL;
+
+    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;
+
+    ret = get_default(context, &data->anchor_name, &data->collection_name,
+                      &data->subsidiary_name);
+    if (ret)
+        goto error;
+
+    /* If there is no default collection, return an empty cursor. */
+    if (data->anchor_name == NULL) {
+        *cursor_out = cursor;
+        return 0;
+    }
 
-    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 key, cache_id = 0;
+    const char *first_name, *keytype, *sep, *subsidiary_name;
+    size_t keytypelen;
+    char *description = NULL;
+
+    *cache_out = NULL;
+
+    data = cursor->data;
+
+    /* No keyring available */
+    if (data->collection_id == 0)
+        return 0;
+
+    if (data->first) {
+        /* Look for the primary cache for a collection cursor, or the
+         * subsidiary cache for a subsidiary cursor. */
+        data->first = FALSE;
+        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;
+
+    keytype = KRCC_KEY_TYPE_KEYRING ";";
+    keytypelen = strlen(keytype);
+
+    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;
+    }
 
-    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 != NULL) {
+        free(data->anchor_name);
+        free(data->collection_name);
+        free(data->subsidiary_name);
+        free(data->primary_name);
+        free(data->keys);
+        free(data);
+    }
+    free(*cursor);
+    *cursor = NULL;
     return 0;
 }
 
+static krb5_error_code KRB5_CALLCONV
+krb5_krcc_switch_to(krb5_context context, krb5_ccache cache)
+{
+    krb5_krcc_data *data = cache->data;
+    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 +1830,8 @@ out:
  * KRB5_CC_END - there were not len bytes available
  */
 static  krb5_error_code
-krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf,
-                unsigned int len, krb5_krcc_bc * bc)
+krb5_krcc_parse(krb5_context context, krb5_pointer buf, unsigned int len,
+                krb5_krcc_bc * bc)
 {
     DEBUG_PRINT(("krb5_krcc_parse: entered\n"));
 
@@ -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
-krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds,
-                     char *payload, int psize)
+krb5_krcc_parse_cred(krb5_context context, krb5_creds * creds, char *payload,
+                     int psize)
 {
     krb5_error_code kret;
     krb5_octet octet;
@@ -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;
-    kret = krb5_krcc_parse_principal(context, id, &creds->client, &bc);
+    kret = krb5_krcc_parse_principal(context, &creds->client, &bc);
     CHECK_N_GO(kret, out);
 
-    kret = krb5_krcc_parse_principal(context, id, &creds->server, &bc);
+    kret = krb5_krcc_parse_principal(context, &creds->server, &bc);
     CHECK_N_GO(kret, cleanclient);
 
-    kret = krb5_krcc_parse_keyblock(context, id, &creds->keyblock, &bc);
+    kret = krb5_krcc_parse_keyblock(context, &creds->keyblock, &bc);
     CHECK_N_GO(kret, cleanserver);
 
-    kret = krb5_krcc_parse_times(context, id, &creds->times, &bc);
+    kret = krb5_krcc_parse_times(context, &creds->times, &bc);
     CHECK_N_GO(kret, cleanserver);
 
-    kret = krb5_krcc_parse_octet(context, id, &octet, &bc);
+    kret = krb5_krcc_parse_octet(context, &octet, &bc);
     CHECK_N_GO(kret, cleanserver);
     creds->is_skey = octet;
 
-    kret = krb5_krcc_parse_int32(context, id, &int32, &bc);
+    kret = krb5_krcc_parse_int32(context, &int32, &bc);
     CHECK_N_GO(kret, cleanserver);
     creds->ticket_flags = int32;
 
-    kret = krb5_krcc_parse_addrs(context, id, &creds->addresses, &bc);
+    kret = krb5_krcc_parse_addrs(context, &creds->addresses, &bc);
     CHECK_N_GO(kret, cleanblock);
 
-    kret = krb5_krcc_parse_authdata(context, id, &creds->authdata, &bc);
+    kret = krb5_krcc_parse_authdata(context, &creds->authdata, &bc);
     CHECK_N_GO(kret, cleanaddrs);
 
-    kret = krb5_krcc_parse_krb5data(context, id, &creds->ticket, &bc);
+    kret = krb5_krcc_parse_krb5data(context, &creds->ticket, &bc);
     CHECK_N_GO(kret, cleanauthdata);
 
-    kret = krb5_krcc_parse_krb5data(context, id, &creds->second_ticket, &bc);
+    kret = krb5_krcc_parse_krb5data(context, &creds->second_ticket, &bc);
     CHECK_N_GO(kret, cleanticket);
 
     kret = KRB5_OK;
@@ -1355,8 +1914,8 @@ out:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
-                          krb5_principal * princ, krb5_krcc_bc * bc)
+krb5_krcc_parse_principal(krb5_context context, krb5_principal * princ,
+                          krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     register krb5_principal tmpprinc;
@@ -1364,12 +1923,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
     int     i;
 
     /* Read principal type */
-    kret = krb5_krcc_parse_int32(context, id, &type, bc);
+    kret = krb5_krcc_parse_int32(context, &type, bc);
     if (kret != KRB5_OK)
         return kret;
 
     /* Read the number of components */
-    kret = krb5_krcc_parse_int32(context, id, &length, bc);
+    kret = krb5_krcc_parse_int32(context, &length, bc);
     if (kret != KRB5_OK)
         return kret;
 
@@ -1380,12 +1939,7 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
     if (tmpprinc == NULL)
         return KRB5_CC_NOMEM;
     if (length) {
-        size_t  msize = length;
-        if (msize != length) {
-            free(tmpprinc);
-            return KRB5_CC_NOMEM;
-        }
-        tmpprinc->data = ALLOC(msize, krb5_data);
+        tmpprinc->data = calloc(length, sizeof(krb5_data));
         if (tmpprinc->data == 0) {
             free(tmpprinc);
             return KRB5_CC_NOMEM;
@@ -1396,15 +1950,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
     tmpprinc->length = length;
     tmpprinc->type = type;
 
-    kret = krb5_krcc_parse_krb5data(context, id,
-                                    krb5_princ_realm(context, tmpprinc), bc);
+    kret = krb5_krcc_parse_krb5data(context, &tmpprinc->realm, bc);
     i = 0;
     CHECK(kret);
 
     for (i = 0; i < length; i++) {
-        kret = krb5_krcc_parse_krb5data(context, id,
-                                        krb5_princ_component(context, tmpprinc,
-                                                             i), bc);
+        kret = krb5_krcc_parse_krb5data(context, &tmpprinc->data[i], bc);
         CHECK(kret);
     }
     *princ = tmpprinc;
@@ -1412,16 +1963,16 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id,
 
 errout:
     while (--i >= 0)
-        free(krb5_princ_component(context, tmpprinc, i)->data);
-    free(krb5_princ_realm(context, tmpprinc)->data);
+        free(tmpprinc->data[i].data);
+    free(tmpprinc->realm.data);
     free(tmpprinc->data);
     free(tmpprinc);
     return kret;
 }
 
 static  krb5_error_code
-krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id,
-                         krb5_keyblock * keyblock, krb5_krcc_bc * bc)
+krb5_krcc_parse_keyblock(krb5_context context, krb5_keyblock * keyblock,
+                         krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_ui_2 ui2;
@@ -1430,26 +1981,22 @@ krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id,
     keyblock->magic = KV5M_KEYBLOCK;
     keyblock->contents = 0;
 
-    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
+    kret = krb5_krcc_parse_ui_2(context, &ui2, bc);
     CHECK(kret);
     keyblock->enctype = ui2;
 
-    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
+    kret = krb5_krcc_parse_int32(context, &int32, bc);
     CHECK(kret);
     if (int32 < 0)
         return KRB5_CC_NOMEM;
     keyblock->length = int32;
-    /* Overflow check.  */
-    if (keyblock->length != int32)
-        return KRB5_CC_NOMEM;
     if (keyblock->length == 0)
         return KRB5_OK;
-    keyblock->contents = ALLOC(keyblock->length, krb5_octet);
+    keyblock->contents = malloc(keyblock->length);
     if (keyblock->contents == NULL)
         return KRB5_CC_NOMEM;
 
-    kret = krb5_krcc_parse(context, id, keyblock->contents,
-                           keyblock->length, bc);
+    kret = krb5_krcc_parse(context, keyblock->contents, keyblock->length, bc);
     CHECK(kret);
 
     return KRB5_OK;
@@ -1460,25 +2007,25 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_times(krb5_context context, krb5_ccache id,
-                      krb5_ticket_times * t, krb5_krcc_bc * bc)
+krb5_krcc_parse_times(krb5_context context, krb5_ticket_times * t,
+                      krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 i;
 
-    kret = krb5_krcc_parse_int32(context, id, &i, bc);
+    kret = krb5_krcc_parse_int32(context, &i, bc);
     CHECK(kret);
     t->authtime = i;
 
-    kret = krb5_krcc_parse_int32(context, id, &i, bc);
+    kret = krb5_krcc_parse_int32(context, &i, bc);
     CHECK(kret);
     t->starttime = i;
 
-    kret = krb5_krcc_parse_int32(context, id, &i, bc);
+    kret = krb5_krcc_parse_int32(context, &i, bc);
     CHECK(kret);
     t->endtime = i;
 
-    kret = krb5_krcc_parse_int32(context, id, &i, bc);
+    kret = krb5_krcc_parse_int32(context, &i, bc);
     CHECK(kret);
     t->renew_till = i;
 
@@ -1488,8 +2035,8 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
-                         krb5_data * data, krb5_krcc_bc * bc)
+krb5_krcc_parse_krb5data(krb5_context context, krb5_data * data,
+                         krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 len;
@@ -1497,12 +2044,12 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
     data->magic = KV5M_DATA;
     data->data = 0;
 
-    kret = krb5_krcc_parse_int32(context, id, &len, bc);
+    kret = krb5_krcc_parse_int32(context, &len, bc);
     CHECK(kret);
     if (len < 0)
         return KRB5_CC_NOMEM;
     data->length = len;
-    if (data->length != len || data->length + 1 == 0)
+    if (data->length + 1 == 0)
         return KRB5_CC_NOMEM;
 
     if (data->length == 0) {
@@ -1514,8 +2061,7 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id,
     if (data->data == NULL)
         return KRB5_CC_NOMEM;
 
-    kret = krb5_krcc_parse(context, id, data->data, (unsigned) data->length,
-                           bc);
+    kret = krb5_krcc_parse(context, data->data, (unsigned) data->length, bc);
     CHECK(kret);
 
     data->data[data->length] = 0;       /* Null terminate, just in case.... */
@@ -1527,13 +2073,12 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i,
-                      krb5_krcc_bc * bc)
+krb5_krcc_parse_int32(krb5_context context, krb5_int32 * i, krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     unsigned char buf[4];
 
-    kret = krb5_krcc_parse(context, id, buf, 4, bc);
+    kret = krb5_krcc_parse(context, buf, 4, bc);
     if (kret)
         return kret;
     *i = load_32_be(buf);
@@ -1541,15 +2086,14 @@ krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i,
 }
 
 static  krb5_error_code
-krb5_krcc_parse_octet(krb5_context context, krb5_ccache id, krb5_octet * i,
-                      krb5_krcc_bc * bc)
+krb5_krcc_parse_octet(krb5_context context, krb5_octet * i, krb5_krcc_bc * bc)
 {
-    return krb5_krcc_parse(context, id, (krb5_pointer) i, 1, bc);
+    return krb5_krcc_parse(context, (krb5_pointer) i, 1, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
-                      krb5_address *** addrs, krb5_krcc_bc * bc)
+krb5_krcc_parse_addrs(krb5_context context, krb5_address *** addrs,
+                      krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 length;
@@ -1559,18 +2103,17 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
     *addrs = 0;
 
     /* Read the number of components */
-    kret = krb5_krcc_parse_int32(context, id, &length, bc);
+    kret = krb5_krcc_parse_int32(context, &length, bc);
     CHECK(kret);
 
     /*
      * Make *addrs able to hold length pointers to krb5_address structs
      * Add one extra for a null-terminated list
      */
-    msize = length;
-    msize += 1;
-    if (msize == 0 || msize - 1 != length || length < 0)
+    msize = (size_t)length + 1;
+    if (msize == 0 || length < 0)
         return KRB5_CC_NOMEM;
-    *addrs = ALLOC(msize, krb5_address *);
+    *addrs = calloc(msize, sizeof(krb5_address *));
     if (*addrs == NULL)
         return KRB5_CC_NOMEM;
 
@@ -1580,7 +2123,7 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id,
             krb5_free_addresses(context, *addrs);
             return KRB5_CC_NOMEM;
         }
-        kret = krb5_krcc_parse_addr(context, id, (*addrs)[i], bc);
+        kret = krb5_krcc_parse_addr(context, (*addrs)[i], bc);
         CHECK(kret);
     }
 
@@ -1592,7 +2135,7 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
+krb5_krcc_parse_addr(krb5_context context, krb5_address * addr,
                      krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
@@ -1602,22 +2145,15 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr,
     addr->magic = KV5M_ADDRESS;
     addr->contents = 0;
 
-    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
+    kret = krb5_krcc_parse_ui_2(context, &ui2, bc);
     CHECK(kret);
     addr->addrtype = ui2;
 
-    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
+    kret = krb5_krcc_parse_int32(context, &int32, bc);
     CHECK(kret);
     if ((int32 & VALID_INT_BITS) != int32)      /* Overflow int??? */
         return KRB5_CC_NOMEM;
     addr->length = int32;
-    /*
-     * Length field is "unsigned int", which may be smaller
-     * than 32 bits.
-     */
-    if (addr->length != int32)
-        return KRB5_CC_NOMEM;   /* XXX */
-
     if (addr->length == 0)
         return KRB5_OK;
 
@@ -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;
 
-    kret = krb5_krcc_parse(context, id, addr->contents, addr->length, bc);
+    kret = krb5_krcc_parse(context, addr->contents, addr->length, bc);
     CHECK(kret);
 
     return KRB5_OK;
@@ -1636,8 +2172,8 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
-                         krb5_authdata *** a, krb5_krcc_bc * bc)
+krb5_krcc_parse_authdata(krb5_context context, krb5_authdata *** a,
+                         krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 length;
@@ -1647,7 +2183,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
     *a = 0;
 
     /* Read the number of components */
-    kret = krb5_krcc_parse_int32(context, id, &length, bc);
+    kret = krb5_krcc_parse_int32(context, &length, bc);
     CHECK(kret);
 
     if (length == 0)
@@ -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
      */
-    msize = length;
-    msize += 1;
-    if (msize == 0 || msize - 1 != length || length < 0)
+    msize = (size_t)length + 1;
+    if (msize == 0 || length < 0)
         return KRB5_CC_NOMEM;
-    *a = ALLOC(msize, krb5_authdata *);
+    *a = calloc(msize, sizeof(krb5_authdata *));
     if (*a == NULL)
         return KRB5_CC_NOMEM;
 
@@ -1672,7 +2207,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id,
             *a = NULL;
             return KRB5_CC_NOMEM;
         }
-        kret = krb5_krcc_parse_authdatum(context, id, (*a)[i], bc);
+        kret = krb5_krcc_parse_authdatum(context, (*a)[i], bc);
         CHECK(kret);
     }
 
@@ -1686,8 +2221,8 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
-                          krb5_authdata * a, krb5_krcc_bc * bc)
+krb5_krcc_parse_authdatum(krb5_context context, krb5_authdata * a,
+                          krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 int32;
@@ -1696,21 +2231,14 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
     a->magic = KV5M_AUTHDATA;
     a->contents = NULL;
 
-    kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc);
+    kret = krb5_krcc_parse_ui_2(context, &ui2, bc);
     CHECK(kret);
     a->ad_type = (krb5_authdatatype) ui2;
-    kret = krb5_krcc_parse_int32(context, id, &int32, bc);
+    kret = krb5_krcc_parse_int32(context, &int32, bc);
     CHECK(kret);
     if ((int32 & VALID_INT_BITS) != int32)      /* Overflow int??? */
         return KRB5_CC_NOMEM;
     a->length = int32;
-    /*
-     * Value could have gotten truncated if int is
-     * smaller than 32 bits.
-     */
-    if (a->length != int32)
-        return KRB5_CC_NOMEM;   /* XXX */
-
     if (a->length == 0)
         return KRB5_OK;
 
@@ -1718,7 +2246,7 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id,
     if (a->contents == NULL)
         return KRB5_CC_NOMEM;
 
-    kret = krb5_krcc_parse(context, id, a->contents, a->length, bc);
+    kret = krb5_krcc_parse(context, a->contents, a->length, bc);
     CHECK(kret);
 
     return KRB5_OK;
@@ -1730,13 +2258,12 @@ errout:
 }
 
 static  krb5_error_code
-krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i,
-                     krb5_krcc_bc * bc)
+krb5_krcc_parse_ui_2(krb5_context context, krb5_ui_2 * i, krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     unsigned char buf[2];
 
-    kret = krb5_krcc_parse(context, id, buf, 2, bc);
+    kret = krb5_krcc_parse(context, buf, 2, bc);
     if (kret)
         return kret;
     *i = load_16_be(buf);
@@ -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
-krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf,
-                  unsigned int len, krb5_krcc_bc * bc)
+krb5_krcc_unparse(krb5_context context, krb5_pointer buf, unsigned int len,
+                  krb5_krcc_bc * bc)
 {
+    if (bc->bpp == NULL) {
+        /* This is a dry run; just increase size and return. */
+        bc->size += len;
+        return KRB5_OK;
+    }
+
     if (bc->bpp + len > bc->endp)
         return KRB5_CC_WRITE;
 
@@ -1769,29 +2302,26 @@ krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf,
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id,
-                            krb5_principal princ, krb5_krcc_bc * bc)
+krb5_krcc_unparse_principal(krb5_context context, krb5_principal princ,
+                            krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_int32 i, length, tmp, type;
 
-    type = krb5_princ_type(context, princ);
-    tmp = length = krb5_princ_size(context, princ);
+    type = princ->type;
+    tmp = length = princ->length;
 
-    kret = krb5_krcc_unparse_int32(context, id, type, bc);
+    kret = krb5_krcc_unparse_int32(context, type, bc);
     CHECK_OUT(kret);
 
-    kret = krb5_krcc_unparse_int32(context, id, tmp, bc);
+    kret = krb5_krcc_unparse_int32(context, tmp, bc);
     CHECK_OUT(kret);
 
-    kret = krb5_krcc_unparse_krb5data(context, id,
-                                      krb5_princ_realm(context, princ), bc);
+    kret = krb5_krcc_unparse_krb5data(context, &princ->realm, bc);
     CHECK_OUT(kret);
 
     for (i = 0; i < length; i++) {
-        kret = krb5_krcc_unparse_krb5data(context, id,
-                                          krb5_princ_component(context, princ,
-                                                               i), bc);
+        kret = krb5_krcc_unparse_krb5data(context, &princ->data[i], bc);
         CHECK_OUT(kret);
     }
 
@@ -1799,67 +2329,65 @@ krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id,
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_keyblock(krb5_context context, krb5_ccache id,
-                           krb5_keyblock * keyblock, krb5_krcc_bc * bc)
+krb5_krcc_unparse_keyblock(krb5_context context, krb5_keyblock * keyblock,
+                           krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
 
-    kret = krb5_krcc_unparse_ui_2(context, id, keyblock->enctype, bc);
+    kret = krb5_krcc_unparse_ui_2(context, keyblock->enctype, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_ui_4(context, id, keyblock->length, bc);
+    kret = krb5_krcc_unparse_ui_4(context, keyblock->length, bc);
     CHECK_OUT(kret);
-    return krb5_krcc_unparse(context, id, (char *) keyblock->contents,
+    return krb5_krcc_unparse(context, (char *) keyblock->contents,
                              keyblock->length, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_times(krb5_context context, krb5_ccache id,
-                        krb5_ticket_times * t, krb5_krcc_bc * bc)
+krb5_krcc_unparse_times(krb5_context context, krb5_ticket_times * t,
+                        krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
 
-    kret = krb5_krcc_unparse_int32(context, id, t->authtime, bc);
+    kret = krb5_krcc_unparse_int32(context, t->authtime, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_int32(context, id, t->starttime, bc);
+    kret = krb5_krcc_unparse_int32(context, t->starttime, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_int32(context, id, t->endtime, bc);
+    kret = krb5_krcc_unparse_int32(context, t->endtime, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_int32(context, id, t->renew_till, bc);
+    kret = krb5_krcc_unparse_int32(context, t->renew_till, bc);
     CHECK_OUT(kret);
     return 0;
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_krb5data(krb5_context context, krb5_ccache id,
-                           krb5_data * data, krb5_krcc_bc * bc)
+krb5_krcc_unparse_krb5data(krb5_context context, krb5_data * data,
+                           krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
 
-    kret = krb5_krcc_unparse_ui_4(context, id, data->length, bc);
+    kret = krb5_krcc_unparse_ui_4(context, data->length, bc);
     CHECK_OUT(kret);
-    return krb5_krcc_unparse(context, id, data->data, data->length, bc);
+    return krb5_krcc_unparse(context, data->data, data->length, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_int32(krb5_context context, krb5_ccache id, krb5_int32 i,
-                        krb5_krcc_bc * bc)
+krb5_krcc_unparse_int32(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc)
 {
-    return krb5_krcc_unparse_ui_4(context, id, (krb5_ui_4) i, bc);
+    return krb5_krcc_unparse_ui_4(context, (krb5_ui_4) i, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_octet(krb5_context context, krb5_ccache id, krb5_int32 i,
-                        krb5_krcc_bc * bc)
+krb5_krcc_unparse_octet(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc)
 {
     krb5_octet ibuf;
 
     ibuf = (krb5_octet) i;
-    return krb5_krcc_unparse(context, id, (char *) &ibuf, 1, bc);
+    return krb5_krcc_unparse(context, (char *) &ibuf, 1, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
-                        krb5_address ** addrs, krb5_krcc_bc * bc)
+krb5_krcc_unparse_addrs(krb5_context context, krb5_address ** addrs,
+                        krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
     krb5_address **temp;
@@ -1872,10 +2400,10 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
             length += 1;
     }
 
-    kret = krb5_krcc_unparse_int32(context, id, length, bc);
+    kret = krb5_krcc_unparse_int32(context, length, bc);
     CHECK_OUT(kret);
     for (i = 0; i < length; i++) {
-        kret = krb5_krcc_unparse_addr(context, id, addrs[i], bc);
+        kret = krb5_krcc_unparse_addr(context, addrs[i], bc);
         CHECK_OUT(kret);
     }
 
@@ -1883,21 +2411,21 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id,
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_addr(krb5_context context, krb5_ccache id,
-                       krb5_address * addr, krb5_krcc_bc * bc)
+krb5_krcc_unparse_addr(krb5_context context, krb5_address * addr,
+                       krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
 
-    kret = krb5_krcc_unparse_ui_2(context, id, addr->addrtype, bc);
+    kret = krb5_krcc_unparse_ui_2(context, addr->addrtype, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_ui_4(context, id, addr->length, bc);
+    kret = krb5_krcc_unparse_ui_4(context, addr->length, bc);
     CHECK_OUT(kret);
-    return krb5_krcc_unparse(context, id, (char *) addr->contents,
+    return krb5_krcc_unparse(context, (char *) addr->contents,
                              addr->length, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id,
+krb5_krcc_unparse_authdata(krb5_context context,
                            krb5_authdata ** a, krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
@@ -1909,47 +2437,45 @@ krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id,
             length++;
     }
 
-    kret = krb5_krcc_unparse_int32(context, id, length, bc);
+    kret = krb5_krcc_unparse_int32(context, length, bc);
     CHECK_OUT(kret);
     for (i = 0; i < length; i++) {
-        kret = krb5_krcc_unparse_authdatum(context, id, a[i], bc);
+        kret = krb5_krcc_unparse_authdatum(context, a[i], bc);
         CHECK_OUT(kret);
     }
     return KRB5_OK;
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_authdatum(krb5_context context, krb5_ccache id,
-                            krb5_authdata * a, krb5_krcc_bc * bc)
+krb5_krcc_unparse_authdatum(krb5_context context, krb5_authdata * a,
+                            krb5_krcc_bc * bc)
 {
     krb5_error_code kret;
 
-    kret = krb5_krcc_unparse_ui_2(context, id, a->ad_type, bc);
+    kret = krb5_krcc_unparse_ui_2(context, a->ad_type, bc);
     CHECK_OUT(kret);
-    kret = krb5_krcc_unparse_ui_4(context, id, a->length, bc);
+    kret = krb5_krcc_unparse_ui_4(context, a->length, bc);
     CHECK_OUT(kret);
-    return krb5_krcc_unparse(context, id, (krb5_pointer) a->contents,
+    return krb5_krcc_unparse(context, (krb5_pointer) a->contents,
                              a->length, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_ui_4(krb5_context context, krb5_ccache id, krb5_ui_4 i,
-                       krb5_krcc_bc * bc)
+krb5_krcc_unparse_ui_4(krb5_context context, krb5_ui_4 i, krb5_krcc_bc * bc)
 {
     unsigned char buf[4];
 
     store_32_be(i, buf);
-    return krb5_krcc_unparse(context, id, buf, 4, bc);
+    return krb5_krcc_unparse(context, buf, 4, bc);
 }
 
 static  krb5_error_code
-krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i,
-                       krb5_krcc_bc * bc)
+krb5_krcc_unparse_ui_2(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc)
 {
     unsigned char buf[2];
 
     store_16_be(i, buf);
-    return krb5_krcc_unparse(context, id, buf, 2, bc);
+    return krb5_krcc_unparse(context, buf, 2, bc);
 }
 
 /*
@@ -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
-krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
-                       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;
-    char   *buf;
+
+    kret = krb5_krcc_unparse_principal(context, creds->client, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_principal(context, creds->server, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_keyblock(context, &creds->keyblock, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_times(context, &creds->times, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_octet(context, (krb5_int32) creds->is_skey, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_int32(context, creds->ticket_flags, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_addrs(context, creds->addresses, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_authdata(context, creds->authdata, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_krb5data(context, &creds->ticket, bc);
+    CHECK_OUT(kret);
+    CHECK(kret);
+
+    kret = krb5_krcc_unparse_krb5data(context, &creds->second_ticket, bc);
+    CHECK_OUT(kret);
+
+    /* Success! */
+    kret = KRB5_OK;
+
+errout:
+    return kret;
+}
+
+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 = NULL;
     krb5_krcc_bc bc;
 
     if (!creds || !datapp || !lenptr)
@@ -1978,43 +2548,102 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
     *datapp = NULL;
     *lenptr = 0;
 
-    buf = malloc(GUESS_CRED_SIZE);
+    /* Do a dry run first to calculate the size. */
+    bc.bpp = bc.endp = NULL;
+    bc.size = 0;
+    kret = krb5_krcc_unparse_cred(context, creds, &bc);
+    CHECK(kret);
+    if (bc.size > MAX_CRED_SIZE)
+        return KRB5_CC_WRITE;
+
+    /* Allocate a buffer and unparse for real. */
+    buf = malloc(bc.size);
     if (buf == NULL)
         return KRB5_CC_NOMEM;
-
     bc.bpp = buf;
-    bc.endp = buf + GUESS_CRED_SIZE;
+    bc.endp = buf + bc.size;
+    kret = krb5_krcc_unparse_cred(context, creds, &bc);
+    CHECK(kret);
 
-    kret = krb5_krcc_unparse_principal(context, id, creds->client, &bc);
-    CHECK_N_GO(kret, errout);
+    /* Success! */
+    *datapp = buf;
+    *lenptr = bc.bpp - buf;
+    buf = NULL;
+    kret = KRB5_OK;
 
-    kret = krb5_krcc_unparse_principal(context, id, creds->server, &bc);
-    CHECK_N_GO(kret, errout);
+errout:
+    free(buf);
+    return kret;
+}
 
-    kret = krb5_krcc_unparse_keyblock(context, id, &creds->keyblock, &bc);
-    CHECK_N_GO(kret, errout);
+static krb5_error_code
+krb5_krcc_parse_index(krb5_context context, krb5_int32 *version,
+                      char **primary, void *payload, int psize)
+{
+    krb5_error_code kret;
+    krb5_krcc_bc bc;
+    krb5_data data;
 
-    kret = krb5_krcc_unparse_times(context, id, &creds->times, &bc);
-    CHECK_N_GO(kret, errout);
+    bc.bpp = payload;
+    bc.endp = bc.bpp + psize;
 
-    kret = krb5_krcc_unparse_octet(context, id, (krb5_int32) creds->is_skey,
-                                   &bc);
-    CHECK_N_GO(kret, errout);
+    kret = krb5_krcc_parse_int32(context, version, &bc);
+    CHECK_OUT(kret);
 
-    kret = krb5_krcc_unparse_int32(context, id, creds->ticket_flags, &bc);
-    CHECK_N_GO(kret, errout);
+    kret = krb5_krcc_parse_krb5data(context, &data, &bc);
+    CHECK_OUT(kret);
 
-    kret = krb5_krcc_unparse_addrs(context, id, creds->addresses, &bc);
-    CHECK_N_GO(kret, errout);
+    *primary = (char *)data.data;
+    return KRB5_OK;
+}
 
-    kret = krb5_krcc_unparse_authdata(context, id, creds->authdata, &bc);
-    CHECK_N_GO(kret, errout);
+static krb5_error_code
+krb5_krcc_unparse_index_internal(krb5_context context, krb5_int32 version,
+                                 const char *primary, krb5_krcc_bc *bc)
+{
+    krb5_error_code kret;
+    krb5_data data;
 
-    kret = krb5_krcc_unparse_krb5data(context, id, &creds->ticket, &bc);
-    CHECK_N_GO(kret, errout);
+    data.length = strlen(primary) + 1;
+    data.data = (void *)primary;
 
-    kret = krb5_krcc_unparse_krb5data(context, id, &creds->second_ticket, &bc);
-    CHECK_N_GO(kret, errout);
+    kret = krb5_krcc_unparse_int32(context, version, bc);
+    CHECK_OUT(kret);
+
+    kret = krb5_krcc_unparse_krb5data(context, &data, bc);
+    CHECK_OUT(kret);
+
+    return KRB5_OK;
+}
+
+static krb5_error_code
+krb5_krcc_unparse_index(krb5_context context, krb5_int32 version,
+                        const char *primary, void **datapp, int *lenptr)
+{
+    krb5_error_code kret;
+    krb5_krcc_bc bc;
+    char *buf;
+
+    if (!primary || !datapp || !lenptr)
+        return EINVAL;
+
+    *datapp = NULL;
+    *lenptr = 0;
+
+    /* Do a dry run first to calculate the size. */
+    bc.bpp = bc.endp = NULL;
+    bc.size = 0;
+    kret = krb5_krcc_unparse_index_internal(context, version, primary, &bc);
+    CHECK_OUT(kret);
+
+    buf = malloc(bc.size);
+    if (buf == NULL)
+        return ENOMEM;
+
+    bc.bpp = buf;
+    bc.endp = buf + bc.size;
+    kret = krb5_krcc_unparse_index_internal(context, version, primary, &bc);
+    CHECK(kret);
 
     /* Success! */
     *datapp = buf;
@@ -2022,6 +2651,8 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id,
     kret = KRB5_OK;
 
 errout:
+    if (kret)
+        free(buf);
     return kret;
 }
 
@@ -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 */
-    NULL,
-    NULL,
-    NULL,
+    krb5_krcc_ptcursor_new,
+    krb5_krcc_ptcursor_next,
+    krb5_krcc_ptcursor_free,
     NULL, /* move */
     krb5_krcc_last_change_time, /* lastchange */
     NULL, /* wasdefault */
     krb5_krcc_lock,
     krb5_krcc_unlock,
-    NULL, /* 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@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@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@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)