From: Ken Raeburn Date: Mon, 2 Oct 2006 22:50:10 +0000 (+0000) Subject: Merge Kevin Coffman's keyring ccache branch for Linux, with some modifications: X-Git-Tag: krb5-1.6-alpha1~115 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=15d7eedfe654faaa6a74dea83b5d42b2cb626a03;p=krb5.git Merge Kevin Coffman's keyring ccache branch for Linux, with some modifications: aclocal.m4: Enable keyring ccache if the header and library are available; no configure-time option. No error if it's not found. ccdefname.c: Keep old default of FILE: cache, at least for now. libkrb5.exports: Don't export krb5_krcc_ops. ccbase.c: Only initialize krb5int_krcc_mutex if USE_KEYRING_CCACHE; destroy it in finalization. Define INITIAL_TYPEHEAD macro (for file vs keyring), and use it for initialization and in krb5int_cc_finalize. Re-enable freeing of additional registered-type structures. cc_keyring.c: Avoid calls to com_err from within library. cc_file.c: Punt change; generate_new is badly broken, and we expect to replace it with a new API anyways. git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@18638 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/aclocal.m4 b/src/aclocal.m4 index 51ca472f4..c4ce88aa5 100644 --- a/src/aclocal.m4 +++ b/src/aclocal.m4 @@ -106,6 +106,7 @@ KRB5_LIB_PARAMS KRB5_AC_INITFINI KRB5_AC_ENABLE_THREADS KRB5_AC_FIND_DLOPEN +KRB5_AC_KEYRING_CCACHE ])dnl dnl Maintainer mode, akin to what automake provides, 'cept we don't @@ -1811,3 +1812,14 @@ dnl AC_MSG_NOTICE(disabling ldap backend module support) fi AC_SUBST(OPENLDAP_PLUGIN) ])dnl +dnl +dnl If libkeyutils exists (on Linux) include it and use keyring ccache +AC_DEFUN(KRB5_AC_KEYRING_CCACHE,[ + AC_CHECK_HEADERS([keyutils.h], + AC_CHECK_LIB(keyutils, add_key, + [dnl Pre-reqs were found + AC_DEFINE(USE_KEYRING_CCACHE, 1, [Define if the keyring ccache should be enabled]) + LIBS="-lkeyutils $LIBS" + ])) +])dnl +dnl diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in index 44ba2a91b..7d611f839 100644 --- a/src/lib/krb5/ccache/Makefile.in +++ b/src/lib/krb5/ccache/Makefile.in @@ -26,7 +26,9 @@ STLIBOBJS= \ ccdefault.o \ ccdefops.o \ cc_retr.o \ - cc_file.o cc_memory.o \ + cc_file.o \ + cc_memory.o \ + cc_keyring.o \ ccfns.o \ ser_cc.o @@ -37,6 +39,7 @@ OBJS= $(OUTPRE)ccbase.$(OBJEXT) \ $(OUTPRE)cc_retr.$(OBJEXT) \ $(OUTPRE)cc_file.$(OBJEXT) \ $(OUTPRE)cc_memory.$(OBJEXT) \ + $(OUTPRE)cc_keyring.$(OBJEXT) \ $(OUTPRE)ccfns.$(OBJEXT) \ $(OUTPRE)ser_cc.$(OBJEXT) $(MSLSA_OBJ) @@ -47,6 +50,7 @@ SRCS= $(srcdir)/ccbase.c \ $(srcdir)/cc_retr.c \ $(srcdir)/cc_file.c \ $(srcdir)/cc_memory.c \ + $(srcdir)/cc_keyring.c \ $(srcdir)/ccfns.c \ $(srcdir)/ser_cc.c $(MSLSA_SRC) @@ -157,6 +161,14 @@ cc_memory.so cc_memory.po $(OUTPRE)cc_memory.$(OBJEXT): \ $(SRCTOP)/include/k5-thread.h $(SRCTOP)/include/krb5.h \ $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/port-sockets.h \ $(SRCTOP)/include/socket-utils.h cc_memory.c +cc_keyring.so cc_keyring.po $(OUTPRE)cc_keyring.$(OBJEXT): \ + $(BUILDTOP)/include/autoconf.h $(BUILDTOP)/include/krb5/krb5.h \ + $(BUILDTOP)/include/osconf.h $(BUILDTOP)/include/profile.h \ + $(COM_ERR_DEPS) $(SRCTOP)/include/k5-err.h $(SRCTOP)/include/k5-int.h \ + $(SRCTOP)/include/k5-platform.h $(SRCTOP)/include/k5-plugin.h \ + $(SRCTOP)/include/k5-thread.h $(SRCTOP)/include/krb5.h \ + $(SRCTOP)/include/krb5/locate_plugin.h $(SRCTOP)/include/port-sockets.h \ + $(SRCTOP)/include/socket-utils.h cc_keyring.c ccfns.so ccfns.po $(OUTPRE)ccfns.$(OBJEXT): $(BUILDTOP)/include/autoconf.h \ $(BUILDTOP)/include/krb5/krb5.h $(BUILDTOP)/include/osconf.h \ $(BUILDTOP)/include/profile.h $(COM_ERR_DEPS) $(SRCTOP)/include/k5-err.h \ diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h index b2d6c78c9..430e4c375 100644 --- a/src/lib/krb5/ccache/cc-int.h +++ b/src/lib/krb5/ccache/cc-int.h @@ -43,6 +43,7 @@ void krb5int_cc_finalize(void); extern k5_mutex_t krb5int_mcc_mutex; +extern k5_mutex_t krb5int_krcc_mutex; extern k5_mutex_t krb5int_cc_file_mutex; #endif /* __KRB5_CCACHE_H__ */ diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c new file mode 100644 index 000000000..8b36fe0cd --- /dev/null +++ b/src/lib/krb5/ccache/cc_keyring.c @@ -0,0 +1,2080 @@ +/* + * lib/krb5/ccache/cc_keyring.c + * + * Copyright (c) 2006 + * The Regents of the University of Michigan + * ALL RIGHTS RESERVED + * + * 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. + */ + +/* + * Implementation of a credentials cache stored in the Linux keyring facility + * + * Some assumptions: + * + * - A credentials cache "file" == a keyring with separate keys + * for the information in the ccache (see below) + * - A credentials cache keyring will contain only keys, + * not other keyrings + * - Each Kerberos ticket will have its own key within the ccache keyring + * - The principal information for the ccache is stored in a + * special key, which is not counted in the 'numkeys' count + */ + +#include "k5-int.h" + +#ifdef USE_KEYRING_CCACHE + +#include +#include + +#ifdef DEBUG +#define KRCC_DEBUG 1 +#endif + +#if KRCC_DEBUG +void debug_print(char *fmt, ...); /* prototype to silence warning */ +#include +#define DEBUG_PRINT(x) debug_print x +void +debug_print(char *fmt, ...) +{ + va_list ap; + va_start(ap, fmt); +#ifdef DEBUG_STDERR + vfprintf(stderr, fmt, ap); +#else + vsyslog(LOG_ERR, fmt, ap); +#endif + va_end(ap); +} +#else +#define DEBUG_PRINT(x) +#endif + +/* + * We always use "user" key type + */ +#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__" + +/* + * XXX The following two really belong in some external + * header since outside programs will need to use these + * same names. + */ +/* + * Special name for key to communicate key serial numbers + * This is used by the Linux gssd process to pass the + * user's keyring values it gets in an upcall. + * The format of the contents should be + * :: + */ +#define KRCC_SPEC_IDS_KEYNAME "_gssd_keyring_ids_" + +/* + * Special name for the key to communicate the name(s) + * of credentials caches to be used for requests. + * This should currently contain a single name, but + * in the future may contain a list that may be + * intelligently chosen from. + */ +#define KRCC_SPEC_CCACHE_SET_KEYNAME "__krb5_cc_set__" + +#define KRB5_OK 0 + +/* Hopefully big enough to hold a serialized credential */ +#define GUESS_CRED_SIZE 4096 + +#define ALLOC(NUM,TYPE) \ + (((NUM) <= (((size_t)0-1)/ sizeof(TYPE))) \ + ? (TYPE *) calloc((NUM), sizeof(TYPE)) \ + : (errno = ENOMEM,(TYPE *) 0)) + +#define CHECK_N_GO(ret, errdest) if (ret != KRB5_OK) goto errdest +#define CHECK(ret) if (ret != KRB5_OK) goto errout +#define CHECK_OUT(ret) if (ret != KRB5_OK) return ret + +typedef struct krb5_krcc_ring_ids { + key_serial_t session; + key_serial_t process; + key_serial_t thread; +} krb5_krcc_ring_ids_t; + +typedef struct _krb5_krcc_cursor +{ + int numkeys; + int currkey; + key_serial_t princ_id; + key_serial_t *keys; +} *krb5_krcc_cursor; + +/* + * This represents a credentials cache "file" + * where ring_id is the keyring serial number for + * this credentials cache "file". Each key + * in the keyring contains a separate key. + */ +typedef struct _krb5_krcc_data +{ + char *name; /* Name for this credentials cache */ + k5_mutex_t lock; /* synchronization */ + key_serial_t parent_id; /* parent keyring of this ccache keyring */ + key_serial_t ring_id; /* keyring representing ccache */ + key_serial_t princ_id; /* key holding principal info */ + int numkeys; /* # of keys in this ring + * (does NOT include principal info) */ +} krb5_krcc_data; + +/* Passed internally to assure we don't go past the bounds of our buffer */ +typedef struct _krb5_krcc_buffer_cursor +{ + char *bpp; + char *endp; +} krb5_krcc_bc; + +/* Global mutex */ +k5_mutex_t krb5int_krcc_mutex = K5_MUTEX_PARTIAL_INITIALIZER; + +/* + * Internal functions (exported via the krb5_krcc_ops) + */ + +extern const krb5_cc_ops krb5_krcc_ops; + +static const char *KRB5_CALLCONV krb5_krcc_get_name + (krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_resolve + (krb5_context, krb5_ccache * id, const char *residual); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_generate_new + (krb5_context, krb5_ccache * id); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_initialize + (krb5_context, krb5_ccache id, krb5_principal princ); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_destroy + (krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_close + (krb5_context, krb5_ccache id); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_store + (krb5_context, krb5_ccache id, krb5_creds * creds); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_retrieve + (krb5_context, krb5_ccache id, krb5_flags whichfields, + krb5_creds * mcreds, krb5_creds * creds); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_get_principal + (krb5_context, krb5_ccache id, krb5_principal * princ); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_start_seq_get + (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_next_cred + (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor, + krb5_creds * creds); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_end_seq_get + (krb5_context, krb5_ccache id, krb5_cc_cursor * cursor); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_remove_cred + (krb5_context context, krb5_ccache cache, krb5_flags flags, + krb5_creds * creds); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_set_flags + (krb5_context, krb5_ccache id, krb5_flags flags); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_get_flags + (krb5_context context, krb5_ccache id, krb5_flags * flags); + +/* + * Internal utility functions + */ + +static krb5_error_code krb5_krcc_clearcache + (krb5_context context, krb5_ccache id); + +static krb5_error_code krb5_krcc_new_data + (const char *, key_serial_t ring, key_serial_t parent_ring, + krb5_krcc_data **); + +static krb5_error_code krb5_krcc_save_principal + (krb5_context context, krb5_ccache id, krb5_principal princ); + +static krb5_error_code krb5_krcc_retrieve_principal + (krb5_context context, krb5_ccache id, krb5_principal * princ); + +static int krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p); + +/* Routines to parse a key from a keyring into a cred structure */ +static krb5_error_code krb5_krcc_parse + (krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_cred + (krb5_context context, krb5_ccache id, krb5_creds * creds, + char *payload, int psize); +static krb5_error_code krb5_krcc_parse_principal + (krb5_context context, krb5_ccache id, krb5_principal * princ, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_keyblock + (krb5_context context, krb5_ccache id, krb5_keyblock * keyblock, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_times + (krb5_context context, krb5_ccache id, krb5_ticket_times * t, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_krb5data + (krb5_context context, krb5_ccache id, krb5_data * data, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_int32 + (krb5_context context, krb5_ccache id, krb5_int32 * i, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_octet + (krb5_context context, krb5_ccache id, krb5_octet * octet, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_addrs + (krb5_context context, krb5_ccache id, krb5_address *** a, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_addr + (krb5_context context, krb5_ccache id, krb5_address * a, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_authdata + (krb5_context context, krb5_ccache id, krb5_authdata *** ad, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_authdatum + (krb5_context context, krb5_ccache id, krb5_authdata * ad, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_parse_ui_2 + (krb5_context, krb5_ccache id, krb5_ui_2 * i, krb5_krcc_bc * bc); + +/* Routines to unparse a cred structure into keyring key */ +static krb5_error_code krb5_krcc_unparse + (krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_cred + (krb5_context context, krb5_ccache id, krb5_creds * creds, + char **datapp, unsigned int *lenptr); +static krb5_error_code krb5_krcc_unparse_principal + (krb5_context, krb5_ccache id, krb5_principal princ, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_keyblock + (krb5_context, krb5_ccache id, krb5_keyblock * keyblock, + krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_times + (krb5_context, krb5_ccache id, krb5_ticket_times * t, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_krb5data + (krb5_context, krb5_ccache id, krb5_data * data, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_int32 + (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_octet + (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_addrs + (krb5_context, krb5_ccache, krb5_address ** a, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_addr + (krb5_context, krb5_ccache, krb5_address * a, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_authdata + (krb5_context, krb5_ccache, krb5_authdata ** ad, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_authdatum + (krb5_context, krb5_ccache, krb5_authdata * ad, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_ui_4 + (krb5_context, krb5_ccache id, krb5_ui_4 i, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_ui_2 + (krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); + +/* Note the following is a stub function for Linux */ +extern krb5_error_code krb5_change_cache(void); + +/* + * Modifies: + * id + * + * Effects: + * Creates/refreshes the cred cache id. If the cache exists, its + * contents are destroyed. + * + * Errors: + * system errors + * permission errors + */ + +static krb5_error_code KRB5_CALLCONV +krb5_krcc_initialize(krb5_context context, krb5_ccache id, + krb5_principal princ) +{ + krb5_error_code kret; + + DEBUG_PRINT(("krb5_krcc_initialize: entered\n")); + + kret = k5_mutex_lock(&((krb5_krcc_data *) id->data)->lock); + if (kret) + return kret; + + kret = krb5_krcc_clearcache(context, id); + if (kret != KRB5_OK) + goto out; + + kret = krb5_krcc_save_principal(context, id, princ); + if (kret == KRB5_OK) + krb5_change_cache(); + +out: + k5_mutex_unlock(&((krb5_krcc_data *) id->data)->lock); + return kret; +} + +/* + * Modifies: + * id + * + * Effects: + * Invalidates the id, and frees any resources associated with the cache. + * (Does NOT destroy the underlying ccache in the keyring.) + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_close(krb5_context context, krb5_ccache id) +{ + krb5_krcc_data *d; + + DEBUG_PRINT(("krb5_krcc_close: entered\n")); + + d = (krb5_krcc_data *) id->data; + + krb5_xfree(d->name); + k5_mutex_destroy(&d->lock); + krb5_xfree(d); + + krb5_xfree(id); + + return KRB5_OK; +} + +/* + * Modifies: + * id + * + * Effects: + * Clears out a ccache keyring, unlinking all keys within it + * (Including the "hidden" key with the principal information) + * + * Requires: + * Must be called with mutex locked. + * + * Errors: + * system errors + */ + +static krb5_error_code +krb5_krcc_clearcache(krb5_context context, krb5_ccache id) +{ + krb5_krcc_data *d; + key_serial_t *keys; + unsigned int size; + int res; + int i; + + k5_assert_locked(&((krb5_krcc_data *) id->data)->lock); + + d = (krb5_krcc_data *) id->data; + + DEBUG_PRINT(("krb5_krcc_clearcache: ring_id %d, princ_id %d, " + "numkeys is %d\n", d->ring_id, d->princ_id, d->numkeys)); + if (d->numkeys > 0) { + + size = (d->numkeys + 1) * sizeof(key_serial_t); /* +1 for princ key */ + keys = malloc(size); + reread: + if (!keys) + return KRB5_CC_NOMEM; + + res = keyctl_read(d->ring_id, (char *) keys, size); + if (res < 0) { + free(keys); + return errno; + } + if (res != size) { /* are there more keys than we thought? */ + DEBUG_PRINT(("krb5_krcc_clearcache: numkeys %d, res %d, " + "size %d reallocing key array\n", + d->numkeys, res, size)); + size = res; + keys = realloc(keys, (unsigned int) res); + goto reread; + } + + for (i = 0; i < (size / sizeof(key_serial_t)); i++) { + res = keyctl_unlink(keys[i], d->ring_id); + if (res < 0) { + DEBUG_PRINT(("krb5_krcc_clearcache: error unlinking " + "key %d, res == %d\n", keys[i], res)); + } + } + free(keys); + d->numkeys = 0; + d->princ_id = 0; + } + return KRB5_OK; +} + +/* + * Effects: + * Destroys the contents of id. + * + * Errors: + * system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_destroy(krb5_context context, krb5_ccache id) +{ + krb5_error_code kret; + krb5_krcc_data *d; + int res; + + DEBUG_PRINT(("krb5_krcc_destroy: entered\n")); + + d = (krb5_krcc_data *) id->data; + + kret = k5_mutex_lock(&d->lock); + if (kret) + return kret; + + krb5_krcc_clearcache(context, id); + krb5_xfree(d->name); + res = keyctl_unlink(d->ring_id, d->parent_id); + if (res < 0) { + kret = errno; + DEBUG_PRINT(("krb5_krcc_destroy: unlinking key %d from ring %d: %s", + d->ring_id, d->parent_id, error_message(errno))); + goto cleanup; + } +cleanup: + k5_mutex_unlock(&d->lock); + k5_mutex_destroy(&d->lock); + krb5_xfree(d); + krb5_xfree(id); + + krb5_change_cache(); + + return KRB5_OK; +} + + +/* + * Requires: + * residual is a legal path name, and a null-terminated string + * + * Modifies: + * id + * + * Effects: + * creates a cred cache that will reside in the keyring with + * a name of . + * + * Returns: + * A filled in krb5_ccache structure "id". + * + * Errors: + * KRB5_CC_NOMEM - there was insufficient memory to allocate the + * krb5_ccache. id is undefined. + * permission errors + */ + +static krb5_error_code KRB5_CALLCONV +krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_residual) +{ + krb5_ccache lid; + krb5_error_code kret; + krb5_krcc_data *d; + key_serial_t key; + key_serial_t pkey = 0; + int nkeys = 0; + int res; + krb5_krcc_ring_ids_t ids; + key_serial_t ring_id; + const char *residual; + + DEBUG_PRINT(("krb5_krcc_resolve: entered with name '%s'\n", + full_residual)); + + res = krb5_krcc_get_ring_ids(&ids); + if (res) { + kret = EINVAL; + DEBUG_PRINT(("krb5_krcc_resolve: Error getting ring id values!\n")); + return kret; + } + + if (strncmp(full_residual, "thread:", 7) == 0) { + residual = full_residual + 7; + ring_id = ids.thread; + } else if (strncmp(full_residual, "process:", 8) == 0) { + residual = full_residual + 8; + ring_id = ids.process; + } else { + residual = full_residual; + ring_id = ids.session; + } + + DEBUG_PRINT(("krb5_krcc_resolve: searching ring %d for residual '%s'\n", + ring_id, residual)); + + /* + * Use keyctl_search instead of request_key. If we're supposed + * to be looking for a process ccache, we shouldn't find a + * thread ccache. + * XXX But should we look in the session ring if we don't find it + * in the process ring? Same goes for thread. Should we look in + * the process and session rings if not found in the thread ring? + * + */ + key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, residual, 0); + if (key < 0) { + key = add_key(KRCC_KEY_TYPE_KEYRING, residual, NULL, 0, ring_id); + if (key < 0) { + kret = errno; + DEBUG_PRINT(("krb5_krcc_resolve: Error adding new " + "keyring '%s': %s\n", residual, strerror(errno))); + return kret; + } + DEBUG_PRINT(("krb5_krcc_resolve: new keyring '%s', " + "key %d, added to keyring %d\n", + residual, key, ring_id)); + } else { + DEBUG_PRINT(("krb5_krcc_resolve: found existing " + "key %d, with name '%s' in keyring %d\n", + key, residual, ring_id)); + /* Determine key containing principal information */ + pkey = keyctl_search(key, KRCC_KEY_TYPE_USER, + KRCC_SPEC_PRINC_KEYNAME, 0); + if (pkey < 0) { + DEBUG_PRINT(("krb5_krcc_resolve: Error locating principal " + "info for existing ccache in ring %d: %s\n", + key, strerror(errno))); + pkey = 0; + } + /* Determine how many keys exist */ + res = keyctl_read(key, NULL, 0); + if (res > 0) + nkeys = (res / sizeof(key_serial_t)) - 1; + else + nkeys = 0; + } + + lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) + return KRB5_CC_NOMEM; + + + kret = krb5_krcc_new_data(residual, key, ring_id, &d); + if (kret) { + free(lid); + return kret; + } + + DEBUG_PRINT(("krb5_krcc_resolve: ring_id %d, princ_id %d, " + "nkeys %d\n", key, pkey, nkeys)); + d->princ_id = pkey; + d->numkeys = nkeys; + lid->ops = &krb5_krcc_ops; + lid->data = d; + lid->magic = KV5M_CCACHE; + *id = lid; + return KRB5_OK; +} + +/* + * Effects: + * Prepares for a sequential search of the credentials cache. + * Returns a krb5_cc_cursor to be used with krb5_krcc_next_cred and + * krb5_krcc_end_seq_get. + * + * If the cache is modified between the time of this call and the time + * of the final krb5_krcc_end_seq_get, the results are undefined. + * + * Errors: + * KRB5_CC_NOMEM + * system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id, + krb5_cc_cursor * cursor) +{ + krb5_krcc_cursor krcursor; + krb5_error_code kret; + krb5_krcc_data *d; + unsigned int size; + int res; + + DEBUG_PRINT(("krb5_krcc_start_seq_get: entered\n")); + + d = id->data; + kret = k5_mutex_lock(&d->lock); + if (kret) + return kret; + + size = sizeof(*krcursor) + ((d->numkeys + 1) * sizeof(key_serial_t)); + + krcursor = (krb5_krcc_cursor) malloc(size); + if (krcursor == NULL) { + k5_mutex_unlock(&d->lock); + return KRB5_CC_NOMEM; + } + + krcursor->keys = (key_serial_t *) ((char *) krcursor + sizeof(*krcursor)); + res = keyctl_read(d->ring_id, (char *) krcursor->keys, + ((d->numkeys + 1) * sizeof(key_serial_t))); + if (res < 0 || res > ((d->numkeys + 1) * sizeof(key_serial_t))) { + DEBUG_PRINT(("Read %d bytes from keyring, numkeys %d: %s\n", + res, d->numkeys, strerror(errno))); + free(krcursor); + k5_mutex_unlock(&d->lock); + return KRB5_CC_IO; + } + + krcursor->numkeys = d->numkeys; + krcursor->currkey = 0; + krcursor->princ_id = d->princ_id; + + k5_mutex_unlock(&d->lock); + *cursor = (krb5_cc_cursor) krcursor; + return KRB5_OK; +} + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_krcc_start_seq_get. + * + * Modifes: + * cursor, creds + * + * Effects: + * Fills in creds with the "next" credentals structure from the cache + * id. The actual order the creds are returned in is arbitrary. + * Space is allocated for the variable length fields in the + * credentials structure, so the object returned must be passed to + * krb5_destroy_credential. + * + * The cursor is updated for the next call to krb5_krcc_next_cred. + * + * Errors: + * system errors + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_next_cred(krb5_context context, krb5_ccache id, + krb5_cc_cursor * cursor, krb5_creds * creds) +{ + krb5_krcc_cursor krcursor; + krb5_error_code kret; + int psize; + void *payload; + + DEBUG_PRINT(("krb5_krcc_next_cred: entered\n")); + + /* + * Once the node in the linked list is created, it's never + * modified, so we don't need to worry about locking here. + * (Note that we don't support _remove_cred.) + */ + krcursor = (krb5_krcc_cursor) * cursor; + if (krcursor == NULL) + return KRB5_CC_END; + memset(creds, 0, sizeof(krb5_creds)); + + /* If we're pointing at the entry with the principal, skip it */ + if (krcursor->keys[krcursor->currkey] == krcursor->princ_id) + krcursor->currkey++; + + /* If we're pointing past the end of the keys array, there are no more */ + if (krcursor->currkey > krcursor->numkeys) + return KRB5_CC_END; + + /* Read the key, the right size buffer will ba allocated and returned */ + psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload); + if (psize == -1) { + DEBUG_PRINT(("Error reading key %d: %s\n", + krcursor->keys[krcursor->currkey], + strerror(errno))); + kret = KRB5_CC_IO; + goto freepayload; + } + krcursor->currkey++; + + kret = krb5_krcc_parse_cred(context, id, creds, payload, psize); + + freepayload: + free(payload); + return kret; +} + +/* + * Requires: + * cursor is a krb5_cc_cursor originally obtained from + * krb5_krcc_start_seq_get. + * + * Modifies: + * id, cursor + * + * Effects: + * Finishes sequential processing of the keyring credentials ccache id, + * and invalidates the cursor (it must never be used after this call). + */ +/* ARGSUSED */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_end_seq_get(krb5_context context, krb5_ccache id, + krb5_cc_cursor * cursor) +{ + DEBUG_PRINT(("krb5_krcc_end_seq_get: entered\n")); + + free(*cursor); + *cursor = 0L; + return KRB5_OK; +} + +/* Utility routine: Creates the back-end data for a keyring cache. + + Call with the global list lock held. */ +static krb5_error_code +krb5_krcc_new_data(const char *name, key_serial_t ring, + key_serial_t parent_ring, krb5_krcc_data ** datapp) +{ + krb5_error_code kret; + krb5_krcc_data *d; + + d = malloc(sizeof(krb5_krcc_data)); + if (d == NULL) + return KRB5_CC_NOMEM; + + kret = k5_mutex_init(&d->lock); + if (kret) { + krb5_xfree(d); + return kret; + } + + d->name = strdup(name); + if (d->name == NULL) { + k5_mutex_destroy(&d->lock); + krb5_xfree(d); + return KRB5_CC_NOMEM; + } + d->princ_id = 0; + d->ring_id = ring; + d->parent_id = parent_ring; + d->numkeys = 0; + + *datapp = d; + return 0; +} + +/* Utility routine: Creates a random memory ccache name. + * This algorithm was selected because it creates readable + * random ccache names in a fixed size buffer. */ + +static krb5_error_code +random_string (krb5_context context, char *string, krb5_int32 length) +{ + static const unsigned char charlist[] = + "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; + krb5_error_code err = 0; + unsigned char *bytes = NULL; + size_t bytecount = length - 1; + + if (!err) { + bytes = malloc (bytecount); + if (bytes == NULL) { err = ENOMEM; } + } + + if (!err) { + krb5_data data; + data.length = bytecount; + data.data = (char *) bytes; + err = krb5_c_random_make_octets (context, &data); + } + + if (!err) { + krb5_int32 i; + for (i = 0; i < bytecount; i++) { + string [i] = charlist[bytes[i] % (sizeof (charlist) - 1)]; + } + string[length - 1] = '\0'; + } + + if (bytes != NULL) { free (bytes); } + + return err; +} + +/* + * Effects: + * Creates a new keyring cred cache whose name is guaranteed to be + * unique. + * + * Returns: + * The filled in krb5_ccache id. + * + * Errors: + * KRB5_CC_NOMEM - there was insufficient memory to allocate the + * krb5_ccache. id is undefined. + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_generate_new(krb5_context context, krb5_ccache * id) +{ + krb5_ccache lid; + char uniquename[8]; + krb5_error_code kret; + krb5_krcc_data *d; + key_serial_t ring_id = KEY_SPEC_SESSION_KEYRING; + key_serial_t key; + + DEBUG_PRINT(("krb5_krcc_generate_new: entered\n")); + + /* Allocate memory */ + lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); + if (lid == NULL) + return KRB5_CC_NOMEM; + + lid->ops = &krb5_krcc_ops; + + kret = k5_mutex_lock(&krb5int_krcc_mutex); + if (kret) { + free(lid); + return kret; + } + +/* XXX These values are platform-specific and should not be here! */ +/* XXX There is a bug in FC5 where these are not included in errno.h */ +#ifndef ENOKEY +#define ENOKEY 126 /* Required key not available */ +#endif +#ifndef EKEYEXPIRED +#define EKEYEXPIRED 127 /* Key has expired */ +#endif +#ifndef EKEYREVOKED +#define EKEYREVOKED 128 /* Key has been revoked */ +#endif +#ifndef EKEYREJECTED +#define EKEYREJECTED 129 /* Key was rejected by service */ +#endif + + /* + * Loop until we successfully create a new ccache keyring with + * a unique name, or we get an error. + */ + while (1) { + random_string(context, uniquename, sizeof(uniquename)); + DEBUG_PRINT(("krb5_krcc_generate_new: searching for name '%s'\n", + uniquename)); + key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0); +/*XXX*/ DEBUG_PRINT(("krb5_krcc_generate_new: after searching for '%s', key = %d, errno = %d\n", uniquename, key, errno)); + if (key < 0 && errno == ENOKEY) { + /* name does not already exist, create it to reserve the name */ + key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, ring_id); + if (key < 0) { + kret = errno; + DEBUG_PRINT(("krb5_krcc_generate_new: '%s' trying to " + "create '%s'\n", strerror(errno), uniquename)); + k5_mutex_unlock(&krb5int_krcc_mutex); + return kret; + } + break; + } + } + + kret = krb5_krcc_new_data(uniquename, key, ring_id, &d); + k5_mutex_unlock(&krb5int_krcc_mutex); + if (kret) { + krb5_xfree(lid); + return kret; + } + lid->data = d; + *id = lid; + krb5_change_cache(); + return KRB5_OK; +} + +/* + * Requires: + * id is a keyring credential cache + * + * Returns: + * The name of the keyring cred cache id. + */ +static const char *KRB5_CALLCONV +krb5_krcc_get_name(krb5_context context, krb5_ccache id) +{ + DEBUG_PRINT(("krb5_krcc_get_name: entered\n")); + return (char *) ((krb5_krcc_data *) id->data)->name; +} + +/* + * Modifies: + * id, princ + * + * Effects: + * Retrieves the primary principal from id, as set with + * krb5_krcc_initialize. The principal is returned is allocated + * storage that must be freed by the caller via krb5_free_principal. + * + * Errors: + * system errors + * KRB5_CC_NOMEM + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_get_principal(krb5_context context, krb5_ccache id, + krb5_principal * princ) +{ + DEBUG_PRINT(("krb5_krcc_get_principal: entered\n")); + + return krb5_krcc_retrieve_principal(context, id, princ); +} + +static krb5_error_code KRB5_CALLCONV +krb5_krcc_retrieve(krb5_context context, krb5_ccache id, + krb5_flags whichfields, krb5_creds * mcreds, + krb5_creds * creds) +{ + DEBUG_PRINT(("krb5_krcc_retrieve: entered\n")); + + return krb5_cc_retrieve_cred_default(context, id, whichfields, + mcreds, creds); +} + +/* + * Non-functional stub implementation for krb5_krcc_remove + * + * Errors: + * KRB5_CC_NOSUPP - not implemented + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_remove_cred(krb5_context context, krb5_ccache cache, + krb5_flags flags, krb5_creds * creds) +{ + DEBUG_PRINT(("krb5_krcc_remove_cred: entered (returning KRB5_CC_NOSUPP)\n")); + + return KRB5_CC_NOSUPP; +} + +/* + * Requires: + * id is a cred cache returned by krb5_krcc_resolve or + * krb5_krcc_generate_new, but has not been opened by krb5_krcc_initialize. + * + * Modifies: + * id + * + * Effects: + * Sets the operational flags of id to flags. + */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags) +{ + DEBUG_PRINT(("krb5_krcc_set_flags: entered\n")); + + return KRB5_OK; +} + +static krb5_error_code KRB5_CALLCONV +krb5_krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags * flags) +{ + DEBUG_PRINT(("krb5_krcc_get_flags: entered\n")); + + *flags = 0; + return KRB5_OK; +} + +/* store: Save away creds in the ccache keyring. */ +static krb5_error_code KRB5_CALLCONV +krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds) +{ + krb5_error_code kret; + krb5_krcc_data *d = (krb5_krcc_data *) id->data; + char *payload = NULL; + unsigned int payloadlen; + key_serial_t newkey; + char *keyname = NULL; + + DEBUG_PRINT(("krb5_krcc_store: entered\n")); + + kret = k5_mutex_lock(&d->lock); + if (kret) + return kret; + + /* Get the service principal name and use it as the key name */ + kret = krb5_unparse_name(context, creds->server, &keyname); + if (kret) { + DEBUG_PRINT(("Error unparsing service principal name!\n")); + goto errout; + } + + /* Serialize credential into memory */ + kret = krb5_krcc_unparse_cred(context, id, creds, &payload, &payloadlen); + if (kret != KRB5_OK) + goto errout; + + /* Add new key (credentials) into keyring */ + DEBUG_PRINT(("krb5_krcc_store: adding new key '%s' to keyring %d\n", + keyname, d->ring_id)); + newkey = add_key(KRCC_KEY_TYPE_USER, keyname, payload, + payloadlen, d->ring_id); + if (newkey < 0) { + kret = errno; + DEBUG_PRINT(("Error adding user key '%s': %s\n", + keyname, strerror(kret))); + } else { + d->numkeys++; + kret = KRB5_OK; + } + + errout: + if (keyname) + krb5_free_unparsed_name(context, keyname); + if (payload) + free(payload); + + k5_mutex_unlock(&d->lock); + return kret; +} + +static krb5_error_code +krb5_krcc_save_principal(krb5_context context, krb5_ccache id, + krb5_principal princ) +{ + krb5_krcc_data *d; + krb5_error_code kret; + char *payload; + key_serial_t newkey; + unsigned int payloadsize; + krb5_krcc_bc bc; + + k5_assert_locked(&((krb5_krcc_data *) id->data)->lock); + + d = (krb5_krcc_data *) id->data; + + payload = malloc(GUESS_CRED_SIZE); + if (payload == NULL) + return KRB5_CC_NOMEM; + + bc.bpp = payload; + bc.endp = payload + GUESS_CRED_SIZE; + + kret = krb5_krcc_unparse_principal(context, id, princ, &bc); + CHECK_N_GO(kret, errout); + + /* Add new key into keyring */ + payloadsize = bc.bpp - payload; +#ifdef KRCC_DEBUG + { + krb5_error_code rc; + char *princname = NULL; + rc = krb5_unparse_name(context, princ, &princname); + DEBUG_PRINT(("krb5_krcc_save_principal: adding new key '%s' " + "to keyring %d for principal '%s'\n", + KRCC_SPEC_PRINC_KEYNAME, d->ring_id, + rc ? "" : princname)); + if (rc == 0) + krb5_free_unparsed_name(context, princname); + } +#endif + newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, payload, + payloadsize, d->ring_id); + if (newkey < 0) { + kret = errno; + DEBUG_PRINT(("Error adding principal key: %s\n", strerror(kret))); + } else { + d->princ_id = newkey; + kret = KRB5_OK; + } + + errout: + free(payload); + return kret; +} + +static krb5_error_code +krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id, + krb5_principal * princ) +{ + krb5_krcc_data *d = (krb5_krcc_data *) id->data; + krb5_error_code kret; + void *payload = NULL; + int psize; + krb5_krcc_bc bc; + + kret = k5_mutex_lock(&d->lock); + if (kret) + return kret; + + if (!d->princ_id) { + princ = 0L; + kret = KRB5_FCC_NOFILE; + goto errout; + } + + psize = keyctl_read_alloc(d->princ_id, &payload); + if (psize == -1) { + DEBUG_PRINT(("Reading principal key %d: %s\n", + d->princ_id, strerror(errno))); + kret = KRB5_CC_IO; + goto errout; + } + bc.bpp = payload; + bc.endp = (char *)payload + psize; + kret = krb5_krcc_parse_principal(context, id, princ, &bc); + + errout: + if (payload) + free(payload); + k5_mutex_unlock(&d->lock); + return kret; +} + +static int +krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p) +{ + key_serial_t ids_key; + char ids_buf[128]; + key_serial_t session, process, thread; + long val; + + DEBUG_PRINT(("krb5_krcc_get_ring_ids: entered\n")); + + if (!p) + return EINVAL; + + /* Use the defaults in case we find no ids key */ + p->session = KEY_SPEC_SESSION_KEYRING; + p->process = KEY_SPEC_PROCESS_KEYRING; + p->thread = KEY_SPEC_THREAD_KEYRING; + + /* + * Note that in the "normal" case, this will not be found. + * The Linux gssd creates this key while creating a + * context to communicate the user's key serial numbers. + */ + ids_key = request_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_IDS_KEYNAME, NULL, 0); + if (ids_key < 0) + goto out; + + DEBUG_PRINT(("krb5_krcc_get_ring_ids: processing '%s' key %d\n", + KRCC_SPEC_IDS_KEYNAME, ids_key)); + /* + * Read and parse the ids file + */ + memset(ids_buf, '\0', sizeof(ids_buf)); + val = keyctl_read(ids_key, ids_buf, sizeof(ids_buf)); + if (val > sizeof(ids_buf)) + goto out; + + val = sscanf(ids_buf, "%d:%d:%d", &session, &process, &thread); + if (val != 3) + goto out; + + p->session = session; + p->process = process; + p->thread = thread; + + out: + DEBUG_PRINT(("krb5_krcc_get_ring_ids: returning %d:%d:%d\n", + p->session, p->process, p->thread)); + return 0; +} + +/* + * =============================================================== + * INTERNAL functions to parse a credential from a key payload + * (Borrowed heavily from krb5_fcc_{read|store}_ funtions.) + * =============================================================== + */ + +/* + * Effects: + * Copies len bytes from the key payload buffer into buf. + * Updates payload pointer and returns KRB5_CC_END if we + * try to read past the end of the payload buffer's data. + * + * Requires: + * Must be called with mutex locked. + * + * Errors: + * KRB5_CC_END - there were not len bytes available + */ +static krb5_error_code +krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf, + unsigned int len, krb5_krcc_bc * bc) +{ + DEBUG_PRINT(("krb5_krcc_parse: entered\n")); + + if ((bc->endp == bc->bpp) || (bc->endp - bc->bpp) < len) + return KRB5_CC_END; + + memcpy(buf, bc->bpp, len); + bc->bpp += len; + + return KRB5_OK; +} + +/* + * Take a key (credential) read from a keyring entry + * and parse it into a credential structure. + */ +static krb5_error_code +krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds, + char *payload, int psize) +{ + krb5_error_code kret; + krb5_octet octet; + krb5_int32 int32; + krb5_krcc_bc bc; + + /* Parse the pieces of the credential */ + bc.bpp = payload; + bc.endp = bc.bpp + psize; + kret = krb5_krcc_parse_principal(context, id, &creds->client, &bc); + CHECK_N_GO(kret, out); + + kret = krb5_krcc_parse_principal(context, id, &creds->server, &bc); + CHECK_N_GO(kret, cleanclient); + + kret = krb5_krcc_parse_keyblock(context, id, &creds->keyblock, &bc); + CHECK_N_GO(kret, cleanserver); + + kret = krb5_krcc_parse_times(context, id, &creds->times, &bc); + CHECK_N_GO(kret, cleanserver); + + kret = krb5_krcc_parse_octet(context, id, &octet, &bc); + CHECK_N_GO(kret, cleanserver); + creds->is_skey = octet; + + kret = krb5_krcc_parse_int32(context, id, &int32, &bc); + CHECK_N_GO(kret, cleanserver); + creds->ticket_flags = int32; + + kret = krb5_krcc_parse_addrs(context, id, &creds->addresses, &bc); + CHECK_N_GO(kret, cleanblock); + + kret = krb5_krcc_parse_authdata(context, id, &creds->authdata, &bc); + CHECK_N_GO(kret, cleanaddrs); + + kret = krb5_krcc_parse_krb5data(context, id, &creds->ticket, &bc); + CHECK_N_GO(kret, cleanauthdata); + + kret = krb5_krcc_parse_krb5data(context, id, &creds->second_ticket, &bc); + CHECK_N_GO(kret, cleanticket); + + kret = KRB5_OK; + goto out; + + cleanticket: + memset(creds->ticket.data, 0, (unsigned) creds->ticket.length); + krb5_xfree(creds->ticket.data); + cleanauthdata: + /* XXX ??? */ + cleanaddrs: + krb5_free_addresses(context, creds->addresses); + cleanblock: + krb5_xfree(creds->keyblock.contents); + cleanserver: + krb5_free_principal(context, creds->server); + cleanclient: + krb5_free_principal(context, creds->client); + + out: + return kret; +} + +static krb5_error_code +krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, + krb5_principal * princ, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + register krb5_principal tmpprinc; + krb5_int32 length, type; + int i; + + /* Read principal type */ + kret = krb5_krcc_parse_int32(context, id, &type, bc); + if (kret != KRB5_OK) + return kret; + + /* Read the number of components */ + kret = krb5_krcc_parse_int32(context, id, &length, bc); + if (kret != KRB5_OK) + return kret; + + if (length < 0) + return KRB5_CC_NOMEM; + + tmpprinc = (krb5_principal) malloc(sizeof(krb5_principal_data)); + if (tmpprinc == NULL) + return KRB5_CC_NOMEM; + if (length) { + size_t msize = length; + if (msize != length) { + free(tmpprinc); + return KRB5_CC_NOMEM; + } + tmpprinc->data = ALLOC(msize, krb5_data); + if (tmpprinc->data == 0) { + free((char *) tmpprinc); + return KRB5_CC_NOMEM; + } + } else + tmpprinc->data = 0; + tmpprinc->magic = KV5M_PRINCIPAL; + tmpprinc->length = length; + tmpprinc->type = type; + + kret = krb5_krcc_parse_krb5data(context, id, + krb5_princ_realm(context, tmpprinc), bc); + i = 0; + CHECK(kret); + + for (i = 0; i < length; i++) { + kret = krb5_krcc_parse_krb5data(context, id, + krb5_princ_component(context, tmpprinc, + i), bc); + CHECK(kret); + } + *princ = tmpprinc; + return KRB5_OK; + + errout: + while (--i >= 0) + free(krb5_princ_component(context, tmpprinc, i)->data); + free((char *) tmpprinc->data); + free((char *) tmpprinc); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id, + krb5_keyblock * keyblock, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_ui_2 ui2; + krb5_int32 int32; + + keyblock->magic = KV5M_KEYBLOCK; + keyblock->contents = 0; + + kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + CHECK(kret); + keyblock->enctype = ui2; + + kret = krb5_krcc_parse_int32(context, id, &int32, bc); + CHECK(kret); + if (int32 < 0) + return KRB5_CC_NOMEM; + keyblock->length = int32; + /* Overflow check. */ + if (keyblock->length != int32) + return KRB5_CC_NOMEM; + if (keyblock->length == 0) + return KRB5_OK; + keyblock->contents = ALLOC(keyblock->length, krb5_octet); + if (keyblock->contents == NULL) + return KRB5_CC_NOMEM; + + kret = krb5_krcc_parse(context, id, keyblock->contents, + keyblock->length, bc); + CHECK(kret); + + return KRB5_OK; + errout: + if (keyblock->contents) + krb5_xfree(keyblock->contents); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_times(krb5_context context, krb5_ccache id, + krb5_ticket_times * t, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 i; + + kret = krb5_krcc_parse_int32(context, id, &i, bc); + CHECK(kret); + t->authtime = i; + + kret = krb5_krcc_parse_int32(context, id, &i, bc); + CHECK(kret); + t->starttime = i; + + kret = krb5_krcc_parse_int32(context, id, &i, bc); + CHECK(kret); + t->endtime = i; + + kret = krb5_krcc_parse_int32(context, id, &i, bc); + CHECK(kret); + t->renew_till = i; + + return 0; + errout: + return kret; +} + +static krb5_error_code +krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id, + krb5_data * data, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 len; + + data->magic = KV5M_DATA; + data->data = 0; + + kret = krb5_krcc_parse_int32(context, id, &len, bc); + CHECK(kret); + if (len < 0) + return KRB5_CC_NOMEM; + data->length = len; + if (data->length != len || data->length + 1 == 0) + return KRB5_CC_NOMEM; + + if (data->length == 0) { + data->data = 0; + return KRB5_OK; + } + + data->data = (char *) malloc(data->length + 1); + if (data->data == NULL) + return KRB5_CC_NOMEM; + + kret = krb5_krcc_parse(context, id, data->data, (unsigned) data->length, + bc); + CHECK(kret); + + data->data[data->length] = 0; /* Null terminate, just in case.... */ + return KRB5_OK; + errout: + if (data->data) + krb5_xfree(data->data); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i, + krb5_krcc_bc * bc) +{ + krb5_error_code kret; + unsigned char buf[4]; + krb5_int32 val; + + kret = krb5_krcc_parse(context, id, buf, 4, bc); + if (kret) + return kret; + val = buf[0]; + val = (val << 8) | buf[1]; + val = (val << 8) | buf[2]; + val = (val << 8) | buf[3]; + *i = val; + return 0; +} + +static krb5_error_code +krb5_krcc_parse_octet(krb5_context context, krb5_ccache id, krb5_octet * i, + krb5_krcc_bc * bc) +{ + return krb5_krcc_parse(context, id, (krb5_pointer) i, 1, bc); +} + +static krb5_error_code +krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id, + krb5_address *** addrs, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 length; + size_t msize; + int i; + + *addrs = 0; + + /* Read the number of components */ + kret = krb5_krcc_parse_int32(context, id, &length, bc); + CHECK(kret); + + /* + * Make *addrs able to hold length pointers to krb5_address structs + * Add one extra for a null-terminated list + */ + msize = length; + msize += 1; + if (msize == 0 || msize - 1 != length || length < 0) + return KRB5_CC_NOMEM; + *addrs = ALLOC(msize, krb5_address *); + if (*addrs == NULL) + return KRB5_CC_NOMEM; + + for (i = 0; i < length; i++) { + (*addrs)[i] = (krb5_address *) malloc(sizeof(krb5_address)); + if ((*addrs)[i] == NULL) { + krb5_free_addresses(context, *addrs); + return KRB5_CC_NOMEM; + } + kret = krb5_krcc_parse_addr(context, id, (*addrs)[i], bc); + CHECK(kret); + } + + return KRB5_OK; + errout: + if (*addrs) + krb5_free_addresses(context, *addrs); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr, + krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_ui_2 ui2; + krb5_int32 int32; + + addr->magic = KV5M_ADDRESS; + addr->contents = 0; + + kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + CHECK(kret); + addr->addrtype = ui2; + + kret = krb5_krcc_parse_int32(context, id, &int32, bc); + CHECK(kret); + if ((int32 & VALID_INT_BITS) != int32) /* Overflow int??? */ + return KRB5_CC_NOMEM; + addr->length = int32; + /* + * Length field is "unsigned int", which may be smaller + * than 32 bits. + */ + if (addr->length != int32) + return KRB5_CC_NOMEM; /* XXX */ + + if (addr->length == 0) + return KRB5_OK; + + addr->contents = (krb5_octet *) malloc(addr->length); + if (addr->contents == NULL) + return KRB5_CC_NOMEM; + + kret = krb5_krcc_parse(context, id, addr->contents, addr->length, bc); + CHECK(kret); + + return KRB5_OK; + errout: + if (addr->contents) + krb5_xfree(addr->contents); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id, + krb5_authdata *** a, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 length; + size_t msize; + int i; + + *a = 0; + + /* Read the number of components */ + kret = krb5_krcc_parse_int32(context, id, &length, bc); + CHECK(kret); + + if (length == 0) + return KRB5_OK; + + /* + * Make *a able to hold length pointers to krb5_authdata structs + * Add one extra for a null-terminated list + */ + msize = length; + msize += 1; + if (msize == 0 || msize - 1 != length || length < 0) + return KRB5_CC_NOMEM; + *a = ALLOC(msize, krb5_authdata *); + if (*a == NULL) + return KRB5_CC_NOMEM; + + for (i = 0; i < length; i++) { + (*a)[i] = (krb5_authdata *) malloc(sizeof(krb5_authdata)); + if ((*a)[i] == NULL) { + krb5_free_authdata(context, *a); + return KRB5_CC_NOMEM; + } + kret = krb5_krcc_parse_authdatum(context, id, (*a)[i], bc); + CHECK(kret); + } + + return KRB5_OK; + errout: + if (*a) + krb5_free_authdata(context, *a); + return kret; +} + +static krb5_error_code +krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id, + krb5_authdata * a, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 int32; + krb5_ui_2 ui2; + + a->magic = KV5M_AUTHDATA; + a->contents = NULL; + + kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + CHECK(kret); + a->ad_type = (krb5_authdatatype) ui2; + kret = krb5_krcc_parse_int32(context, id, &int32, bc); + CHECK(kret); + if ((int32 & VALID_INT_BITS) != int32) /* Overflow int??? */ + return KRB5_CC_NOMEM; + a->length = int32; + /* + * Value could have gotten truncated if int is + * smaller than 32 bits. + */ + if (a->length != int32) + return KRB5_CC_NOMEM; /* XXX */ + + if (a->length == 0) + return KRB5_OK; + + a->contents = (krb5_octet *) malloc(a->length); + if (a->contents == NULL) + return KRB5_CC_NOMEM; + + kret = krb5_krcc_parse(context, id, a->contents, a->length, bc); + CHECK(kret); + + return KRB5_OK; + errout: + if (a->contents) + krb5_xfree(a->contents); + return kret; + +} + +static krb5_error_code +krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i, + krb5_krcc_bc * bc) +{ + krb5_error_code kret; + unsigned char buf[2]; + + kret = krb5_krcc_parse(context, id, buf, 2, bc); + if (kret) + return kret; + *i = (buf[0] << 8) + buf[1]; + return 0; +} + +/* + * Requires: + * locked mutex + * + * Effects: + * Copies len bytes from buf into the payload buffer (bc->bpp). + * Detects attempts to write past end of the payload buffer. + * Updates payload buffer pointer accordingly. + * + * Errors: + * system errors + */ +static krb5_error_code +krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf, + unsigned int len, krb5_krcc_bc * bc) +{ + if (bc->bpp + len > bc->endp) + return KRB5_CC_WRITE; + + memcpy(bc->bpp, buf, len); + bc->bpp += len; + + return KRB5_OK; +} + +static krb5_error_code +krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id, + krb5_principal princ, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_int32 i, length, tmp, type; + + type = krb5_princ_type(context, princ); + tmp = length = krb5_princ_size(context, princ); + + kret = krb5_krcc_unparse_int32(context, id, type, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_int32(context, id, tmp, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_krb5data(context, id, + krb5_princ_realm(context, princ), bc); + CHECK_OUT(kret); + + for (i = 0; i < length; i++) { + kret = krb5_krcc_unparse_krb5data(context, id, + krb5_princ_component(context, princ, + i), bc); + CHECK_OUT(kret); + } + + return KRB5_OK; +} + +static krb5_error_code +krb5_krcc_unparse_keyblock(krb5_context context, krb5_ccache id, + krb5_keyblock * keyblock, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + + kret = krb5_krcc_unparse_ui_2(context, id, keyblock->enctype, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_ui_4(context, id, keyblock->length, bc); + CHECK_OUT(kret); + return krb5_krcc_unparse(context, id, (char *) keyblock->contents, + keyblock->length, bc); +} + +static krb5_error_code +krb5_krcc_unparse_times(krb5_context context, krb5_ccache id, + krb5_ticket_times * t, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + + kret = krb5_krcc_unparse_int32(context, id, t->authtime, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_int32(context, id, t->starttime, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_int32(context, id, t->endtime, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_int32(context, id, t->renew_till, bc); + CHECK_OUT(kret); + return 0; +} + +static krb5_error_code +krb5_krcc_unparse_krb5data(krb5_context context, krb5_ccache id, + krb5_data * data, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + + kret = krb5_krcc_unparse_ui_4(context, id, data->length, bc); + CHECK_OUT(kret); + return krb5_krcc_unparse(context, id, data->data, data->length, bc); +} + +static krb5_error_code +krb5_krcc_unparse_int32(krb5_context context, krb5_ccache id, krb5_int32 i, + krb5_krcc_bc * bc) +{ + unsigned char buf[4]; + + buf[3] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[2] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[1] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[0] = (unsigned char) (i & 0xFF); + return krb5_krcc_unparse(context, id, buf, 4, bc); +} + +static krb5_error_code +krb5_krcc_unparse_octet(krb5_context context, krb5_ccache id, krb5_int32 i, + krb5_krcc_bc * bc) +{ + krb5_octet ibuf; + + ibuf = (krb5_octet) i; + return krb5_krcc_unparse(context, id, (char *) &ibuf, 1, bc); +} + +static krb5_error_code +krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id, + krb5_address ** addrs, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_address **temp; + krb5_int32 i, length = 0; + + /* Count the number of components */ + if (addrs) { + temp = addrs; + while (*temp++) + length += 1; + } + + kret = krb5_krcc_unparse_int32(context, id, length, bc); + CHECK_OUT(kret); + for (i = 0; i < length; i++) { + kret = krb5_krcc_unparse_addr(context, id, addrs[i], bc); + CHECK_OUT(kret); + } + + return KRB5_OK; +} + +static krb5_error_code +krb5_krcc_unparse_addr(krb5_context context, krb5_ccache id, + krb5_address * addr, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + + kret = krb5_krcc_unparse_ui_2(context, id, addr->addrtype, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_ui_4(context, id, addr->length, bc); + CHECK_OUT(kret); + return krb5_krcc_unparse(context, id, (char *) addr->contents, + addr->length, bc); +} + +static krb5_error_code +krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id, + krb5_authdata ** a, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + krb5_authdata **temp; + krb5_int32 i, length = 0; + + if (a != NULL) { + for (temp = a; *temp; temp++) + length++; + } + + kret = krb5_krcc_unparse_int32(context, id, length, bc); + CHECK_OUT(kret); + for (i = 0; i < length; i++) { + kret = krb5_krcc_unparse_authdatum(context, id, a[i], bc); + CHECK_OUT(kret); + } + return KRB5_OK; +} + +static krb5_error_code +krb5_krcc_unparse_authdatum(krb5_context context, krb5_ccache id, + krb5_authdata * a, krb5_krcc_bc * bc) +{ + krb5_error_code kret; + + kret = krb5_krcc_unparse_ui_2(context, id, a->ad_type, bc); + CHECK_OUT(kret); + kret = krb5_krcc_unparse_ui_4(context, id, a->length, bc); + CHECK_OUT(kret); + return krb5_krcc_unparse(context, id, (krb5_pointer) a->contents, + a->length, bc); +} + +static krb5_error_code +krb5_krcc_unparse_ui_4(krb5_context context, krb5_ccache id, krb5_ui_4 i, + krb5_krcc_bc * bc) +{ + unsigned char buf[4]; + + buf[3] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[2] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[1] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[0] = (unsigned char) (i & 0xFF); + return krb5_krcc_unparse(context, id, buf, 4, bc); +} + +static krb5_error_code +krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i, + krb5_krcc_bc * bc) +{ + unsigned char buf[2]; + + buf[1] = (unsigned char) (i & 0xFF); + i >>= 8; + buf[0] = (unsigned char) (i & 0xFF); + return krb5_krcc_unparse(context, id, buf, 2, bc); +} + +/* + * =============================================================== + * INTERNAL functions to unparse a credential into a key payload + * (Borrowed heavily from krb5_fcc_{read|store}_ funtions.) + * =============================================================== + */ + +/* + * Take a credential structure and unparse it (serialize) + * for storage into a keyring key entry. + * Caller is responsible for freeing returned buffer. + */ +static krb5_error_code +krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id, + krb5_creds * creds, char **datapp, unsigned int *lenptr) +{ + krb5_error_code kret; + char *buf; + krb5_krcc_bc bc; + + if (!creds || !datapp || !lenptr) + return EINVAL; + + *datapp = NULL; + *lenptr = 0; + + buf = malloc(GUESS_CRED_SIZE); + if (buf == NULL) + return KRB5_CC_NOMEM; + + bc.bpp = buf; + bc.endp = buf + GUESS_CRED_SIZE; + + kret = krb5_krcc_unparse_principal(context, id, creds->client, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_principal(context, id, creds->server, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_keyblock(context, id, &creds->keyblock, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_times(context, id, &creds->times, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_octet(context, id, (krb5_int32) creds->is_skey, + &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_int32(context, id, creds->ticket_flags, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_addrs(context, id, creds->addresses, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_authdata(context, id, creds->authdata, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_krb5data(context, id, &creds->ticket, &bc); + CHECK_N_GO(kret, errout); + + kret = krb5_krcc_unparse_krb5data(context, id, &creds->second_ticket, &bc); + CHECK_N_GO(kret, errout); + + /* Success! */ + *datapp = buf; + *lenptr = bc.bpp - buf; + kret = KRB5_OK; + + errout: + return kret; +} + +/* + * 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. + */ +const krb5_cc_ops krb5_krcc_ops = { + 0, + "KEYRING", + krb5_krcc_get_name, + krb5_krcc_resolve, + krb5_krcc_generate_new, + krb5_krcc_initialize, + krb5_krcc_destroy, + krb5_krcc_close, + krb5_krcc_store, + krb5_krcc_retrieve, + krb5_krcc_get_principal, + krb5_krcc_start_seq_get, + krb5_krcc_next_cred, + krb5_krcc_end_seq_get, + krb5_krcc_remove_cred, + krb5_krcc_set_flags, + krb5_krcc_get_flags /* added after 1.4 release */ +}; + +#else /* !USE_KEYRING_CCACHE */ + +/* + * Export this, but it shouldn't be used. + */ +const krb5_cc_ops krb5_krcc_ops = { + 0, + "KEYRING", + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL /* added after 1.4 release */ +}; +#endif /* USE_KEYRING_CCACHE */ diff --git a/src/lib/krb5/ccache/ccbase.c b/src/lib/krb5/ccache/ccbase.c index 3a68ecac4..431e43bc9 100644 --- a/src/lib/krb5/ccache/ccbase.c +++ b/src/lib/krb5/ccache/ccbase.c @@ -38,6 +38,9 @@ struct krb5_cc_typelist { struct krb5_cc_typelist *next; }; extern const krb5_cc_ops krb5_mcc_ops; +#ifdef USE_KEYRING_CCACHE +extern const krb5_cc_ops krb5_krcc_ops; +#endif #ifdef _WIN32 extern const krb5_cc_ops krb5_lcc_ops; @@ -45,12 +48,22 @@ static struct krb5_cc_typelist cc_lcc_entry = { &krb5_lcc_ops, NULL }; static struct krb5_cc_typelist cc_mcc_entry = { &krb5_mcc_ops, &cc_lcc_entry }; #else static struct krb5_cc_typelist cc_mcc_entry = { &krb5_mcc_ops, NULL }; +#ifdef USE_KEYRING_CCACHE +static struct krb5_cc_typelist cc_file_entry = { &krb5_cc_file_ops, + &cc_mcc_entry }; +static struct krb5_cc_typelist cc_krcc_entry = { &krb5_krcc_ops, + &cc_file_entry }; +#endif /* USE_KEYRING_CCACHE */ #endif static struct krb5_cc_typelist cc_fcc_entry = { &krb5_cc_file_ops, &cc_mcc_entry }; - -static struct krb5_cc_typelist *cc_typehead = &cc_fcc_entry; +#ifdef USE_KEYRING_CCACHE +#define INITIAL_TYPEHEAD (&cc_krcc_entry) +#else +#define INITIAL_TYPEHEAD (&cc_fcc_entry) +#endif +static struct krb5_cc_typelist *cc_typehead = INITIAL_TYPEHEAD; static k5_mutex_t cc_typelist_lock = K5_MUTEX_PARTIAL_INITIALIZER; int @@ -67,6 +80,11 @@ krb5int_cc_initialize(void) err = k5_mutex_finish_init(&krb5int_cc_file_mutex); if (err) return err; +#ifdef USE_KEYRING_CCACHE + err = k5_mutex_finish_init(&krb5int_krcc_mutex); + if (err) + return err; +#endif return 0; } @@ -77,7 +95,10 @@ krb5int_cc_finalize(void) k5_mutex_destroy(&cc_typelist_lock); k5_mutex_destroy(&krb5int_cc_file_mutex); k5_mutex_destroy(&krb5int_mcc_mutex); - for (t = cc_typehead; t != &cc_fcc_entry; t = t_next) { +#ifdef USE_KEYRING_CCACHE + k5_mutex_destroy(&krb5int_krcc_mutex); +#endif + for (t = cc_typehead; t != INITIAL_TYPEHEAD; t = t_next) { t_next = t->next; free(t); } diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c index 1d2e272f2..0db618b76 100644 --- a/src/lib/krb5/ccache/t_cc.c +++ b/src/lib/krb5/ccache/t_cc.c @@ -285,6 +285,7 @@ int main (void) test_misc(context); do_test(context, ""); + do_test(context, "KEYRING:"); do_test(context, "MEMORY:"); do_test(context, "FILE:");