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