krb5: port MIT Linux keyring credentials cache (#166)
authorLuke Howard <lukeh@padl.com>
Thu, 20 Dec 2018 07:52:55 +0000 (23:52 -0800)
committerLuke Howard <lukeh@padl.com>
Mon, 24 Dec 2018 07:17:32 +0000 (18:17 +1100)
17 files changed:
.travis.yml
configure.ac
include/Makefile.am
include/config.h.w32
lib/base/heimbase.h
lib/krb5/Makefile.am
lib/krb5/NTMakefile
lib/krb5/constants.c
lib/krb5/context.c
lib/krb5/k5e1_err.et [new file with mode: 0644]
lib/krb5/krb5.h
lib/krb5/krb5_locl.h
lib/krb5/krcache.c [new file with mode: 0644]
lib/krb5/libkrb5-exports.def.in
lib/krb5/test_cc.c
lib/krb5/version-script.map
packages/windows/sdk/NTMakefile

index 6cf2b883a6be83e8870e61df958c28dd0260381c..9d52b5fea5a30efd0e444fc77ca638d844049e91 100644 (file)
@@ -10,7 +10,7 @@ env:
 
 before_install:
     - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get update -qq; fi
-    - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq bison comerr-dev flex libcap-ng-dev libdb-dev libedit-dev libjson-perl libldap2-dev libncurses5-dev libperl4-corelibs-perl libsqlite3-dev pkg-config python ss-dev texinfo unzip netbase; fi
+    - if [ $TRAVIS_OS_NAME = linux ]; then sudo apt-get install -qq bison comerr-dev flex libcap-ng-dev libdb-dev libedit-dev libjson-perl libldap2-dev libncurses5-dev libperl4-corelibs-perl libsqlite3-dev libkeyutils-dev pkg-config python ss-dev texinfo unzip netbase keyutils; fi
     - if [ $TRAVIS_OS_NAME = osx ]; then brew update; fi
     - if [ $TRAVIS_OS_NAME = osx ]; then brew install cpanm bison flex berkeley-db lmdb openldap openssl; fi
     - if [ $TRAVIS_OS_NAME = osx ]; then sudo cpanm install JSON; fi
index f98a3c555ee39aed8875548083d1a9a65a920587..35781d071f9387c0ad09d8002c80e7748c16bc9e 100644 (file)
@@ -349,6 +349,7 @@ AC_CHECK_HEADERS([\
        fnmatch.h                               \
        inttypes.h                              \
        io.h                                    \
+       keyutils.h                              \
        libutil.h                               \
        limits.h                                \
        maillock.h                              \
@@ -547,7 +548,28 @@ if test "$enable_kcm" = yes; then
 fi
 AM_CONDITIONAL(KCM, test "$enable_kcm" = yes)
 
+dnl detect keyring on Linux
+if test "$ac_cv_header_keyutils_h" = yes; then
+    AC_CHECK_SIZEOF([key_serial_t],,[
+       #ifdef HAVE_INTTYPES_H
+       #include <inttypes.h>
+       #endif
+       #ifdef HAVE_SYS_TYPES_H
+       #include <sys/types.h>
+       #endif
+       #include <keyutils.h>
+    ])
+fi
+
+AC_FIND_FUNC_NO_LIBS(add_key, keyutils)
+if test -n "$LIB_add_key"; then
+    saved_LIBS="$LIBS"
+    LIBS="$LIBS $LIB_add_key"
+    AC_CHECK_FUNCS(keyctl_get_persistent)
+    LIBS="$saved_LIBS"
+fi
 
+AC_CHECK_SIZEOF([time_t])
 
 dnl Cray stuff
 AC_CHECK_FUNCS(getudbnam setlim)
index 696a023ed117101572dd2a4026259a3c711f3850..29256a375ce32726f962067d2cc72e6a719b3772 100644 (file)
@@ -67,6 +67,7 @@ CLEANFILES =                  \
        hx509.h                 \
        hx509_err.h             \
        k524_err.h              \
+       k5e1_err.h              \
        kafs.h                  \
        kcm-protos.h            \
        kdc-private.h           \
index d52eb6b67d01186f643ebe245c79dd6aba337004..76a7b9aaed03707bcfb8bee4f8b6f7c98c8becc1 100644 (file)
@@ -1425,6 +1425,13 @@ static const char *const rcsid[] = { (const char *)rcsid, "@(#)" msg }
 /* Used with login -p */
 /* #undef LOGIN_ARGS */
 
+/* The size of `time_t', as computed by sizeof. */
+#if defined (_USE_64BIT_TIME_T) || !defined( _USE_32BIT_TIME_T)
+#define SIZEOF_TIME_T 8
+#else
+#define SIZEOF_TIME_T 4
+#endif
+
 #ifdef ROKEN_RENAME
 #include "roken_rename.h"
 #endif
index 238796b5e07ecb445d5e6de84ee65d3b0a196346..45b7ee09778d3158f81428b4ded3cd35c43b5120 100644 (file)
@@ -451,6 +451,9 @@ void heim_w32_service_thread_detach(void *);
 #define heim_base_exchange_pointer(t,v) __sync_lock_test_and_set((t), (v))
 #endif
 
+#define heim_base_exchange_32(t,v)     heim_base_exchange_pointer((t), (v))
+#define heim_base_exchange_64(t,v)     heim_base_exchange_pointer((t), (v))
+
 #elif defined(__sun)
 
 #include <sys/atomic.h>
@@ -461,6 +464,8 @@ void heim_w32_service_thread_detach(void *);
 #define heim_base_atomic_max    UINT_MAX
 
 #define heim_base_exchange_pointer(t,v) atomic_swap_ptr((volatile void *)(t), (void *)(v))
+#define heim_base_exchange_32(t,v)     atomic_swap_32((volatile uint32_t *)(t), (v))
+#define heim_base_exchange_64(t,v)     atomic_swap_64((volatile uint64_t *)(t), (v))
 
 #elif defined(_AIX)
 
@@ -482,6 +487,28 @@ heim_base_exchange_pointer(void *p, void *newval)
     return val;
 }
 
+static inline uint32_t
+heim_base_exchange_32(uint32_t *p, uint32_t newval)
+{
+    uint32_t val = *p;
+
+    while (!compare_and_swap((atomic_p)p, (int *)&val, (int)newval))
+        ;
+
+    return val;
+}
+
+static inline uint64_t
+heim_base_exchange_64(uint64_t *p, uint64_t newval)
+{
+    uint64_t val = *p;
+
+    while (!compare_and_swaplp((atomic_l)p, (long *)&val, (long)newval))
+        ;
+
+    return val;
+}
+
 #elif defined(_WIN32)
 
 #define heim_base_atomic_inc(x) InterlockedIncrement(x)
@@ -490,6 +517,8 @@ heim_base_exchange_pointer(void *p, void *newval)
 #define heim_base_atomic_max    MAXLONG
 
 #define heim_base_exchange_pointer(t,v) InterlockedExchangePointer((PVOID volatile *)(t), (PVOID)(v))
+#define heim_base_exchange_32(t,v)     ((ULONG)InterlockedExchange((LONG volatile *)(t), (LONG)(v)))
+#define heim_base_exchange_64(t,v)     ((ULONG64)InterlockedExchange64((LONG64 violatile *)(t), (LONG64)(v)))
 
 #else
 
@@ -535,4 +564,12 @@ heim_base_exchange_pointer(void *target, void *value)
 
 #endif /* defined(__GNUC__) && defined(HAVE___SYNC_ADD_AND_FETCH) */
 
+#if SIZEOF_TIME_T == 8
+#define heim_base_exchange_time_t(t,v) heim_base_exchange_64((t), (v))
+#elif SIZEOF_TIME_T == 4
+#define heim_base_exchange_time_t(t,v) heim_base_exchange_32((t), (v))
+#else
+#error set SIZEOF_TIME_T for your platform
+#endif
+
 #endif /* HEIM_BASE_H */
index cdb13d5760ff1f290bd5325a9ba2ff500e95aaac..d752deb29d9c41c31a3e8bacda5aa22a2f5ee645 100644 (file)
@@ -79,6 +79,7 @@ libkrb5_la_LIBADD = \
        $(LIB_libintl) \
        $(LIBADD_roken) \
        $(PTHREAD_LIBADD) \
+       $(LIB_add_key) \
        $(LIB_door_create) \
        $(LIB_dlopen)
 
@@ -93,12 +94,13 @@ librfc3961_la_LIBADD = \
        $(LIB_libintl) \
        $(LIBADD_roken) \
        $(PTHREAD_LIBADD) \
+       $(LIB_add_key) \
        $(LIB_door_create) \
        $(LIB_dlopen)
 
 lib_LTLIBRARIES = libkrb5.la
 
-ERR_FILES = krb5_err.c krb_err.c heim_err.c k524_err.c
+ERR_FILES = krb5_err.c krb_err.c heim_err.c k524_err.c k5e1_err.c
 
 libkrb5_la_CPPFLAGS = \
        -DBUILD_KRB5_LIB \
@@ -183,6 +185,7 @@ dist_libkrb5_la_SOURCES =                   \
        keytab_memory.c                         \
        krb5_locl.h                             \
        krb5-v4compat.h                         \
+       krcache.c                               \
        krbhst.c                                \
        kuserok.c                               \
        kuserok_plugin.h                        \
@@ -276,7 +279,7 @@ ALL_OBJECTS += $(test_renew_OBJECTS)
 ALL_OBJECTS += $(test_rfc3961_OBJECTS)
 
 $(ALL_OBJECTS): $(srcdir)/krb5-protos.h $(srcdir)/krb5-private.h
-$(ALL_OBJECTS): krb5_err.h heim_err.h k524_err.h krb5_err.h krb_err.h k524_err.h
+$(ALL_OBJECTS): krb5_err.h heim_err.h k524_err.h k5e1_err.h krb_err.h k524_err.h
 
 librfc3961_la_SOURCES =                                \
        crc.c                                   \
@@ -380,7 +383,7 @@ dist_include_HEADERS = \
 noinst_HEADERS = $(srcdir)/krb5-private.h
 
 
-nodist_include_HEADERS = krb5_err.h heim_err.h k524_err.h
+nodist_include_HEADERS = krb5_err.h heim_err.h k524_err.h k5e1_err.h
 
 # XXX use nobase_include_HEADERS = krb5/locate_plugin.h
 krb5dir = $(includedir)/krb5
@@ -396,9 +399,10 @@ CLEANFILES = \
        krb5_err.c krb5_err.h \
        krb_err.c krb_err.h \
        heim_err.c heim_err.h \
-       k524_err.c k524_err.h
+       k524_err.c k524_err.h \
+       k5e1_err.c k5e1_err.h
 
-$(libkrb5_la_OBJECTS): krb5_err.h krb_err.h heim_err.h k524_err.h
+$(libkrb5_la_OBJECTS): krb5_err.h krb_err.h heim_err.h k524_err.h k5e1_err.h
 
 test_config_strings.out: test_config_strings.cfg
        $(CP) $(srcdir)/test_config_strings.cfg test_config_strings.out
@@ -413,6 +417,7 @@ EXTRA_DIST = \
        krb_err.et \
        heim_err.et \
        k524_err.et \
+       k5e1_err.et \
        $(man_MANS) \
        version-script.map \
        test_config_strings.cfg \
@@ -429,3 +434,5 @@ krb_err.h: krb_err.et
 heim_err.h: heim_err.et
 
 k524_err.h: k524_err.et
+
+k5e1_err.h: k5e1_err.et
index 2378bfbc9b63a29bc87609c9d701ae3096ee68ba..edbbdf060679fc5d075f469d4a51aa2a7593395e 100644 (file)
@@ -166,11 +166,13 @@ libkrb5_gen_OBJS=     \
        $(OBJ)\krb5_err.obj \
        $(OBJ)\krb_err.obj  \
        $(OBJ)\heim_err.obj \
-       $(OBJ)\k524_err.obj
+       $(OBJ)\k524_err.obj \
+       $(OBJ)\k5e1_err.obj
 
 INCFILES=                      \
        $(INCDIR)\heim_err.h    \
        $(INCDIR)\k524_err.h    \
+       $(INCDIR)\k5e1_err.h    \
         $(INCDIR)\kcm.h         \
        $(INCDIR)\krb_err.h     \
        $(INCDIR)\krb5.h        \
@@ -346,6 +348,11 @@ $(OBJ)\k524_err.c $(OBJ)\k524_err.h: k524_err.et
        $(BINDIR)\compile_et.exe $(SRCDIR)\k524_err.et
        cd $(SRCDIR)
 
+$(OBJ)\k5e1_err.c $(OBJ)\k5e1_err.h: k5e1_err.et
+       cd $(OBJ)
+       $(BINDIR)\compile_et.exe $(SRCDIR)\k5e1_err.et
+       cd $(SRCDIR)
+
 #----------------------------------------------------------------------
 # libkrb5
 
index 87147c22bcb717138479d5ba7fd1ed8636109068..42b460f61d51289ed456727a594865b480754eed 100644 (file)
@@ -64,3 +64,4 @@ KRB5_LIB_VARIABLE const char *krb5_cc_type_memory = "MEMORY";
 KRB5_LIB_VARIABLE const char *krb5_cc_type_kcm = "KCM";
 KRB5_LIB_VARIABLE const char *krb5_cc_type_scc = "SCC";
 KRB5_LIB_VARIABLE const char *krb5_cc_type_dcc = "DIR";
+KRB5_LIB_VARIABLE const char *krb5_cc_type_keyring = "KEYRING";
index 731185f9f99dbec3c901f0a8b5b867f36d187a28..bd8b2bfc50b4354810696c6ec1ebea24aad898ee 100644 (file)
@@ -290,6 +290,9 @@ cc_ops_register(krb5_context context)
     krb5_cc_register(context, &krb5_akcm_ops, TRUE);
 #endif
     krb5_cc_register(context, &krb5_kcm_ops, TRUE);
+#endif
+#if defined(HAVE_KEYUTILS_H)
+    krb5_cc_register(context, &krb5_krcc_ops, TRUE);
 #endif
     _krb5_load_ccache_plugins(context);
     return 0;
@@ -1116,6 +1119,7 @@ krb5_init_ets(krb5_context context)
        krb5_add_et_list(context, initialize_heim_error_table_r);
 
        krb5_add_et_list(context, initialize_k524_error_table_r);
+       krb5_add_et_list(context, initialize_k5e1_error_table_r);
 
 #ifdef COM_ERR_BINDDOMAIN_krb5
        bindtextdomain(COM_ERR_BINDDOMAIN_krb5, HEIMDAL_LOCALEDIR);
diff --git a/lib/krb5/k5e1_err.et b/lib/krb5/k5e1_err.et
new file mode 100644 (file)
index 0000000..19414f1
--- /dev/null
@@ -0,0 +1,13 @@
+id "$Id$"
+
+error_table k5e1
+
+index 4
+
+prefix KRB5_DCC
+error_code CANNOT_CREATE,      "Can't create new subsidiary cache"
+
+prefix KRB5_KCC
+error_code INVALID_ANCHOR,     "Invalid keyring anchor name"
+error_code UNKNOWN_VERSION,    "Unknown keyring collection version"
+error_code INVALID_UID,                "Invalid UID in persistent keyring name"
index 19c89f21ae8459fc80190041392e7aa47ded4691..2e580c036fc9f9cf339aec7f4becad1a7f9673a9 100644 (file)
@@ -45,6 +45,7 @@
 #include <krb5_err.h>
 #include <heim_err.h>
 #include <k524_err.h>
+#include <k5e1_err.h>
 
 #include <krb5_asn1.h>
 
@@ -961,6 +962,7 @@ extern KRB5_LIB_VARIABLE const krb5_cc_ops krb5_mcc_ops;
 extern KRB5_LIB_VARIABLE const krb5_cc_ops krb5_kcm_ops;
 extern KRB5_LIB_VARIABLE const krb5_cc_ops krb5_akcm_ops;
 extern KRB5_LIB_VARIABLE const krb5_cc_ops krb5_scc_ops;
+extern KRB5_LIB_VARIABLE const krb5_cc_ops krb5_krcc_ops;
 
 extern KRB5_LIB_VARIABLE const krb5_kt_ops krb5_fkt_ops;
 extern KRB5_LIB_VARIABLE const krb5_kt_ops krb5_wrfkt_ops;
@@ -975,6 +977,7 @@ extern KRB5_LIB_VARIABLE const char *krb5_cc_type_memory;
 extern KRB5_LIB_VARIABLE const char *krb5_cc_type_kcm;
 extern KRB5_LIB_VARIABLE const char *krb5_cc_type_scc;
 extern KRB5_LIB_VARIABLE const char *krb5_cc_type_dcc;
+extern KRB5_LIB_VARIABLE const char *krb5_cc_type_keyring;
 
 #endif /* __KRB5_H__ */
 
index 1ae67f5d580ea67fbe7212ac3afd747655bbd027..0ba68450db2bd5e14ca3f9fa68f1acbe25f24e03 100644 (file)
@@ -142,6 +142,7 @@ struct _krb5_krb_auth_data;
 
 #include <krb5.h>
 #include <krb5_err.h>
+#include <k5e1_err.h>
 #include <asn1_err.h>
 #ifdef PKINIT
 #include <hx509.h>
diff --git a/lib/krb5/krcache.c b/lib/krb5/krcache.c
new file mode 100644 (file)
index 0000000..c00dec0
--- /dev/null
@@ -0,0 +1,2034 @@
+/*
+ * Copyright (c) 2006 The Regents of the University of Michigan.
+ * All rights reserved.
+ *
+ * Portions Copyright (c) 2018, AuriStor, Inc.
+ *
+ * Permission is granted to use, copy, create derivative works
+ * and redistribute this software and such derivative works
+ * for any purpose, so long as the name of The University of
+ * Michigan is not used in any advertising or publicity
+ * pertaining to the use of distribution of this software
+ * without specific, written prior authorization.  If the
+ * above copyright notice or any other identification of the
+ * University of Michigan is included in any copy of any
+ * portion of this software, then the disclaimer below must
+ * also be included.
+ *
+ * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
+ * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
+ * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
+ * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
+ * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
+ * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGES.
+ */
+/*
+ * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of
+ * Technology.  All Rights Reserved.
+ *
+ * Original stdio support copyright 1995 by Cygnus Support.
+ *
+ * Export of this software from the United States of America may
+ *   require a specific license from the United States Government.
+ *   It is the responsibility of any person or organization contemplating
+ *   export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission.  Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose.  It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * This file implements a collection-enabled credential cache type where the
+ * credentials are stored in the Linux keyring facility.
+ *
+ * A residual of this type can have three forms:
+ *    anchor:collection:subsidiary
+ *    anchor:collection
+ *    collection
+ *
+ * The anchor name is "process", "thread", or "legacy" and determines where we
+ * search for keyring collections.  In the third form, the anchor name is
+ * presumed to be "legacy".  The anchor keyring for legacy caches is the
+ * session keyring.
+ *
+ * If the subsidiary name is present, the residual identifies a single cache
+ * within a collection.  Otherwise, the residual identifies the collection
+ * itself.  When a residual identifying a collection is resolved, the
+ * collection's primary key is looked up (or initialized, using the collection
+ * name as the subsidiary name), and the resulting cache's name will use the
+ * first name form and will identify the primary cache.
+ *
+ * Keyring collections are named "_krb_<collection>" and are linked from the
+ * anchor keyring.  The keys within a keyring collection are links to cache
+ * keyrings, plus a link to one user key named "krb_ccache:primary" which
+ * contains a serialized representation of the collection version (currently 1)
+ * and the primary name of the collection.
+ *
+ * Cache keyrings contain one user key per credential which contains a
+ * serialized representation of the credential.  There is also one user key
+ * named "__krb5_princ__" which contains a serialized representation of the
+ * cache's default principal.
+ *
+ * If the anchor name is "legacy", then the initial primary cache (the one
+ * named with the collection name) is also linked to the session keyring, and
+ * we look for a cache in that location when initializing the collection.  This
+ * extra link allows that cache to be visible to old versions of the KEYRING
+ * cache type, and allows us to see caches created by that code.
+ */
+
+#include "krb5_locl.h"
+
+#ifdef HAVE_KEYUTILS_H
+
+#include <keyutils.h>
+
+/*
+ * 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_KEYCTL_GET_PERSISTENT
+#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"
+
+/*
+ * We create ccaches as separate keyrings
+ */
+#define KRCC_KEY_TYPE_KEYRING          "keyring"
+
+/*
+ * Special name of the key within a ccache keyring
+ * holding principal information
+ */
+#define KRCC_SPEC_PRINC_KEYNAME                "__krb5_princ__"
+
+/*
+ * 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
+ * in the future may contain a list that may be
+ * intelligently chosen from.
+ */
+#define KRCC_SPEC_CCACHE_SET_KEYNAME   "__krb5_cc_set__"
+
+/*
+ * This name identifies the key containing the name of the current primary
+ * cache within a collection.
+ */
+#define KRCC_COLLECTION_PRIMARY                "krb_ccache:primary"
+
+/*
+ * If the library context does not specify a keyring collection, unique ccaches
+ * will be created within this collection.
+ */
+#define KRCC_DEFAULT_UNIQUE_COLLECTION "session:__krb5_unique__"
+
+/*
+ * Collection keyring names begin with this prefix.  We use a prefix so that a
+ * cache keyring with the collection name itself can be linked directly into
+ * the anchor, for legacy session keyring compatibility.
+ */
+#define KRCC_CCCOL_PREFIX              "_krb_"
+
+/*
+ * For the "persistent" anchor type, we look up or create this fixed keyring
+ * name within the per-UID persistent keyring.
+ */
+#define KRCC_PERSISTENT_KEYRING_NAME   "_krb"
+
+/*
+ * Name of the key holding time offsets for the individual cache
+ */
+#define KRCC_TIME_OFFSETS              "__krb5_time_offsets__"
+
+/*
+ * Keyring name prefix and length of random name part
+ */
+#define KRCC_NAME_PREFIX               "krb_ccache_"
+#define KRCC_NAME_RAND_CHARS           8
+
+#define KRCC_COLLECTION_VERSION                1
+
+#define KRCC_PERSISTENT_ANCHOR         "persistent"
+#define KRCC_PROCESS_ANCHOR            "process"
+#define KRCC_THREAD_ANCHOR             "thread"
+#define KRCC_SESSION_ANCHOR            "session"
+#define KRCC_USER_ANCHOR               "user"
+#define KRCC_LEGACY_ANCHOR             "legacy"
+
+#if SIZEOF_KEY_SERIAL_T != 4
+/* lockless implementation assumes 32-bit key serials */
+#error only 32-bit key serial numbers supported by this version of keyring ccache
+#endif
+
+typedef union _krb5_krcache_and_princ_id {
+    uint64_t krcu_cache_and_princ_id;
+    struct {
+       key_serial_t cache_id;          /* keyring ID representing ccache */
+       key_serial_t princ_id;          /* key ID holding principal info */
+    } krcu_id;
+    #define krcu_cache_id              krcu_id.cache_id
+    #define krcu_princ_id              krcu_id.princ_id
+} krb5_krcache_and_princ_id;
+
+/*
+ * This represents a credentials cache "file" where cache_id is the keyring
+ * serial number for this credentials cache "file".  Each key in the keyring
+ * contains a separate key.
+ *
+ * Thread-safe as long as caches are not destroyed whilst other threads are
+ * using them.
+ */
+typedef struct _krb5_krcache {
+    char *krc_name;                    /* Name for this credentials cache */
+    krb5_timestamp krc_changetime;     /* update time, does not decrease (mutable) */
+    krb5_krcache_and_princ_id krc_id;  /* cache and principal IDs (mutable) */
+    #define krc_cache_and_principal_id krc_id.krcu_cache_and_princ_id
+    #define krc_cache_id               krc_id.krcu_id.cache_id
+    #define krc_princ_id               krc_id.krcu_id.princ_id
+    key_serial_t krc_coll_id;          /* collection containing this cache keyring */
+    krb5_boolean krc_is_legacy;                /* */
+} krb5_krcache;
+
+#define KRCACHE(X) ((krb5_krcache *)(X)->data.data)
+
+static krb5_error_code KRB5_CALLCONV
+krcc_get_first(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_get_next(krb5_context context,
+             krb5_ccache id,
+             krb5_cc_cursor *cursor,
+             krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_end_get(krb5_context context,
+            krb5_ccache id,
+            krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor);
+
+static krb5_error_code
+clear_cache_keyring(krb5_context context, key_serial_t *pcache_id);
+
+static krb5_error_code
+alloc_cache(krb5_context context,
+           key_serial_t collection_id,
+           key_serial_t cache_id,
+           const char *anchor_name,
+           const char *collection_name,
+           const char *subsidiary_name,
+           krb5_krcache **data);
+
+static krb5_error_code
+save_principal(krb5_context context,
+              key_serial_t cache_id,
+              krb5_const_principal princ,
+              key_serial_t *pprinc_id);
+
+static krb5_error_code
+save_time_offsets(krb5_context context,
+                 key_serial_t cache_id,
+                 int32_t sec_offset,
+                 int32_t usec_offset);
+
+static void
+update_change_time(krb5_context context,
+                  krb5_timestamp now,
+                  krb5_krcache *d);
+
+/*
+ * 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_KEYCTL_GET_PERSISTENT
+#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
+
+/*
+ * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING
+ * will resolve to the user session keyring for ID lookup and reading, but in
+ * some kernel versions, writing to that special keyring will instead create a
+ * new empty session keyring for the process.  We do not want that; the keys we
+ * create would be invisible to other processes.  We can work around that
+ * behavior by explicitly writing to the user session keyring when it matches
+ * the session keyring.  This function returns the keyring we should write to
+ * for the session anchor.
+ */
+static key_serial_t
+session_write_anchor(void)
+{
+    key_serial_t s, u;
+
+    s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
+    u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
+
+    return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
+}
+
+/*
+ * 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 *pkey)
+{
+    key_serial_t key;
+
+    *pkey = -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 || keyctl_link(key, parent) == -1)
+               return errno;
+       } else {
+           key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
+           if (key == -1)
+               return errno;
+       }
+    }
+
+    *pkey = 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(krb5_context context,
+              const char *residual,
+              char **panchor_name,
+              char **pcollection_name,
+              char **psubsidiary_name)
+{
+    char *anchor_name = NULL;
+    char *collection_name = NULL;
+    char *subsidiary_name = NULL;
+    const char *sep;
+
+    *panchor_name = NULL;
+    *pcollection_name = NULL;
+    *psubsidiary_name = 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 nomem;
+    } else {
+       anchor_name = strndup(residual, sep - residual);
+       if (anchor_name == NULL)
+           goto nomem;
+       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 nomem;
+
+       subsidiary_name = NULL;
+    } else {
+       collection_name = strndup(residual, sep - residual);
+       if (collection_name == NULL)
+           goto nomem;
+
+       subsidiary_name = strdup(sep + 1);
+       if (subsidiary_name == NULL)
+           goto nomem;
+    }
+
+    *panchor_name = anchor_name;
+    *pcollection_name = collection_name;
+    *psubsidiary_name = subsidiary_name;
+
+    return 0;
+
+nomem:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+
+    return krb5_enomem(context);
+}
+
+/*
+ * 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_p(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 **panchor_name,
+           char **pcollection_name,
+           char **psubsidiary_name)
+{
+    const char *defname;
+
+    *panchor_name = *pcollection_name = *psubsidiary_name = NULL;
+
+    defname = krb5_cc_default_name(context);
+    if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
+       return 0;
+
+    return parse_residual(context, defname + 8,
+                         panchor_name, pcollection_name, psubsidiary_name);
+}
+
+/* Create a residual identifying a subsidiary cache. */
+static krb5_error_code
+make_subsidiary_residual(krb5_context context,
+                        const char *anchor_name,
+                        const char *collection_name,
+                        const char *subsidiary_name,
+                        char **presidual)
+{
+    if (asprintf(presidual, "%s:%s:%s", anchor_name, collection_name,
+                subsidiary_name) < 0) {
+       *presidual = NULL;
+       return krb5_enomem(context);
+    }
+
+    return 0;
+}
+
+/*
+ * Retrieve or create a keyring for collection_name within the anchor, and set
+ * *collection_id to its serial number.
+ */
+static krb5_error_code
+get_collection(krb5_context context,
+              const char *anchor_name,
+              const char *collection_name,
+              key_serial_t *pcollection_id)
+{
+    krb5_error_code ret;
+    key_serial_t persistent_id, anchor_id, possess_id = 0;
+    char *ckname, *cnend;
+    uid_t uidnum;
+
+    *pcollection_id = 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 = (uid_t)strtol(collection_name, &cnend, 10);
+           if (errno || *cnend != '\0')
+               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,
+                                     pcollection_id);
+    }
+
+    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 = session_write_anchor();
+    } 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 = session_write_anchor();
+    } 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 krb5_enomem(context);
+
+    ret = find_or_create_keyring(anchor_id, possess_id, ckname,
+                                pcollection_id);
+    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;
+    krb5_storage *sp;
+    krb5_data payload;
+    key_serial_t key;
+
+    sp = krb5_storage_emem();
+    if (sp == NULL) {
+       krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
+       return KRB5_CC_NOMEM;
+    }
+    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
+
+    ret = krb5_store_int32(sp, KRCC_COLLECTION_VERSION);
+    if (ret)
+       goto cleanup;
+
+    ret = krb5_store_string(sp, subsidiary_name);
+    if (ret)
+       goto cleanup;
+
+    ret = krb5_storage_to_data(sp, &payload);
+    if (ret)
+       goto cleanup;
+
+    key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
+                 payload.data, payload.length, collection_id);
+    ret = (key == -1) ? errno : 0;
+    krb5_data_free(&payload);
+
+cleanup:
+    krb5_storage_free(sp);
+
+    return ret;
+}
+
+static krb5_error_code
+parse_index(krb5_context context,
+           int32_t *version,
+           char **primary,
+           const unsigned char *payload,
+           size_t psize)
+{
+    krb5_error_code ret;
+    krb5_data payload_data;
+    krb5_storage *sp;
+
+    payload_data.length = psize;
+    payload_data.data = rk_UNCONST(payload);
+
+    sp = krb5_storage_from_data(&payload_data);
+    if (sp == NULL)
+       return KRB5_CC_NOMEM;
+
+    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
+
+    ret = krb5_ret_int32(sp, version);
+    if (ret == 0)
+       ret = krb5_ret_string(sp, primary);
+
+    krb5_storage_free(sp);
+
+    return ret;
+}
+
+/*
+ * Get or initialize the primary name within collection_id and set
+ * *subsidiary 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 **psubsidiary)
+{
+    krb5_error_code ret;
+    key_serial_t primary_id, legacy;
+    void *payload = NULL;
+    int payloadlen;
+    int32_t version;
+    char *subsidiary_name = NULL;
+
+    *psubsidiary = 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 = krb5_enomem(context);
+           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 = parse_index(context, &version, &subsidiary_name, payload,
+                         payloadlen);
+       if (ret)
+           goto cleanup;
+
+       if (version != KRCC_COLLECTION_VERSION) {
+           ret = KRB5_KCC_UNKNOWN_VERSION;
+           goto cleanup;
+       }
+    }
+
+    *psubsidiary = subsidiary_name;
+    subsidiary_name = NULL;
+
+cleanup:
+    free(payload);
+    free(subsidiary_name);
+
+    return ret;
+}
+
+/*
+ * Note: MIT keyring code uses krb5int_random_string() as if the second argument
+ * is a character count rather than a size. The function below takes a character
+ * count to match the usage in this file correctly.
+ */
+static krb5_error_code
+generate_random_string(krb5_context context, char *s, size_t slen)
+{
+    static char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
+    char *p;
+    size_t i;
+
+    p = malloc(slen);
+    if (p == NULL)
+       return krb5_enomem(context);
+
+    krb5_generate_random_block(p, slen);
+
+    for (i = 0; i < slen; i++)
+       s[i] = chars[p[i] % (sizeof(chars) - 1)];
+
+    s[i] = '\0';
+    free(p);
+
+    return 0;
+}
+
+/*
+ * Create a keyring with a unique random name within collection_id.  Set
+ * *subsidiary to its name and *cache_id to its key serial number.
+ */
+static krb5_error_code
+add_unique_keyring(krb5_context context,
+                  key_serial_t collection_id,
+                  char **psubsidiary,
+                  key_serial_t *pcache_id)
+{
+    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;
+
+    *psubsidiary = NULL;
+    *pcache_id = 0;
+
+    memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
+
+    for (key = -1, tries = 0; tries < 5; tries++) {
+       ret = generate_random_string(context, uniquename + prefixlen,
+                                    KRCC_NAME_RAND_CHARS);
+       if (ret)
+           return ret;
+
+       key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0);
+       if (key == -1) {
+           /* 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 == -1)
+               return errno;
+           break;
+       }
+    }
+
+    if (key == -1)
+       return KRB5_CC_BADNAME;
+
+    *psubsidiary = strdup(uniquename);
+    if (*psubsidiary == NULL)
+       return krb5_enomem(context);
+
+    *pcache_id = key;
+
+    return 0;
+}
+
+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 *pkey)
+{
+    key_serial_t key;
+
+    *pkey = -1;
+
+    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) {
+           *pkey = key;
+           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);
+    if (key == -1)
+       return errno;
+
+    *pkey = key;
+
+    return 0;
+}
+
+static void
+update_keyring_expiration(krb5_context context,
+                         krb5_ccache id,
+                         key_serial_t cache_id,
+                         krb5_timestamp now)
+{
+    krb5_cc_cursor cursor;
+    krb5_creds creds;
+    krb5_timestamp endtime = 0;
+    unsigned int timeout;
+
+    /*
+     * We have no way to know what is the actual timeout set on the keyring.
+     * We also cannot keep track of it in a local variable as another process
+     * can always modify the keyring independently, so just always enumerate
+     * all start TGT keys and find out the highest endtime time.
+     */
+    if (krcc_get_first(context, id, &cursor) != 0)
+       return;
+
+    for (;;) {
+       if (krcc_get_next(context, id, &cursor, &creds) != 0)
+           break;
+       if (creds.times.endtime > endtime)
+           endtime = creds.times.endtime;
+       krb5_free_cred_contents(context, &creds);
+    }
+    (void) krcc_end_get(context, id, &cursor);
+
+    if (endtime == 0)  /* No creds with end times */
+       return;
+
+    /*
+     * Setting the timeout to zero would reset the timeout, so we set it to one
+     * second instead if creds are already expired.
+     */
+    timeout = endtime > now ? endtime - now : 1;
+    (void) keyctl_set_timeout(cache_id, timeout);
+}
+
+/*
+ * Create or overwrite the cache keyring, and set the default principal.
+ */
+static krb5_error_code
+initialize_internal(krb5_context context,
+                   krb5_ccache id,
+                   krb5_const_principal princ)
+{
+    krb5_krcache *data = KRCACHE(id);
+    krb5_error_code ret;
+    const char *cache_name, *p;
+    krb5_krcache_and_princ_id ids;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    heim_base_exchange_64(&ids.krcu_cache_and_princ_id, data->krc_cache_and_principal_id);
+
+    ret = clear_cache_keyring(context, &ids.krcu_cache_id);
+    if (ret)
+       return ret;
+
+    if (ids.krcu_cache_id == 0) {
+       /*
+        * The key didn't exist at resolve time, or was destroyed after resolving.
+        * Check again and create the key if it still isn't there.
+         */
+       p = strrchr(data->krc_name, ':');
+       cache_name = (p != NULL) ? p + 1 : data->krc_name;
+       ret = find_or_create_keyring(data->krc_coll_id, 0, cache_name, &ids.krcu_cache_id);
+       if (ret)
+           return ret;
+    }
+
+    /*
+     * 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_p(data->krc_name))
+       (void) keyctl_link(ids.krcu_cache_id, session_write_anchor());
+
+    if (princ != NULL) {
+       ret = save_principal(context, ids.krcu_cache_id, princ, &ids.krcu_princ_id);
+       if (ret)
+           return ret;
+    } else
+       ids.krcu_princ_id = 0;
+
+    /*
+     * Save time offset if it is valid and this is not a legacy cache.  Legacy
+     * applications would fail to parse the new key in the cache keyring.
+     */
+    if (context->kdc_sec_offset && !is_legacy_cache_name_p(data->krc_name)) {
+       ret = save_time_offsets(context,
+                               ids.krcu_cache_id,
+                               context->kdc_sec_offset,
+                               context->kdc_usec_offset);
+       if (ret)
+           return ret;
+    }
+
+    /* update cache and principal IDs atomically */
+    heim_base_exchange_64(&data->krc_cache_and_principal_id, ids.krcu_cache_and_princ_id);
+
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+    krb5_error_code ret;
+
+    if (princ == NULL)
+       return KRB5_CC_BADNAME;
+
+    ret = initialize_internal(context, id, princ);
+    if (ret == 0)
+       update_change_time(context, 0, KRCACHE(id));
+
+    return ret;
+}
+
+/* Release the ccache handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_close(krb5_context context, krb5_ccache id)
+{
+    krb5_krcache *data = KRCACHE(id);
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    free(data->krc_name);
+    krb5_data_free(&id->data);
+
+    return 0;
+}
+
+/*
+ * Clear out a ccache keyring, unlinking all keys within it.
+ */
+static krb5_error_code
+clear_cache_keyring(krb5_context context,
+                   key_serial_t *pcache_id)
+{
+    int res;
+
+    _krb5_debug(context, 10, "clear_cache_keyring: cache_id %d\n", *pcache_id);
+
+    if (*pcache_id != 0) {
+       res = keyctl_clear(*pcache_id);
+       if (res == -1 && (errno == EACCES || errno == ENOKEY)) {
+           /*
+            * Possibly the keyring was destroyed between krcc_resolve() and now;
+            * if we really don't have permission, we will fail later.
+            */
+           res = 0;
+           *pcache_id = 0;
+       }
+       if (res == -1)
+           return errno;
+    }
+
+    return 0;
+}
+
+/* Destroy the cache keyring */
+static krb5_error_code KRB5_CALLCONV
+krcc_destroy(krb5_context context, krb5_ccache id)
+{
+    krb5_error_code ret = 0;
+    krb5_krcache *data = KRCACHE(id);
+    int res;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    /* no atomics, destroy is not thread-safe */
+    (void) clear_cache_keyring(context, &data->krc_cache_id);
+
+    if (data->krc_cache_id != 0) {
+       res = keyctl_unlink(data->krc_cache_id, data->krc_coll_id);
+       if (res < 0) {
+           ret = errno;
+           _krb5_debug(context, 10, "unlinking key %d from ring %d: %s",
+                       data->krc_cache_id, data->krc_coll_id, error_message(errno));
+       }
+       /* If this is a legacy cache, unlink it from the session anchor. */
+       if (is_legacy_cache_name_p(data->krc_name))
+           (void) keyctl_unlink(data->krc_cache_id, session_write_anchor());
+    }
+
+    data->krc_princ_id = 0;
+
+    /* krcc_close is called by libkrb5, do not double-free */
+    return ret;
+}
+
+/* Create a cache handle for a cache ID. */
+static krb5_error_code
+make_cache(krb5_context context,
+          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)
+{
+    krb5_error_code ret;
+    krb5_krcache *data;
+    key_serial_t princ_id = 0;
+
+    /* Determine the key containing principal information, if present. */
+    princ_id = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, 0);
+    if (princ_id == -1)
+       princ_id = 0;
+
+    ret = alloc_cache(context, collection_id, cache_id,
+                     anchor_name, collection_name, subsidiary_name, &data);
+    if (ret)
+       return ret;
+
+    if (*cache == NULL) {
+       ret = _krb5_cc_allocate(context, &krb5_krcc_ops, cache);
+       if (ret) {
+           free(data->krc_name);
+           free(data);
+           return ret;
+       }
+    }
+
+    data->krc_princ_id = princ_id;
+
+    (*cache)->data.data = data;
+    (*cache)->data.length = sizeof(*data);
+
+    return 0;
+}
+
+/* Create a keyring ccache handle for the given residual string. */
+static krb5_error_code KRB5_CALLCONV
+krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual)
+{
+    krb5_error_code ret;
+    key_serial_t collection_id, cache_id;
+    char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+
+    ret = parse_residual(context, residual, &anchor_name, &collection_name,
+                        &subsidiary_name);
+    if (ret)
+       goto cleanup;
+
+    ret = get_collection(context, anchor_name, collection_name, &collection_id);
+    if (ret)
+       goto cleanup;
+
+    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;
+    }
+
+    /* 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;
+
+    ret = make_cache(context, collection_id, cache_id, anchor_name,
+                    collection_name, subsidiary_name, id);
+    if (ret)
+       goto cleanup;
+
+cleanup:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+
+    return ret;
+}
+
+struct krcc_cursor {
+    size_t numkeys;
+    size_t currkey;
+    key_serial_t princ_id;
+    key_serial_t offsets_id;
+    key_serial_t *keys;
+};
+
+/* Prepare for a sequential iteration over the cache keyring. */
+static krb5_error_code
+krcc_get_first(krb5_context context,
+              krb5_ccache id,
+              krb5_cc_cursor *cursor)
+{
+    struct krcc_cursor *krcursor;
+    krb5_krcache *data = KRCACHE(id);
+    key_serial_t cache_id;
+    void *keys;
+    long size;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    heim_base_exchange_32(&cache_id, data->krc_cache_id);
+
+    if (cache_id == 0)
+       return KRB5_FCC_NOFILE;
+
+    size = keyctl_read_alloc(cache_id, &keys);
+    if (size == -1) {
+       _krb5_debug(context, 10, "Error getting from keyring: %s\n",
+                   strerror(errno));
+       return KRB5_CC_IO;
+    }
+
+    krcursor = calloc(1, sizeof(*krcursor));
+    if (krcursor == NULL) {
+       free(keys);
+       return KRB5_CC_NOMEM;
+    }
+
+    heim_base_exchange_32(&krcursor->princ_id, data->krc_princ_id);
+    krcursor->offsets_id = keyctl_search(cache_id, KRCC_KEY_TYPE_USER,
+                                        KRCC_TIME_OFFSETS, 0);
+    krcursor->numkeys = size / sizeof(key_serial_t);
+    krcursor->keys = keys;
+
+    *cursor = krcursor;
+
+    return 0;
+}
+
+static krb5_error_code
+keyctl_read_krb5_data(key_serial_t keyid, krb5_data *payload)
+{
+    krb5_data_zero(payload);
+
+    payload->length = keyctl_read_alloc(keyid, &payload->data);
+
+    return (payload->length == -1) ? KRB5_FCC_NOFILE : 0;
+}
+
+/* Get the next credential from the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_next(krb5_context context,
+             krb5_ccache id,
+             krb5_cc_cursor *cursor,
+             krb5_creds *creds)
+{
+    struct krcc_cursor *krcursor;
+    krb5_error_code ret;
+    krb5_data payload;
+    krb5_storage *sp;
+
+    memset(creds, 0, sizeof(krb5_creds));
+
+    krcursor = *cursor;
+    if (krcursor == NULL)
+       return KRB5_CC_END;
+
+    if (krcursor->currkey >= krcursor->numkeys)
+       return KRB5_CC_END;
+
+    /*
+     * If we're pointing at the entry with the principal, or at the key
+     * with the time offsets, skip it.
+     */
+    while (krcursor->keys[krcursor->currkey] == krcursor->princ_id ||
+          krcursor->keys[krcursor->currkey] == krcursor->offsets_id) {
+       krcursor->currkey++;
+       if (krcursor->currkey >= krcursor->numkeys)
+           return KRB5_CC_END;
+    }
+
+    ret = keyctl_read_krb5_data(krcursor->keys[krcursor->currkey], &payload);
+    if (ret) {
+       _krb5_debug(context, 10, "Error reading key %d: %s\n",
+                   krcursor->keys[krcursor->currkey],
+                   strerror(errno));
+       return ret;
+    }
+    krcursor->currkey++;
+
+    sp = krb5_storage_from_data(&payload);
+    if (sp == NULL) {
+       ret = KRB5_CC_IO;
+    } else {
+       ret = krb5_ret_creds(sp, creds);
+       krb5_storage_free(sp);
+    }
+
+    krb5_data_free(&payload);
+
+    return ret;
+}
+
+/* Release an iteration cursor. */
+static krb5_error_code KRB5_CALLCONV
+krcc_end_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+    struct krcc_cursor *krcursor = *cursor;
+
+    if (krcursor != NULL) {
+       free(krcursor->keys);
+       free(krcursor);
+    }
+
+    *cursor = NULL;
+
+    return 0;
+}
+
+/* Create keyring data for a credential cache. */
+static krb5_error_code
+alloc_cache(krb5_context context,
+           key_serial_t collection_id,
+           key_serial_t cache_id,
+           const char *anchor_name,
+           const char *collection_name,
+           const char *subsidiary_name,
+           krb5_krcache **pdata)
+{
+    krb5_error_code ret;
+    krb5_krcache *data;
+
+    *pdata = NULL;
+
+    data = calloc(1, sizeof(*data));
+    if (data == NULL)
+       return KRB5_CC_NOMEM;
+
+    ret = make_subsidiary_residual(context, anchor_name, collection_name,
+                                  subsidiary_name, &data->krc_name);
+    if (ret) {
+       free(data);
+       return ret;
+    }
+
+    data->krc_princ_id = 0;
+    data->krc_cache_id = cache_id;
+    data->krc_coll_id = collection_id;
+    data->krc_changetime = 0;
+    data->krc_is_legacy = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
+
+    update_change_time(context, 0, data);
+
+    *pdata = data;
+
+    return 0;
+}
+
+/* Create a new keyring cache with a unique name. */
+static krb5_error_code KRB5_CALLCONV
+krcc_gen_new(krb5_context context, krb5_ccache *id)
+{
+    krb5_error_code ret;
+    char *anchor_name, *collection_name, *subsidiary_name;
+    char *new_subsidiary_name = NULL, *new_residual = NULL;
+    krb5_krcache *data;
+    key_serial_t collection_id;
+    key_serial_t cache_id = 0;
+
+    /* Determine the collection in which we will create the cache.*/
+    ret = get_default(context, &anchor_name, &collection_name,
+                     &subsidiary_name);
+    if (ret)
+       return ret;
+
+    if (anchor_name == NULL) {
+       ret = parse_residual(context, KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
+                            &collection_name, &subsidiary_name);
+       if (ret)
+           return ret;
+    }
+    if (subsidiary_name != NULL) {
+       krb5_set_error_message(context, KRB5_DCC_CANNOT_CREATE,
+               N_("Can't create new subsidiary cache because default cache "
+                  "is already a subsidiary", ""));
+       ret = KRB5_DCC_CANNOT_CREATE;
+       goto cleanup;
+    }
+
+    /* Make a unique keyring within the chosen collection. */
+    ret = get_collection(context, anchor_name, collection_name, &collection_id);
+    if (ret)
+       goto cleanup;
+
+    ret = add_unique_keyring(context, collection_id, &new_subsidiary_name, &cache_id);
+    if (ret)
+       goto cleanup;
+
+    ret = alloc_cache(context, collection_id, cache_id,
+                     anchor_name, collection_name, new_subsidiary_name,
+                     &data);
+    if (ret)
+       goto cleanup;
+
+    (*id)->data.data = data;
+    (*id)->data.length = sizeof(*data);
+
+cleanup:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+    free(new_subsidiary_name);
+    free(new_residual);
+
+    return ret;
+}
+
+/* Return an alias to the residual string of the cache. */
+static const char *KRB5_CALLCONV
+krcc_get_name(krb5_context context, krb5_ccache id)
+{
+    return KRCACHE(id)->krc_name;
+}
+
+/* Retrieve a copy of the default principal, if the cache is initialized. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_principal(krb5_context context,
+                  krb5_ccache id,
+                  krb5_principal *princ)
+{
+    krb5_krcache *data = KRCACHE(id);
+    krb5_error_code ret;
+    krb5_storage *sp = NULL;
+    krb5_data payload;
+    krb5_krcache_and_princ_id ids;
+
+    krb5_data_zero(&payload);
+    *princ = NULL;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    heim_base_exchange_64(&ids.krcu_cache_and_princ_id, data->krc_cache_and_principal_id);
+
+    if (ids.krcu_cache_id == 0 || ids.krcu_princ_id == 0) {
+       ret = KRB5_FCC_NOFILE;
+       krb5_set_error_message(context, ret,
+                              N_("Credentials cache keyring '%s' not found", ""),
+                              data->krc_name);
+       goto cleanup;
+    }
+
+    ret = keyctl_read_krb5_data(ids.krcu_princ_id, &payload);
+    if (ret) {
+       _krb5_debug(context, 10, "Reading principal key %d: %s\n",
+                   ids.krcu_princ_id, strerror(errno));
+       goto cleanup;
+    }
+
+    sp = krb5_storage_from_data(&payload);
+    if (sp == NULL) {
+       ret = KRB5_CC_IO;
+       goto cleanup;
+    }
+
+    ret = krb5_ret_principal(sp, princ);
+    if (ret)
+       goto cleanup;
+
+cleanup:
+    krb5_storage_free(sp);
+    krb5_data_free(&payload);
+
+    return ret;
+}
+
+/* Remove a cred from the cache keyring */
+static krb5_error_code KRB5_CALLCONV
+krcc_remove_cred(krb5_context context, krb5_ccache id,
+                krb5_flags which, krb5_creds *mcred)
+{
+    krb5_krcache *data = KRCACHE(id);
+    krb5_error_code ret, ret2;
+    krb5_cc_cursor cursor;
+    krb5_creds found_cred;
+    krb5_krcache_and_princ_id ids;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    ret = krcc_get_first(context, id, &cursor);
+    if (ret)
+       return ret;
+
+    heim_base_exchange_64(&ids.krcu_cache_and_princ_id, data->krc_cache_and_principal_id);
+
+    while ((ret = krcc_get_next(context, id, &cursor, &found_cred)) == 0) {
+       struct krcc_cursor *krcursor = cursor;
+
+       if (!krb5_compare_creds(context, which, mcred, &found_cred)) {
+           krb5_free_cred_contents(context, &found_cred);
+           continue;
+       }
+
+       _krb5_debug(context, 10, "Removing cred %d from cache_id %d, princ_id %d\n",
+                   krcursor->keys[krcursor->currkey],
+                   ids.krcu_cache_id, ids.krcu_princ_id);
+
+       keyctl_invalidate(krcursor->keys[krcursor->currkey]);
+       krcursor->keys[krcursor->currkey] = 0;
+       krb5_free_cred_contents(context, &found_cred);
+    }
+
+    ret2 = krcc_end_get(context, id, &cursor);
+    if (ret == 0)
+       ret = ret2;
+    else if (ret == KRB5_CC_END)
+       ret = 0;
+
+    return ret;
+}
+
+/* Set flags on the cache.  (We don't care about any flags.) */
+static krb5_error_code KRB5_CALLCONV
+krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
+{
+    return 0;
+}
+
+static int KRB5_CALLCONV
+krcc_get_version(krb5_context context, krb5_ccache id)
+{
+    return 0;
+}
+/* Store a credential in the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
+{
+    krb5_error_code ret;
+    krb5_krcache *data = KRCACHE(id);
+    krb5_storage *sp = NULL;
+    char *keyname = NULL;
+    key_serial_t cred_key, cache_id;
+    krb5_timestamp now;
+    krb5_data payload;
+
+    krb5_data_zero(&payload);
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    heim_base_exchange_32(&cache_id, data->krc_cache_id);
+
+    if (cache_id == 0)
+       return KRB5_FCC_NOFILE;
+
+    ret = krb5_unparse_name(context, creds->server, &keyname);
+    if (ret)
+       goto cleanup;
+
+    sp = krb5_storage_emem();
+    if (sp == NULL) {
+       krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
+       ret = KRB5_CC_NOMEM;
+       goto cleanup;
+    }
+
+    ret = krb5_store_creds(sp, creds);
+    if (ret)
+       goto cleanup;
+
+    ret = krb5_storage_to_data(sp, &payload);
+    if (ret)
+       goto cleanup;
+
+    _krb5_debug(context, 10, "krcc_store: adding new key '%s' to keyring %d\n",
+               keyname, cache_id);
+    ret = add_cred_key(keyname, payload.data, payload.length, cache_id,
+                      data->krc_is_legacy, &cred_key);
+    if (ret)
+       goto cleanup;
+
+    ret = krb5_timeofday(context, &now);
+    if (ret)
+       goto cleanup;
+
+    update_change_time(context, now, data);
+
+    /* Set timeout on credential key */
+    if (creds->times.endtime > now)
+       (void) keyctl_set_timeout(cred_key, creds->times.endtime - now);
+
+    /* Set timeout on credential cache keyring */
+    update_keyring_expiration(context, id, cache_id, now);
+
+cleanup:
+    krb5_data_free(&payload);
+    krb5_storage_free(sp);
+    krb5_xfree(keyname);
+
+    return ret;
+}
+
+/*
+ * Get the cache's last modification time.  (This is currently broken; it
+ * returns only the last change made using this handle.)
+ */
+static krb5_error_code KRB5_CALLCONV
+krcc_lastchange(krb5_context context,
+               krb5_ccache id,
+               krb5_timestamp *change_time)
+{
+    krb5_krcache *data = KRCACHE(id);
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    heim_base_exchange_time_t(change_time, data->krc_changetime);
+
+    return 0;
+}
+
+static krb5_error_code
+save_principal(krb5_context context,
+              key_serial_t cache_id,
+              krb5_const_principal princ,
+              key_serial_t *pprinc_id)
+{
+    krb5_error_code ret;
+    krb5_storage *sp;
+    key_serial_t newkey;
+    krb5_data payload;
+
+    krb5_data_zero(&payload);
+
+    sp = krb5_storage_emem();
+    if (sp == NULL) {
+       krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
+       return KRB5_CC_NOMEM;
+    }
+
+    ret = krb5_store_principal(sp, princ);
+    if (ret) {
+       krb5_storage_free(sp);
+       return ret;
+    }
+
+    ret = krb5_storage_to_data(sp, &payload);
+    if (ret) {
+       krb5_storage_free(sp);
+       return ret;
+    }
+
+    krb5_storage_free(sp);
+    {
+       krb5_error_code tmp;
+       char *princname = NULL;
+
+       tmp = krb5_unparse_name(context, princ, &princname);
+       _krb5_debug(context, 10, "save_principal: adding new key '%s' "
+                   "to keyring %d for principal '%s'\n",
+                   KRCC_SPEC_PRINC_KEYNAME, cache_id,
+                   tmp ? "<unknown>" : princname);
+       if (tmp == 0)
+           krb5_xfree(princname);
+    }
+
+    /* Add new key into keyring */
+    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
+                    payload.data, payload.length, cache_id);
+    if (newkey == -1) {
+       ret = errno;
+       _krb5_debug(context, 10, "Error adding principal key: %s\n", strerror(ret));
+    } else {
+       ret = 0;
+       *pprinc_id = newkey;
+    }
+
+    krb5_data_free(&payload);
+
+    return ret;
+}
+
+/* Add a key to the cache keyring containing the given time offsets. */
+static krb5_error_code
+save_time_offsets(krb5_context context,
+                 key_serial_t cache_id,
+                 int32_t sec_offset,
+                 int32_t usec_offset)
+{
+    krb5_error_code ret;
+    key_serial_t newkey;
+    krb5_storage *sp;
+    krb5_data payload;
+
+    krb5_data_zero(&payload);
+
+    sp = krb5_storage_emem();
+    if (sp == NULL) {
+       krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
+       return KRB5_CC_NOMEM;
+    }
+
+    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
+
+    ret = krb5_store_int32(sp, sec_offset);
+    if (ret == 0)
+       ret = krb5_store_int32(sp, usec_offset);
+    if (ret) {
+       krb5_storage_free(sp);
+       return ret;
+    }
+
+    ret = krb5_storage_to_data(sp, &payload);
+    if (ret) {
+       krb5_storage_free(sp);
+       return ret;
+    }
+
+    krb5_storage_free(sp);
+
+    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload.data,
+                    payload.length, cache_id);
+    ret = newkey == -1 ? errno : 0;
+
+    krb5_data_free(&payload);
+
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat offset)
+{
+    krb5_krcache *data = KRCACHE(id);
+    key_serial_t cache_id;
+    krb5_error_code ret;
+
+    heim_base_exchange_32(&cache_id, &data->krc_cache_id);
+    ret = save_time_offsets(context, cache_id, (int32_t)offset, 0);
+    if (ret == 0)
+       update_change_time(context, 0, data);
+
+    return ret;
+}
+
+/* Retrieve and parse the key in the cache keyring containing time offsets. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_kdc_offset(krb5_context context,
+                   krb5_ccache id,
+                   krb5_deltat *offset)
+{
+    krb5_krcache *data = KRCACHE(id);
+    krb5_error_code ret = 0;
+    key_serial_t key, cache_id;
+    krb5_storage *sp = NULL;
+    krb5_data payload;
+    int32_t sec_offset, usec_offset;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    krb5_data_zero(&payload);
+    heim_base_exchange_32(&cache_id, data->krc_cache_id);
+
+    if (cache_id == 0) {
+       ret = KRB5_FCC_NOFILE;
+       goto cleanup;
+    }
+
+    key = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, 0);
+    if (key == -1) {
+       ret = ENOENT;
+       goto cleanup;
+    }
+
+    ret = keyctl_read_krb5_data(key, &payload);
+    if (ret) {
+       _krb5_debug(context, 10, "Reading time offsets key %d: %s\n",
+                   key, strerror(errno));
+       goto cleanup;
+    }
+
+    sp = krb5_storage_from_data(&payload);
+    if (sp == NULL) {
+       ret = KRB5_CC_IO;
+       goto cleanup;
+    }
+
+    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
+
+    ret = krb5_ret_int32(sp, &sec_offset);
+    if (ret == 0)
+       krb5_ret_int32(sp, &usec_offset);
+    if (ret) {
+       ret = KRB5_CC_END;
+       goto cleanup;
+    }
+
+    *offset = sec_offset;
+
+cleanup:
+    krb5_storage_free(sp);
+    krb5_data_free(&payload);
+
+    return ret;
+}
+
+struct krcc_iter {
+    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;
+};
+
+static krb5_error_code KRB5_CALLCONV
+krcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
+{
+    struct krcc_iter *iter;
+    krb5_error_code ret;
+    void *keys;
+    long size;
+
+    *cursor = NULL;
+
+    iter = calloc(1, sizeof(*iter));
+    if (iter == NULL) {
+       ret = krb5_enomem(context);
+       goto error;
+    }
+    iter->first = TRUE;
+
+    ret = get_default(context, &iter->anchor_name, &iter->collection_name,
+                     &iter->subsidiary_name);
+    if (ret)
+       goto error;
+
+    /* If there is no default collection, return an empty cursor. */
+    if (iter->anchor_name == NULL) {
+       *cursor = iter;
+       return 0;
+    }
+
+    ret = get_collection(context, iter->anchor_name, iter->collection_name,
+                        &iter->collection_id);
+    if (ret)
+       goto error;
+
+    if (iter->subsidiary_name == NULL) {
+       ret = get_primary_name(context, iter->anchor_name,
+                              iter->collection_name, iter->collection_id,
+                              &iter->primary_name);
+       if (ret)
+           goto error;
+
+       size = keyctl_read_alloc(iter->collection_id, &keys);
+       if (size == -1) {
+           ret = errno;
+           goto error;
+       }
+       iter->keys = keys;
+       iter->num_keys = size / sizeof(key_serial_t);
+    }
+
+    *cursor = iter;
+
+    return 0;
+
+error:
+    krcc_end_cache_get(context, iter);
+
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_get_cache_next(krb5_context context,
+                   krb5_cc_cursor cursor,
+                   krb5_ccache *cache)
+{
+    krb5_error_code ret;
+    struct krcc_iter *iter = cursor;
+    key_serial_t key, cache_id = 0;
+    const char *first_name, *keytype, *sep, *subsidiary_name;
+    size_t keytypelen;
+    char *description = NULL;
+
+    *cache = NULL;
+
+    /* No keyring available */
+    if (iter->collection_id == 0)
+       return KRB5_CC_END;
+
+    if (iter->first) {
+       /*
+        * Look for the primary cache for a collection cursor, or the
+        * subsidiary cache for a subsidiary cursor.
+        */
+       iter->first = FALSE;
+       first_name = (iter->primary_name != NULL) ? iter->primary_name :
+                    iter->subsidiary_name;
+       cache_id = keyctl_search(iter->collection_id, KRCC_KEY_TYPE_KEYRING,
+                                first_name, 0);
+       if (cache_id != -1) {
+           return make_cache(context, iter->collection_id, cache_id,
+                             iter->anchor_name, iter->collection_name,
+                             first_name, cache);
+       }
+    }
+
+    /* A subsidiary cursor yields at most the first cache. */
+    if (iter->subsidiary_name != NULL)
+       return KRB5_CC_END;
+
+    keytype = KRCC_KEY_TYPE_KEYRING ";";
+    keytypelen = strlen(keytype);
+
+    for (ret = KRB5_CC_END; iter->next_key < iter->num_keys; iter->next_key++) {
+       free(description);
+       description = NULL;
+
+       /*
+        * Get the key description, which should have the form:
+        *   typename;UID;GID;permissions;description
+        */
+       key = iter->keys[iter->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, iter->primary_name) == 0)
+           continue;
+
+       /* We found a valid key */
+       iter->next_key++;
+       ret = make_cache(context, iter->collection_id, key, iter->anchor_name,
+                        iter->collection_name, subsidiary_name, cache);
+       break;
+    }
+
+    free(description);
+
+    return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
+{
+    struct krcc_iter *iter = cursor;
+
+    if (iter != NULL) {
+       free(iter->anchor_name);
+       free(iter->collection_name);
+       free(iter->subsidiary_name);
+       free(iter->primary_name);
+       free(iter->keys);
+
+       memset(iter, 0, sizeof(*iter));
+       free(iter);
+    }
+
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_set_default(krb5_context context, krb5_ccache id)
+{
+    krb5_krcache *data = KRCACHE(id);
+    krb5_error_code ret;
+    char *anchor_name, *collection_name, *subsidiary_name;
+    key_serial_t collection_id;
+
+    if (data == NULL)
+       return krb5_einval(context, 2);
+
+    ret = parse_residual(context, data->krc_name,
+                        &anchor_name, &collection_name, &subsidiary_name);
+    if (ret)
+       goto cleanup;
+
+    ret = get_collection(context, anchor_name, collection_name, &collection_id);
+    if (ret)
+       goto cleanup;
+
+    ret = set_primary_name(context, collection_id, subsidiary_name);
+    if (ret)
+       goto cleanup;
+
+cleanup:
+    free(anchor_name);
+    free(collection_name);
+    free(subsidiary_name);
+
+    return ret;
+}
+
+/*
+ * Utility routine: called by krcc_* functions to keep
+ * result of krcc_last_change_time up to date.
+ */
+static void
+update_change_time(krb5_context context, krb5_timestamp now, krb5_krcache *data)
+{
+    krb5_timestamp old;
+
+    if (now == 0)
+       krb5_timeofday(context, &now);
+
+    old = heim_base_exchange_time_t(&data->krc_changetime, now);
+    if (old > now) /* don't go backwards */
+       heim_base_exchange_time_t(&data->krc_changetime, old + 1);
+}
+
+static int
+move_key_to_new_keyring(key_serial_t parent, key_serial_t key,
+                       char *desc, int desc_len, void *data)
+{
+    key_serial_t cache_id = *(key_serial_t *)data;
+
+    if (parent) {
+       if (keyctl_link(key, cache_id) == -1 ||
+           keyctl_unlink(key, parent) == -1)
+           return -1;
+    }
+
+    return 0;
+}
+
+/* Move contents of one ccache to another; destroys from cache */
+static krb5_error_code KRB5_CALLCONV
+krcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
+{
+    krb5_krcache *krfrom = KRCACHE(from);
+    krb5_krcache *krto = KRCACHE(to);
+    krb5_error_code ret;
+    krb5_timestamp now;
+    key_serial_t to_cache_id;
+
+    if (krfrom == NULL || krto == NULL)
+       return krb5_einval(context, 2);
+
+    ret = initialize_internal(context, to, NULL);
+    if (ret)
+       return ret;
+
+    krb5_timeofday(context, &now);
+    heim_base_exchange_32(&to_cache_id, krto->krc_cache_id);
+
+    if (krfrom->krc_cache_id != 0) {
+       ret = recursive_key_scan(krfrom->krc_cache_id,
+                                move_key_to_new_keyring, &to_cache_id);
+       if (ret)
+           return KRB5_CC_IO;
+
+       if (keyctl_unlink(krfrom->krc_cache_id, krfrom->krc_coll_id) == -1)
+           return errno;
+
+       heim_base_exchange_32(&krto->krc_princ_id, krfrom->krc_princ_id);
+    }
+
+    update_change_time(context, now, krto);
+
+    krcc_destroy(context, from);
+    krcc_close(context, from);
+
+    return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_get_default_name(krb5_context context, char **str)
+{
+    *str = strdup("KEYRING:");
+    if (*str == NULL)
+       return krb5_enomem(context);
+
+    return 0;
+}
+
+/*
+ * ccache implementation storing credentials in the Linux keyring facility
+ * The default is to put them at the session keyring level.
+ * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
+ * be stored at the process or thread level respectively.
+ */
+KRB5_LIB_VARIABLE const krb5_cc_ops krb5_krcc_ops = {
+    KRB5_CC_OPS_VERSION,
+    "KEYRING",
+    krcc_get_name,
+    krcc_resolve,
+    krcc_gen_new,
+    krcc_initialize,
+    krcc_destroy,
+    krcc_close,
+    krcc_store,
+    NULL,                  /* retrieve */
+    krcc_get_principal,
+    krcc_get_first,
+    krcc_get_next,
+    krcc_end_get,
+    krcc_remove_cred,
+    krcc_set_flags,
+    krcc_get_version,
+    krcc_get_cache_first,
+    krcc_get_cache_next,
+    krcc_end_cache_get,
+    krcc_move,
+    krcc_get_default_name,
+    krcc_set_default,
+    krcc_lastchange,
+    krcc_set_kdc_offset,
+    krcc_get_kdc_offset,
+};
+
+#endif /* HAVE_KEYUTILS_H */
index 614308dfc6388e0dd24b1cdb8efb26a52d6ccb30..aae574744a3bd5525d73489fb11f2efe9ecd5f1f 100644 (file)
@@ -717,6 +717,8 @@ EXPORTS
        initialize_heim_error_table
        initialize_k524_error_table_r
        initialize_k524_error_table
+       initialize_k5e1_error_table_r
+       initialize_k5e1_error_table
 
         ; variables
        krb5_mcc_ops            DATA
@@ -727,6 +729,9 @@ EXPORTS
 #endif
 #ifdef HAVE_KCM
        krb5_kcm_ops            DATA
+#endif
+#ifdef HAVE_KEYUTILS
+       krb5_krcc_ops           DATA
 #endif
        krb5_wrfkt_ops          DATA
        krb5_mkt_ops            DATA
@@ -740,6 +745,7 @@ EXPORTS
        krb5_cc_type_file       DATA
        krb5_cc_type_memory     DATA
        krb5_cc_type_kcm        DATA
+       krb5_cc_type_keyring    DATA
        krb5_cc_type_scc        DATA
 
         ; Shared with GSSAPI krb5
index a6f7d312dd62ce86222b454f5b601471d0efdea0..caef19e6616bb8fd14f4a4faa4e34b98db423f87 100644 (file)
@@ -391,6 +391,7 @@ test_cache_iter(krb5_context context, const char *type, int destroy)
        krb5_principal principal;
        char *name;
 
+       heim_assert(id != NULL, "credentials cache is non-NULL");
        if (debug_flag)
            printf("name: %s\n", krb5_cc_get_name(context, id));
        ret = krb5_cc_get_principal(context, id, &principal);
@@ -683,6 +684,9 @@ main(int argc, char **argv)
 #ifdef USE_SQLITE
     test_cache_remove(context, krb5_cc_type_scc);
 #endif
+#ifdef HAVE_KEYUTILS_H
+    test_cache_remove(context, krb5_cc_type_keyring);
+#endif
 
     test_default_name(context);
     test_mcache(context);
@@ -693,6 +697,9 @@ main(int argc, char **argv)
 #endif
     test_init_vs_destroy(context, krb5_cc_type_scc);
     test_init_vs_destroy(context, krb5_cc_type_dcc);
+#ifdef HAVE_KEYUTILS_H
+    test_init_vs_destroy(context, krb5_cc_type_keyring);
+#endif
     test_mcc_default();
     test_def_cc_name(context);
 
@@ -722,6 +729,10 @@ main(int argc, char **argv)
     test_cache_iter(context, krb5_cc_type_dcc, 0);
     test_cache_iter(context, krb5_cc_type_dcc, 1);
 #endif
+#ifdef HAVE_KEYUTILS_H
+    test_cache_iter(context, krb5_cc_type_keyring, 0);
+    test_cache_iter(context, krb5_cc_type_keyring, 1);
+#endif
 
     test_copy(context, krb5_cc_type_file, krb5_cc_type_file);
     test_copy(context, krb5_cc_type_memory, krb5_cc_type_memory);
@@ -736,6 +747,34 @@ main(int argc, char **argv)
     test_copy(context, krb5_cc_type_dcc, krb5_cc_type_file);
     test_copy(context, krb5_cc_type_dcc, krb5_cc_type_scc);
 #endif
+#ifdef HAVE_KEYUTILS_H
+    test_copy(context, krb5_cc_type_keyring, krb5_cc_type_file);
+    test_copy(context, krb5_cc_type_file, krb5_cc_type_file);
+    test_copy(context, "KEYRING:", "KEYRING:bar");
+    test_copy(context, "KEYRING:bar", "KEYRING:baz");
+# ifdef HAVE_KEYCTL_GET_PERSISTENT
+    test_copy(context, krb5_cc_type_file, "KEYRING:persistent");
+    test_copy(context, "KEYRING:persistent:", krb5_cc_type_file);
+    test_copy(context, krb5_cc_type_file, "KEYRING:persistent:foo");
+    test_copy(context, "KEYRING:persistent:foo", krb5_cc_type_file);
+# endif
+    test_copy(context, krb5_cc_type_memory, "KEYRING:process:");
+    test_copy(context, "KEYRING:process:", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_memory, "KEYRING:process:foo");
+    test_copy(context, "KEYRING:process:foo", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_memory, "KEYRING:thread:");
+    test_copy(context, "KEYRING:thread:", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_memory, "KEYRING:thread:foo");
+    test_copy(context, "KEYRING:thread:foo", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_memory, "KEYRING:session:");
+    test_copy(context, "KEYRING:session:", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_memory, "KEYRING:session:foo");
+    test_copy(context, "KEYRING:session:foo", krb5_cc_type_memory);
+    test_copy(context, krb5_cc_type_file, "KEYRING:user:");
+    test_copy(context, "KEYRING:user:", krb5_cc_type_file);
+    test_copy(context, krb5_cc_type_file, "KEYRING:user:foo");
+    test_copy(context, "KEYRING:user:foo", krb5_cc_type_memory);
+#endif /* HAVE_KEYUTILS_H */
 
     test_move(context, krb5_cc_type_file);
     test_move(context, krb5_cc_type_memory);
@@ -746,6 +785,21 @@ main(int argc, char **argv)
 #if 0
     test_move(context, krb5_cc_type_dcc);
 #endif
+#ifdef HAVE_KEYUTILS_H
+    test_move(context, krb5_cc_type_keyring);
+# ifdef HAVE_KEYCTL_GET_PERSISTENT
+    test_move(context, "KEYRING:persistent:");
+    test_move(context, "KEYRING:persistent:foo");
+# endif
+    test_move(context, "KEYRING:process:");
+    test_move(context, "KEYRING:process:foo");
+    test_move(context, "KEYRING:thread:");
+    test_move(context, "KEYRING:thread:foo");
+    test_move(context, "KEYRING:session:");
+    test_move(context, "KEYRING:session:foo");
+    test_move(context, "KEYRING:user:");
+    test_move(context, "KEYRING:user:foo");
+#endif /* HAVE_KEYUTILS_H */
 
     test_prefix_ops(context, "FILE:/tmp/foo", &krb5_fcc_ops);
     test_prefix_ops(context, "FILE", &krb5_fcc_ops);
@@ -760,6 +814,10 @@ main(int argc, char **argv)
     test_prefix_ops(context, "DIR:", &krb5_dcc_ops);
     test_prefix_ops(context, "DIR:tkt1", &krb5_dcc_ops);
 #endif
+#ifdef HAVE_KEYUTILS_H
+    test_prefix_ops(context, "KEYRING:", &krb5_krcc_ops);
+    test_prefix_ops(context, "KEYRING:foo", &krb5_krcc_ops);
+#endif /* HAVE_KEYUTILS_H */
 
     krb5_cc_destroy(context, id1);
     krb5_cc_destroy(context, id2);
index db3eff7d0ecbe9387df5a2b46a2ff708d2b3df59..82a107e49df1c78e80c198ac0977b18c6c031743 100644 (file)
@@ -709,12 +709,15 @@ HEIMDAL_KRB5_2.0 {
                initialize_heim_error_table;
                initialize_k524_error_table_r;
                initialize_k524_error_table;
+               initialize_k5e1_error_table_r;
+               initialize_k5e1_error_table;
 
                # variables
                krb5_dcc_ops;
                krb5_mcc_ops;
                krb5_acc_ops;
                krb5_fcc_ops;
+               krb5_krcc_ops;
                krb5_scc_ops;
                krb5_kcm_ops;
                krb5_wrfkt_ops;
@@ -730,6 +733,7 @@ HEIMDAL_KRB5_2.0 {
                krb5_cc_type_file;
                krb5_cc_type_memory;
                krb5_cc_type_kcm;
+               krb5_cc_type_keyring;
                krb5_cc_type_scc;
 
                # shared with HDB
index e15242679bbab640476f79813ffbb561dfbc3efb..085ed95d5f34e2f7af365677d2fbd89da093be1b 100644 (file)
@@ -73,6 +73,7 @@ INCFILES=\
        $(SDKINCDIR)\krb5\asn1_err.h    \
        $(SDKINCDIR)\krb5\heim_err.h    \
        $(SDKINCDIR)\krb5\k524_err.h    \
+       $(SDKINCDIR)\krb5\k5e1_err.h    \
        $(SDKINCDIR)\krb5\krb5-protos.h \
        $(SDKINCDIR)\krb5\krb5-types.h  \
        $(SDKINCDIR)\krb5\krb5-v4compat.h       \
@@ -105,6 +106,7 @@ INCFILES=\
        $(SDKINCDIR)\heimdal\asn1_err.h \
        $(SDKINCDIR)\heimdal\heim_err.h \
        $(SDKINCDIR)\heimdal\k524_err.h \
+       $(SDKINCDIR)\heimdal\k5e1_err.h \
        $(SDKINCDIR)\heimdal\krb5-protos.h      \
        $(SDKINCDIR)\heimdal\krb5-types.h       \
        $(SDKINCDIR)\heimdal\krb5-v4compat.h    \