diff options
author | Nalin Dahyabhai <nalin@dahyabhai.net> | 2013-10-02 14:46:20 -0400 |
---|---|---|
committer | Nalin Dahyabhai <nalin@dahyabhai.net> | 2013-10-02 14:46:20 -0400 |
commit | 494e7adbb0c26ae0ee51fe7ef887d28b48e7c413 (patch) | |
tree | bf50ebc59cfa3271c0ed81c7698dd2478c506a57 | |
parent | 682dc07d28b06e8d3a48962db46cf52202d92045 (diff) | |
download | krb5-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.patch | 21 | ||||
-rw-r--r-- | krb5-master-kinit-cccol.patch | 314 | ||||
-rw-r--r-- | krb5.spec | 17 | ||||
-rw-r--r-- | persistent_keyring.patch | 2662 |
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 @@ -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) + |