diff --git a/.travis.yml b/.travis.yml index 6cf2b883a..9d52b5fea 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/configure.ac b/configure.ac index f98a3c555..35781d071 100644 --- a/configure.ac +++ b/configure.ac @@ -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 + #endif + #ifdef HAVE_SYS_TYPES_H + #include + #endif + #include + ]) +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) diff --git a/include/Makefile.am b/include/Makefile.am index 696a023ed..29256a375 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -67,6 +67,7 @@ CLEANFILES = \ hx509.h \ hx509_err.h \ k524_err.h \ + k5e1_err.h \ kafs.h \ kcm-protos.h \ kdc-private.h \ diff --git a/include/config.h.w32 b/include/config.h.w32 index d52eb6b67..76a7b9aae 100644 --- a/include/config.h.w32 +++ b/include/config.h.w32 @@ -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 diff --git a/lib/base/heimbase.h b/lib/base/heimbase.h index 238796b5e..45b7ee097 100644 --- a/lib/base/heimbase.h +++ b/lib/base/heimbase.h @@ -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 @@ -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 */ diff --git a/lib/krb5/Makefile.am b/lib/krb5/Makefile.am index cdb13d576..d752deb29 100644 --- a/lib/krb5/Makefile.am +++ b/lib/krb5/Makefile.am @@ -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 diff --git a/lib/krb5/NTMakefile b/lib/krb5/NTMakefile index 2378bfbc9..edbbdf060 100644 --- a/lib/krb5/NTMakefile +++ b/lib/krb5/NTMakefile @@ -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 diff --git a/lib/krb5/constants.c b/lib/krb5/constants.c index 87147c22b..42b460f61 100644 --- a/lib/krb5/constants.c +++ b/lib/krb5/constants.c @@ -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"; diff --git a/lib/krb5/context.c b/lib/krb5/context.c index 731185f9f..bd8b2bfc5 100644 --- a/lib/krb5/context.c +++ b/lib/krb5/context.c @@ -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 index 000000000..19414f10a --- /dev/null +++ b/lib/krb5/k5e1_err.et @@ -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" diff --git a/lib/krb5/krb5.h b/lib/krb5/krb5.h index 19c89f21a..2e580c036 100644 --- a/lib/krb5/krb5.h +++ b/lib/krb5/krb5.h @@ -45,6 +45,7 @@ #include #include #include +#include #include @@ -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__ */ diff --git a/lib/krb5/krb5_locl.h b/lib/krb5/krb5_locl.h index 1ae67f5d5..0ba68450d 100644 --- a/lib/krb5/krb5_locl.h +++ b/lib/krb5/krb5_locl.h @@ -142,6 +142,7 @@ struct _krb5_krb_auth_data; #include #include +#include #include #ifdef PKINIT #include diff --git a/lib/krb5/krcache.c b/lib/krb5/krcache.c new file mode 100644 index 000000000..c00dec071 --- /dev/null +++ b/lib/krb5/krcache.c @@ -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_" 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 + +/* + * 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 ? "" : 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 */ diff --git a/lib/krb5/libkrb5-exports.def.in b/lib/krb5/libkrb5-exports.def.in index 614308dfc..aae574744 100644 --- a/lib/krb5/libkrb5-exports.def.in +++ b/lib/krb5/libkrb5-exports.def.in @@ -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 diff --git a/lib/krb5/test_cc.c b/lib/krb5/test_cc.c index a6f7d312d..caef19e66 100644 --- a/lib/krb5/test_cc.c +++ b/lib/krb5/test_cc.c @@ -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); diff --git a/lib/krb5/version-script.map b/lib/krb5/version-script.map index db3eff7d0..82a107e49 100644 --- a/lib/krb5/version-script.map +++ b/lib/krb5/version-script.map @@ -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 diff --git a/packages/windows/sdk/NTMakefile b/packages/windows/sdk/NTMakefile index e15242679..085ed95d5 100644 --- a/packages/windows/sdk/NTMakefile +++ b/packages/windows/sdk/NTMakefile @@ -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 \