Move init/copy/destroy fallback sequence from fcc_move() to krb5_cc_move(). Make sure all backends's move() method calls krb5_cc_destroy() on the source on success (and only on success). In text_cc make sure that we can find in the destination the cred stored into the source.
		
			
				
	
	
		
			2030 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2030 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * Copyright (c) 2006 The Regents of the University of Michigan.
 | 
						|
 * All rights reserved.
 | 
						|
 *
 | 
						|
 * Portions Copyright (c) 2018, AuriStor, Inc.
 | 
						|
 *
 | 
						|
 * Permission is granted to use, copy, create derivative works
 | 
						|
 * and redistribute this software and such derivative works
 | 
						|
 * for any purpose, so long as the name of The University of
 | 
						|
 * Michigan is not used in any advertising or publicity
 | 
						|
 * pertaining to the use of distribution of this software
 | 
						|
 * without specific, written prior authorization.  If the
 | 
						|
 * above copyright notice or any other identification of the
 | 
						|
 * University of Michigan is included in any copy of any
 | 
						|
 * portion of this software, then the disclaimer below must
 | 
						|
 * also be included.
 | 
						|
 *
 | 
						|
 * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
 | 
						|
 * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
 | 
						|
 * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
 | 
						|
 * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
 | 
						|
 * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
 | 
						|
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
 | 
						|
 * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
 | 
						|
 * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
 | 
						|
 * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
 | 
						|
 * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
 | 
						|
 * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
 | 
						|
 * SUCH DAMAGES.
 | 
						|
 */
 | 
						|
/*
 | 
						|
 * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of
 | 
						|
 * Technology.  All Rights Reserved.
 | 
						|
 *
 | 
						|
 * Original stdio support copyright 1995 by Cygnus Support.
 | 
						|
 *
 | 
						|
 * Export of this software from the United States of America may
 | 
						|
 *   require a specific license from the United States Government.
 | 
						|
 *   It is the responsibility of any person or organization contemplating
 | 
						|
 *   export to obtain such a license before exporting.
 | 
						|
 *
 | 
						|
 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
 | 
						|
 * distribute this software and its documentation for any purpose and
 | 
						|
 * without fee is hereby granted, provided that the above copyright
 | 
						|
 * notice appear in all copies and that both that copyright notice and
 | 
						|
 * this permission notice appear in supporting documentation, and that
 | 
						|
 * the name of M.I.T. not be used in advertising or publicity pertaining
 | 
						|
 * to distribution of the software without specific, written prior
 | 
						|
 * permission.  Furthermore if you modify this software you must label
 | 
						|
 * your software as modified software and not distribute it in such a
 | 
						|
 * fashion that it might be confused with the original M.I.T. software.
 | 
						|
 * M.I.T. makes no representations about the suitability of
 | 
						|
 * this software for any purpose.  It is provided "as is" without express
 | 
						|
 * or implied warranty.
 | 
						|
 */
 | 
						|
 | 
						|
/*
 | 
						|
 * This file implements a collection-enabled credential cache type where the
 | 
						|
 * credentials are stored in the Linux keyring facility.
 | 
						|
 *
 | 
						|
 * A residual of this type can have three forms:
 | 
						|
 *    anchor:collection:subsidiary
 | 
						|
 *    anchor:collection
 | 
						|
 *    collection
 | 
						|
 *
 | 
						|
 * The anchor name is "process", "thread", or "legacy" and determines where we
 | 
						|
 * search for keyring collections.  In the third form, the anchor name is
 | 
						|
 * presumed to be "legacy".  The anchor keyring for legacy caches is the
 | 
						|
 * session keyring.
 | 
						|
 *
 | 
						|
 * If the subsidiary name is present, the residual identifies a single cache
 | 
						|
 * within a collection.  Otherwise, the residual identifies the collection
 | 
						|
 * itself.  When a residual identifying a collection is resolved, the
 | 
						|
 * collection's primary key is looked up (or initialized, using the collection
 | 
						|
 * name as the subsidiary name), and the resulting cache's name will use the
 | 
						|
 * first name form and will identify the primary cache.
 | 
						|
 *
 | 
						|
 * Keyring collections are named "_krb_<collection>" and are linked from the
 | 
						|
 * anchor keyring.  The keys within a keyring collection are links to cache
 | 
						|
 * keyrings, plus a link to one user key named "krb_ccache:primary" which
 | 
						|
 * contains a serialized representation of the collection version (currently 1)
 | 
						|
 * and the primary name of the collection.
 | 
						|
 *
 | 
						|
 * Cache keyrings contain one user key per credential which contains a
 | 
						|
 * serialized representation of the credential.  There is also one user key
 | 
						|
 * named "__krb5_princ__" which contains a serialized representation of the
 | 
						|
 * cache's default principal.
 | 
						|
 *
 | 
						|
 * If the anchor name is "legacy", then the initial primary cache (the one
 | 
						|
 * named with the collection name) is also linked to the session keyring, and
 | 
						|
 * we look for a cache in that location when initializing the collection.  This
 | 
						|
 * extra link allows that cache to be visible to old versions of the KEYRING
 | 
						|
 * cache type, and allows us to see caches created by that code.
 | 
						|
 */
 | 
						|
 | 
						|
#include "krb5_locl.h"
 | 
						|
 | 
						|
#ifdef HAVE_KEYUTILS_H
 | 
						|
 | 
						|
#include <keyutils.h>
 | 
						|
 | 
						|
/*
 | 
						|
 * We try to use the big_key key type for credentials except in legacy caches.
 | 
						|
 * We fall back to the user key type if the kernel does not support big_key.
 | 
						|
 * If the library doesn't support keyctl_get_persistent(), we don't even try
 | 
						|
 * big_key since the two features were added at the same time.
 | 
						|
 */
 | 
						|
#ifdef HAVE_KEYCTL_GET_PERSISTENT
 | 
						|
#define KRCC_CRED_KEY_TYPE		"big_key"
 | 
						|
#else
 | 
						|
#define KRCC_CRED_KEY_TYPE		"user"
 | 
						|
#endif
 | 
						|
 | 
						|
/*
 | 
						|
 * We use the "user" key type for collection primary names, for cache principal
 | 
						|
 * names, and for credentials in legacy caches.
 | 
						|
 */
 | 
						|
#define KRCC_KEY_TYPE_USER		"user"
 | 
						|
 | 
						|
/*
 | 
						|
 * We create ccaches as separate keyrings
 | 
						|
 */
 | 
						|
#define KRCC_KEY_TYPE_KEYRING		"keyring"
 | 
						|
 | 
						|
/*
 | 
						|
 * Special name of the key within a ccache keyring
 | 
						|
 * holding principal information
 | 
						|
 */
 | 
						|
#define KRCC_SPEC_PRINC_KEYNAME		"__krb5_princ__"
 | 
						|
 | 
						|
/*
 | 
						|
 * Special name for the key to communicate the name(s)
 | 
						|
 * of credentials caches to be used for requests.
 | 
						|
 * This should currently contain a single name, but
 | 
						|
 * in the future may contain a list that may be
 | 
						|
 * intelligently chosen from.
 | 
						|
 */
 | 
						|
#define KRCC_SPEC_CCACHE_SET_KEYNAME	"__krb5_cc_set__"
 | 
						|
 | 
						|
/*
 | 
						|
 * This name identifies the key containing the name of the current primary
 | 
						|
 * cache within a collection.
 | 
						|
 */
 | 
						|
#define KRCC_COLLECTION_PRIMARY		"krb_ccache:primary"
 | 
						|
 | 
						|
/*
 | 
						|
 * If the library context does not specify a keyring collection, unique ccaches
 | 
						|
 * will be created within this collection.
 | 
						|
 */
 | 
						|
#define KRCC_DEFAULT_UNIQUE_COLLECTION	"session:__krb5_unique__"
 | 
						|
 | 
						|
/*
 | 
						|
 * Collection keyring names begin with this prefix.  We use a prefix so that a
 | 
						|
 * cache keyring with the collection name itself can be linked directly into
 | 
						|
 * the anchor, for legacy session keyring compatibility.
 | 
						|
 */
 | 
						|
#define KRCC_CCCOL_PREFIX		"_krb_"
 | 
						|
 | 
						|
/*
 | 
						|
 * For the "persistent" anchor type, we look up or create this fixed keyring
 | 
						|
 * name within the per-UID persistent keyring.
 | 
						|
 */
 | 
						|
#define KRCC_PERSISTENT_KEYRING_NAME	"_krb"
 | 
						|
 | 
						|
/*
 | 
						|
 * Name of the key holding time offsets for the individual cache
 | 
						|
 */
 | 
						|
#define KRCC_TIME_OFFSETS		"__krb5_time_offsets__"
 | 
						|
 | 
						|
/*
 | 
						|
 * Keyring name prefix and length of random name part
 | 
						|
 */
 | 
						|
#define KRCC_NAME_PREFIX		"krb_ccache_"
 | 
						|
#define KRCC_NAME_RAND_CHARS		8
 | 
						|
 | 
						|
#define KRCC_COLLECTION_VERSION		1
 | 
						|
 | 
						|
#define KRCC_PERSISTENT_ANCHOR		"persistent"
 | 
						|
#define KRCC_PROCESS_ANCHOR		"process"
 | 
						|
#define KRCC_THREAD_ANCHOR		"thread"
 | 
						|
#define KRCC_SESSION_ANCHOR		"session"
 | 
						|
#define KRCC_USER_ANCHOR		"user"
 | 
						|
#define KRCC_LEGACY_ANCHOR		"legacy"
 | 
						|
 | 
						|
#if SIZEOF_KEY_SERIAL_T != 4
 | 
						|
/* lockless implementation assumes 32-bit key serials */
 | 
						|
#error only 32-bit key serial numbers supported by this version of keyring ccache
 | 
						|
#endif
 | 
						|
 | 
						|
typedef union _krb5_krcache_and_princ_id {
 | 
						|
    uint64_t krcu_cache_and_princ_id;
 | 
						|
    struct {
 | 
						|
	key_serial_t cache_id;		/* keyring ID representing ccache */
 | 
						|
	key_serial_t princ_id;		/* key ID holding principal info */
 | 
						|
    } krcu_id;
 | 
						|
    #define krcu_cache_id		krcu_id.cache_id
 | 
						|
    #define krcu_princ_id		krcu_id.princ_id
 | 
						|
} krb5_krcache_and_princ_id;
 | 
						|
 | 
						|
/*
 | 
						|
 * This represents a credentials cache "file" where cache_id is the keyring
 | 
						|
 * serial number for this credentials cache "file".  Each key in the keyring
 | 
						|
 * contains a separate key.
 | 
						|
 *
 | 
						|
 * Thread-safe as long as caches are not destroyed whilst other threads are
 | 
						|
 * using them.
 | 
						|
 */
 | 
						|
typedef struct _krb5_krcache {
 | 
						|
    char *krc_name;			/* Name for this credentials cache */
 | 
						|
    krb5_timestamp krc_changetime;	/* update time, does not decrease (mutable) */
 | 
						|
    krb5_krcache_and_princ_id krc_id;	/* cache and principal IDs (mutable) */
 | 
						|
    #define krc_cache_and_principal_id	krc_id.krcu_cache_and_princ_id
 | 
						|
    #define krc_cache_id		krc_id.krcu_id.cache_id
 | 
						|
    #define krc_princ_id		krc_id.krcu_id.princ_id
 | 
						|
    key_serial_t krc_coll_id;		/* collection containing this cache keyring */
 | 
						|
    krb5_boolean krc_is_legacy;		/* */
 | 
						|
} krb5_krcache;
 | 
						|
 | 
						|
#define KRCACHE(X) ((krb5_krcache *)(X)->data.data)
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_get_first(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_get_next(krb5_context context,
 | 
						|
	      krb5_ccache id,
 | 
						|
	      krb5_cc_cursor *cursor,
 | 
						|
	      krb5_creds *creds);
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_end_get(krb5_context context,
 | 
						|
	     krb5_ccache id,
 | 
						|
	     krb5_cc_cursor *cursor);
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor);
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
clear_cache_keyring(krb5_context context, key_serial_t *pcache_id);
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
alloc_cache(krb5_context context,
 | 
						|
 	    key_serial_t collection_id,
 | 
						|
	    key_serial_t cache_id,
 | 
						|
	    const char *anchor_name,
 | 
						|
	    const char *collection_name,
 | 
						|
	    const char *subsidiary_name,
 | 
						|
	    krb5_krcache **data);
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
save_principal(krb5_context context,
 | 
						|
	       key_serial_t cache_id,
 | 
						|
	       krb5_const_principal princ,
 | 
						|
	       key_serial_t *pprinc_id);
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
save_time_offsets(krb5_context context,
 | 
						|
		  key_serial_t cache_id,
 | 
						|
		  int32_t sec_offset,
 | 
						|
		  int32_t usec_offset);
 | 
						|
 | 
						|
static void
 | 
						|
update_change_time(krb5_context context,
 | 
						|
		   krb5_timestamp now,
 | 
						|
		   krb5_krcache *d);
 | 
						|
 | 
						|
/*
 | 
						|
 * GET_PERSISTENT(uid) acquires the persistent keyring for uid, or falls back
 | 
						|
 * to the user keyring if uid matches the current effective uid.
 | 
						|
 */
 | 
						|
 | 
						|
static key_serial_t
 | 
						|
get_persistent_fallback(uid_t uid)
 | 
						|
{
 | 
						|
    return (uid == geteuid()) ? KEY_SPEC_USER_KEYRING : -1;
 | 
						|
}
 | 
						|
 | 
						|
#ifdef HAVE_KEYCTL_GET_PERSISTENT
 | 
						|
#define GET_PERSISTENT get_persistent_real
 | 
						|
static key_serial_t
 | 
						|
get_persistent_real(uid_t uid)
 | 
						|
{
 | 
						|
    key_serial_t key;
 | 
						|
 | 
						|
    key = keyctl_get_persistent(uid, KEY_SPEC_PROCESS_KEYRING);
 | 
						|
 | 
						|
    return (key == -1 && errno == ENOTSUP) ? get_persistent_fallback(uid) : key;
 | 
						|
}
 | 
						|
#else
 | 
						|
#define GET_PERSISTENT get_persistent_fallback
 | 
						|
#endif
 | 
						|
 | 
						|
/*
 | 
						|
 * If a process has no explicitly set session keyring, KEY_SPEC_SESSION_KEYRING
 | 
						|
 * will resolve to the user session keyring for ID lookup and reading, but in
 | 
						|
 * some kernel versions, writing to that special keyring will instead create a
 | 
						|
 * new empty session keyring for the process.  We do not want that; the keys we
 | 
						|
 * create would be invisible to other processes.  We can work around that
 | 
						|
 * behavior by explicitly writing to the user session keyring when it matches
 | 
						|
 * the session keyring.  This function returns the keyring we should write to
 | 
						|
 * for the session anchor.
 | 
						|
 */
 | 
						|
static key_serial_t
 | 
						|
session_write_anchor(void)
 | 
						|
{
 | 
						|
    key_serial_t s, u;
 | 
						|
 | 
						|
    s = keyctl_get_keyring_ID(KEY_SPEC_SESSION_KEYRING, 0);
 | 
						|
    u = keyctl_get_keyring_ID(KEY_SPEC_USER_SESSION_KEYRING, 0);
 | 
						|
 | 
						|
    return (s == u) ? KEY_SPEC_USER_SESSION_KEYRING : KEY_SPEC_SESSION_KEYRING;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Find or create a keyring within parent with the given name.  If possess is
 | 
						|
 * nonzero, also make sure the key is linked from possess.  This is necessary
 | 
						|
 * to ensure that we have possession rights on the key when the parent is the
 | 
						|
 * user or persistent keyring.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
find_or_create_keyring(key_serial_t parent,
 | 
						|
		       key_serial_t possess,
 | 
						|
		       const char *name,
 | 
						|
		       key_serial_t *pkey)
 | 
						|
{
 | 
						|
    key_serial_t key;
 | 
						|
 | 
						|
    *pkey = -1;
 | 
						|
 | 
						|
    key = keyctl_search(parent, KRCC_KEY_TYPE_KEYRING, name, possess);
 | 
						|
    if (key == -1) {
 | 
						|
	if (possess != 0) {
 | 
						|
	    key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, possess);
 | 
						|
	    if (key == -1 || keyctl_link(key, parent) == -1)
 | 
						|
		return errno;
 | 
						|
	} else {
 | 
						|
	    key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
 | 
						|
	    if (key == -1)
 | 
						|
		return errno;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    *pkey = key;
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Parse a residual name into an anchor name, a collection name, and possibly a
 | 
						|
 * subsidiary name.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
parse_residual(krb5_context context,
 | 
						|
	       const char *residual,
 | 
						|
	       char **panchor_name,
 | 
						|
	       char **pcollection_name,
 | 
						|
	       char **psubsidiary_name)
 | 
						|
{
 | 
						|
    char *anchor_name = NULL;
 | 
						|
    char *collection_name = NULL;
 | 
						|
    char *subsidiary_name = NULL;
 | 
						|
    const char *sep;
 | 
						|
 | 
						|
    *panchor_name = NULL;
 | 
						|
    *pcollection_name = NULL;
 | 
						|
    *psubsidiary_name = NULL;
 | 
						|
 | 
						|
    /* Parse out the anchor name.  Use the legacy anchor if not present. */
 | 
						|
    sep = strchr(residual, ':');
 | 
						|
    if (sep == NULL) {
 | 
						|
	anchor_name = strdup(KRCC_LEGACY_ANCHOR);
 | 
						|
	if (anchor_name == NULL)
 | 
						|
	    goto nomem;
 | 
						|
    } else {
 | 
						|
	anchor_name = strndup(residual, sep - residual);
 | 
						|
	if (anchor_name == NULL)
 | 
						|
	    goto nomem;
 | 
						|
	residual = sep + 1;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Parse out the collection and subsidiary name. */
 | 
						|
    sep = strchr(residual, ':');
 | 
						|
    if (sep == NULL) {
 | 
						|
	collection_name = strdup(residual);
 | 
						|
	if (collection_name == NULL)
 | 
						|
	    goto nomem;
 | 
						|
 | 
						|
	subsidiary_name = NULL;
 | 
						|
    } else {
 | 
						|
	collection_name = strndup(residual, sep - residual);
 | 
						|
	if (collection_name == NULL)
 | 
						|
	    goto nomem;
 | 
						|
 | 
						|
	subsidiary_name = strdup(sep + 1);
 | 
						|
	if (subsidiary_name == NULL)
 | 
						|
	    goto nomem;
 | 
						|
    }
 | 
						|
 | 
						|
    *panchor_name = anchor_name;
 | 
						|
    *pcollection_name = collection_name;
 | 
						|
    *psubsidiary_name = subsidiary_name;
 | 
						|
 | 
						|
    return 0;
 | 
						|
 | 
						|
nomem:
 | 
						|
    free(anchor_name);
 | 
						|
    free(collection_name);
 | 
						|
    free(subsidiary_name);
 | 
						|
 | 
						|
    return krb5_enomem(context);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Return TRUE if residual identifies a subsidiary cache which should be linked
 | 
						|
 * into the anchor so it can be visible to old code.  This is the case if the
 | 
						|
 * residual has the legacy anchor and the subsidiary name matches the
 | 
						|
 * collection name.
 | 
						|
 */
 | 
						|
static krb5_boolean
 | 
						|
is_legacy_cache_name_p(const char *residual)
 | 
						|
{
 | 
						|
    const char *sep, *aname, *cname, *sname;
 | 
						|
    size_t alen, clen, legacy_len = sizeof(KRCC_LEGACY_ANCHOR) - 1;
 | 
						|
 | 
						|
    /* Get pointers to the anchor, collection, and subsidiary names. */
 | 
						|
    aname = residual;
 | 
						|
    sep = strchr(residual, ':');
 | 
						|
    if (sep == NULL)
 | 
						|
	return FALSE;
 | 
						|
 | 
						|
    alen = sep - aname;
 | 
						|
    cname = sep + 1;
 | 
						|
    sep = strchr(cname, ':');
 | 
						|
    if (sep == NULL)
 | 
						|
	return FALSE;
 | 
						|
 | 
						|
    clen = sep - cname;
 | 
						|
    sname = sep + 1;
 | 
						|
 | 
						|
    return alen == legacy_len && clen == strlen(sname) &&
 | 
						|
	   strncmp(aname, KRCC_LEGACY_ANCHOR, alen) == 0 &&
 | 
						|
	   strncmp(cname, sname, clen) == 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * If the default cache name for context is a KEYRING cache, parse its residual
 | 
						|
 * string.  Otherwise set all outputs to NULL.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
get_default(krb5_context context,
 | 
						|
	    char **panchor_name,
 | 
						|
	    char **pcollection_name,
 | 
						|
	    char **psubsidiary_name)
 | 
						|
{
 | 
						|
    const char *defname;
 | 
						|
 | 
						|
    *panchor_name = *pcollection_name = *psubsidiary_name = NULL;
 | 
						|
 | 
						|
    defname = krb5_cc_default_name(context);
 | 
						|
    if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
 | 
						|
	return 0;
 | 
						|
 | 
						|
    return parse_residual(context, defname + 8,
 | 
						|
			  panchor_name, pcollection_name, psubsidiary_name);
 | 
						|
}
 | 
						|
 | 
						|
/* Create a residual identifying a subsidiary cache. */
 | 
						|
static krb5_error_code
 | 
						|
make_subsidiary_residual(krb5_context context,
 | 
						|
			 const char *anchor_name,
 | 
						|
			 const char *collection_name,
 | 
						|
			 const char *subsidiary_name,
 | 
						|
			 char **presidual)
 | 
						|
{
 | 
						|
    if (asprintf(presidual, "%s:%s:%s", anchor_name, collection_name,
 | 
						|
		 subsidiary_name) < 0) {
 | 
						|
	*presidual = NULL;
 | 
						|
	return krb5_enomem(context);
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Retrieve or create a keyring for collection_name within the anchor, and set
 | 
						|
 * *collection_id to its serial number.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
get_collection(krb5_context context,
 | 
						|
	       const char *anchor_name,
 | 
						|
	       const char *collection_name,
 | 
						|
	       key_serial_t *pcollection_id)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    key_serial_t persistent_id, anchor_id, possess_id = 0;
 | 
						|
    char *ckname, *cnend;
 | 
						|
    uid_t uidnum;
 | 
						|
 | 
						|
    *pcollection_id = 0;
 | 
						|
 | 
						|
    if (strcmp(anchor_name, KRCC_PERSISTENT_ANCHOR) == 0) {
 | 
						|
	/*
 | 
						|
	 * The collection name is a uid (or empty for the current effective
 | 
						|
	 * uid), and we look up a fixed keyring name within the persistent
 | 
						|
	 * keyring for that uid.  We link it to the process keyring to ensure
 | 
						|
	 * that we have possession rights on the collection key.
 | 
						|
	 */
 | 
						|
	if (*collection_name != '\0') {
 | 
						|
	    errno = 0;
 | 
						|
	    uidnum = (uid_t)strtol(collection_name, &cnend, 10);
 | 
						|
	    if (errno || *cnend != '\0')
 | 
						|
		return KRB5_KCC_INVALID_UID;
 | 
						|
	} else {
 | 
						|
	    uidnum = geteuid();
 | 
						|
	}
 | 
						|
 | 
						|
	persistent_id = GET_PERSISTENT(uidnum);
 | 
						|
	if (persistent_id == -1)
 | 
						|
	    return KRB5_KCC_INVALID_UID;
 | 
						|
 | 
						|
	return find_or_create_keyring(persistent_id, KEY_SPEC_PROCESS_KEYRING,
 | 
						|
				      KRCC_PERSISTENT_KEYRING_NAME,
 | 
						|
				      pcollection_id);
 | 
						|
    }
 | 
						|
 | 
						|
    if (strcmp(anchor_name, KRCC_PROCESS_ANCHOR) == 0) {
 | 
						|
	anchor_id = KEY_SPEC_PROCESS_KEYRING;
 | 
						|
    } else if (strcmp(anchor_name, KRCC_THREAD_ANCHOR) == 0) {
 | 
						|
	anchor_id = KEY_SPEC_THREAD_KEYRING;
 | 
						|
    } else if (strcmp(anchor_name, KRCC_SESSION_ANCHOR) == 0) {
 | 
						|
	anchor_id = session_write_anchor();
 | 
						|
    } else if (strcmp(anchor_name, KRCC_USER_ANCHOR) == 0) {
 | 
						|
	/*
 | 
						|
	 * The user keyring does not confer possession, so we need to link the
 | 
						|
	 * collection to the process keyring to maintain possession rights.
 | 
						|
	 */
 | 
						|
	anchor_id = KEY_SPEC_USER_KEYRING;
 | 
						|
	possess_id = KEY_SPEC_PROCESS_KEYRING;
 | 
						|
    } else if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
 | 
						|
	anchor_id = session_write_anchor();
 | 
						|
    } else {
 | 
						|
	return KRB5_KCC_INVALID_ANCHOR;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Look up the collection keyring name within the anchor keyring. */
 | 
						|
    if (asprintf(&ckname, "%s%s", KRCC_CCCOL_PREFIX, collection_name) == -1)
 | 
						|
	return krb5_enomem(context);
 | 
						|
 | 
						|
    ret = find_or_create_keyring(anchor_id, possess_id, ckname,
 | 
						|
				 pcollection_id);
 | 
						|
    free(ckname);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Store subsidiary_name into the primary index key for collection_id. */
 | 
						|
static krb5_error_code
 | 
						|
set_primary_name(krb5_context context,
 | 
						|
		 key_serial_t collection_id,
 | 
						|
		 const char *subsidiary_name)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    krb5_storage *sp;
 | 
						|
    krb5_data payload;
 | 
						|
    key_serial_t key;
 | 
						|
 | 
						|
    sp = krb5_storage_emem();
 | 
						|
    if (sp == NULL) {
 | 
						|
	krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
 | 
						|
	return KRB5_CC_NOMEM;
 | 
						|
    }
 | 
						|
    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
 | 
						|
 | 
						|
    ret = krb5_store_int32(sp, KRCC_COLLECTION_VERSION);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = krb5_store_string(sp, subsidiary_name);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = krb5_storage_to_data(sp, &payload);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
 | 
						|
		  payload.data, payload.length, collection_id);
 | 
						|
    ret = (key == -1) ? errno : 0;
 | 
						|
    krb5_data_free(&payload);
 | 
						|
 | 
						|
cleanup:
 | 
						|
    krb5_storage_free(sp);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
parse_index(krb5_context context,
 | 
						|
	    int32_t *version,
 | 
						|
	    char **primary,
 | 
						|
	    const unsigned char *payload,
 | 
						|
	    size_t psize)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    krb5_data payload_data;
 | 
						|
    krb5_storage *sp;
 | 
						|
 | 
						|
    payload_data.length = psize;
 | 
						|
    payload_data.data = rk_UNCONST(payload);
 | 
						|
 | 
						|
    sp = krb5_storage_from_data(&payload_data);
 | 
						|
    if (sp == NULL)
 | 
						|
	return KRB5_CC_NOMEM;
 | 
						|
 | 
						|
    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
 | 
						|
 | 
						|
    ret = krb5_ret_int32(sp, version);
 | 
						|
    if (ret == 0)
 | 
						|
	ret = krb5_ret_string(sp, primary);
 | 
						|
 | 
						|
    krb5_storage_free(sp);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get or initialize the primary name within collection_id and set
 | 
						|
 * *subsidiary to its value.  If initializing a legacy collection, look
 | 
						|
 * for a legacy cache and add it to the collection.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
get_primary_name(krb5_context context,
 | 
						|
		 const char *anchor_name,
 | 
						|
		 const char *collection_name,
 | 
						|
		 key_serial_t collection_id,
 | 
						|
		 char **psubsidiary)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    key_serial_t primary_id, legacy;
 | 
						|
    void *payload = NULL;
 | 
						|
    int payloadlen;
 | 
						|
    int32_t version;
 | 
						|
    char *subsidiary_name = NULL;
 | 
						|
 | 
						|
    *psubsidiary = NULL;
 | 
						|
 | 
						|
    primary_id = keyctl_search(collection_id, KRCC_KEY_TYPE_USER,
 | 
						|
			       KRCC_COLLECTION_PRIMARY, 0);
 | 
						|
    if (primary_id == -1) {
 | 
						|
	/*
 | 
						|
	 * Initialize the primary key using the collection name.  We can't name
 | 
						|
	 * a key with the empty string, so map that to an arbitrary string.
 | 
						|
	 */
 | 
						|
	subsidiary_name = strdup((*collection_name == '\0') ? "tkt" :
 | 
						|
				 collection_name);
 | 
						|
	if (subsidiary_name == NULL) {
 | 
						|
	    ret = krb5_enomem(context);
 | 
						|
	    goto cleanup;
 | 
						|
	}
 | 
						|
 | 
						|
	ret = set_primary_name(context, collection_id, subsidiary_name);
 | 
						|
	if (ret)
 | 
						|
	    goto cleanup;
 | 
						|
 | 
						|
	if (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0) {
 | 
						|
	    /*
 | 
						|
	     * Look for a cache created by old code. If we find one, add it to
 | 
						|
	     * the collection.
 | 
						|
	     */
 | 
						|
	    legacy = keyctl_search(KEY_SPEC_SESSION_KEYRING,
 | 
						|
				   KRCC_KEY_TYPE_KEYRING, subsidiary_name, 0);
 | 
						|
	    if (legacy != -1 && keyctl_link(legacy, collection_id) == -1) {
 | 
						|
		ret = errno;
 | 
						|
		goto cleanup;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    } else {
 | 
						|
	/* Read, parse, and free the primary key's payload. */
 | 
						|
	payloadlen = keyctl_read_alloc(primary_id, &payload);
 | 
						|
	if (payloadlen == -1) {
 | 
						|
	    ret = errno;
 | 
						|
	    goto cleanup;
 | 
						|
	}
 | 
						|
	ret = parse_index(context, &version, &subsidiary_name, payload,
 | 
						|
			  payloadlen);
 | 
						|
	if (ret)
 | 
						|
	    goto cleanup;
 | 
						|
 | 
						|
	if (version != KRCC_COLLECTION_VERSION) {
 | 
						|
	    ret = KRB5_KCC_UNKNOWN_VERSION;
 | 
						|
	    goto cleanup;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    *psubsidiary = subsidiary_name;
 | 
						|
    subsidiary_name = NULL;
 | 
						|
 | 
						|
cleanup:
 | 
						|
    free(payload);
 | 
						|
    free(subsidiary_name);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Note: MIT keyring code uses krb5int_random_string() as if the second argument
 | 
						|
 * is a character count rather than a size. The function below takes a character
 | 
						|
 * count to match the usage in this file correctly.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
generate_random_string(krb5_context context, char *s, size_t slen)
 | 
						|
{
 | 
						|
    static char chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
 | 
						|
    char *p;
 | 
						|
    size_t i;
 | 
						|
 | 
						|
    p = malloc(slen);
 | 
						|
    if (p == NULL)
 | 
						|
	return krb5_enomem(context);
 | 
						|
 | 
						|
    krb5_generate_random_block(p, slen);
 | 
						|
 | 
						|
    for (i = 0; i < slen; i++)
 | 
						|
	s[i] = chars[p[i] % (sizeof(chars) - 1)];
 | 
						|
 | 
						|
    s[i] = '\0';
 | 
						|
    free(p);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Create a keyring with a unique random name within collection_id.  Set
 | 
						|
 * *subsidiary to its name and *cache_id to its key serial number.
 | 
						|
 */
 | 
						|
static krb5_error_code
 | 
						|
add_unique_keyring(krb5_context context,
 | 
						|
		   key_serial_t collection_id,
 | 
						|
		   char **psubsidiary,
 | 
						|
		   key_serial_t *pcache_id)
 | 
						|
{
 | 
						|
    key_serial_t key;
 | 
						|
    krb5_error_code ret;
 | 
						|
    char uniquename[sizeof(KRCC_NAME_PREFIX) + KRCC_NAME_RAND_CHARS];
 | 
						|
    int prefixlen = sizeof(KRCC_NAME_PREFIX) - 1;
 | 
						|
    int tries;
 | 
						|
 | 
						|
    *psubsidiary = NULL;
 | 
						|
    *pcache_id = 0;
 | 
						|
 | 
						|
    memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
 | 
						|
 | 
						|
    for (key = -1, tries = 0; tries < 5; tries++) {
 | 
						|
	ret = generate_random_string(context, uniquename + prefixlen,
 | 
						|
				     KRCC_NAME_RAND_CHARS);
 | 
						|
	if (ret)
 | 
						|
	    return ret;
 | 
						|
 | 
						|
	key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0);
 | 
						|
	if (key == -1) {
 | 
						|
	    /* Name does not already exist.  Create it to reserve the name. */
 | 
						|
	    key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, collection_id);
 | 
						|
	    if (key == -1)
 | 
						|
		return errno;
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    *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);
 | 
						|
 | 
						|
    memset(&ids, 0, sizeof(ids));
 | 
						|
    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);
 | 
						|
 | 
						|
    memset(&ids, 0, sizeof(ids));
 | 
						|
    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;
 | 
						|
 | 
						|
    memset(&ids, 0, sizeof(ids));
 | 
						|
    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 - 1],
 | 
						|
		    ids.krcu_cache_id, ids.krcu_princ_id);
 | 
						|
 | 
						|
	keyctl_invalidate(krcursor->keys[krcursor->currkey - 1]);
 | 
						|
	krcursor->keys[krcursor->currkey - 1] = 0;
 | 
						|
	krb5_free_cred_contents(context, &found_cred);
 | 
						|
    }
 | 
						|
 | 
						|
    ret2 = krcc_end_get(context, id, &cursor);
 | 
						|
    if (ret == KRB5_CC_END)
 | 
						|
	ret = ret2;
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Set flags on the cache.  (We don't care about any flags.) */
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_set_flags(krb5_context context, krb5_ccache id, krb5_flags flags)
 | 
						|
{
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int KRB5_CALLCONV
 | 
						|
krcc_get_version(krb5_context context, krb5_ccache id)
 | 
						|
{
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 
 | 
						|
/* Store a credential in the cache keyring. */
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    krb5_krcache *data = KRCACHE(id);
 | 
						|
    krb5_storage *sp = NULL;
 | 
						|
    char *keyname = NULL;
 | 
						|
    key_serial_t cred_key, cache_id;
 | 
						|
    krb5_timestamp now;
 | 
						|
    krb5_data payload;
 | 
						|
 | 
						|
    krb5_data_zero(&payload);
 | 
						|
 | 
						|
    if (data == NULL)
 | 
						|
	return krb5_einval(context, 2);
 | 
						|
 | 
						|
    heim_base_exchange_32(&cache_id, data->krc_cache_id);
 | 
						|
 | 
						|
    if (cache_id == 0)
 | 
						|
	return KRB5_FCC_NOFILE;
 | 
						|
 | 
						|
    ret = krb5_unparse_name(context, creds->server, &keyname);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    sp = krb5_storage_emem();
 | 
						|
    if (sp == NULL) {
 | 
						|
	krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
 | 
						|
	ret = KRB5_CC_NOMEM;
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = krb5_store_creds(sp, creds);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = krb5_storage_to_data(sp, &payload);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    _krb5_debug(context, 10, "krcc_store: adding new key '%s' to keyring %d\n",
 | 
						|
		keyname, cache_id);
 | 
						|
    ret = add_cred_key(keyname, payload.data, payload.length, cache_id,
 | 
						|
		       data->krc_is_legacy, &cred_key);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = krb5_timeofday(context, &now);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    update_change_time(context, now, data);
 | 
						|
 | 
						|
    /* Set timeout on credential key */
 | 
						|
    if (creds->times.endtime > now)
 | 
						|
	(void) keyctl_set_timeout(cred_key, creds->times.endtime - now);
 | 
						|
 | 
						|
    /* Set timeout on credential cache keyring */
 | 
						|
    update_keyring_expiration(context, id, cache_id, now);
 | 
						|
 | 
						|
cleanup:
 | 
						|
    krb5_data_free(&payload);
 | 
						|
    krb5_storage_free(sp);
 | 
						|
    krb5_xfree(keyname);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Get the cache's last modification time.  (This is currently broken; it
 | 
						|
 * returns only the last change made using this handle.)
 | 
						|
 */
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_lastchange(krb5_context context,
 | 
						|
		krb5_ccache id,
 | 
						|
		krb5_timestamp *change_time)
 | 
						|
{
 | 
						|
    krb5_krcache *data = KRCACHE(id);
 | 
						|
 | 
						|
    if (data == NULL)
 | 
						|
	return krb5_einval(context, 2);
 | 
						|
 | 
						|
    heim_base_exchange_time_t(change_time, data->krc_changetime);
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code
 | 
						|
save_principal(krb5_context context,
 | 
						|
	       key_serial_t cache_id,
 | 
						|
	       krb5_const_principal princ,
 | 
						|
	       key_serial_t *pprinc_id)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    krb5_storage *sp;
 | 
						|
    key_serial_t newkey;
 | 
						|
    krb5_data payload;
 | 
						|
 | 
						|
    krb5_data_zero(&payload);
 | 
						|
 | 
						|
    sp = krb5_storage_emem();
 | 
						|
    if (sp == NULL) {
 | 
						|
	krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
 | 
						|
	return KRB5_CC_NOMEM;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = krb5_store_principal(sp, princ);
 | 
						|
    if (ret) {
 | 
						|
	krb5_storage_free(sp);
 | 
						|
	return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = krb5_storage_to_data(sp, &payload);
 | 
						|
    if (ret) {
 | 
						|
	krb5_storage_free(sp);
 | 
						|
	return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    krb5_storage_free(sp);
 | 
						|
    {
 | 
						|
	krb5_error_code tmp;
 | 
						|
	char *princname = NULL;
 | 
						|
 | 
						|
	tmp = krb5_unparse_name(context, princ, &princname);
 | 
						|
	_krb5_debug(context, 10, "save_principal: adding new key '%s' "
 | 
						|
		    "to keyring %d for principal '%s'\n",
 | 
						|
		    KRCC_SPEC_PRINC_KEYNAME, cache_id,
 | 
						|
		    tmp ? "<unknown>" : princname);
 | 
						|
	if (tmp == 0)
 | 
						|
	    krb5_xfree(princname);
 | 
						|
    }
 | 
						|
 | 
						|
    /* Add new key into keyring */
 | 
						|
    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
 | 
						|
		     payload.data, payload.length, cache_id);
 | 
						|
    if (newkey == -1) {
 | 
						|
	ret = errno;
 | 
						|
	_krb5_debug(context, 10, "Error adding principal key: %s\n", strerror(ret));
 | 
						|
    } else {
 | 
						|
	ret = 0;
 | 
						|
	*pprinc_id = newkey;
 | 
						|
    }
 | 
						|
 | 
						|
    krb5_data_free(&payload);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Add a key to the cache keyring containing the given time offsets. */
 | 
						|
static krb5_error_code
 | 
						|
save_time_offsets(krb5_context context,
 | 
						|
		  key_serial_t cache_id,
 | 
						|
		  int32_t sec_offset,
 | 
						|
		  int32_t usec_offset)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    key_serial_t newkey;
 | 
						|
    krb5_storage *sp;
 | 
						|
    krb5_data payload;
 | 
						|
 | 
						|
    krb5_data_zero(&payload);
 | 
						|
 | 
						|
    sp = krb5_storage_emem();
 | 
						|
    if (sp == NULL) {
 | 
						|
	krb5_set_error_message(context, KRB5_CC_NOMEM, N_("malloc: out of memory", ""));
 | 
						|
	return KRB5_CC_NOMEM;
 | 
						|
    }
 | 
						|
 | 
						|
    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
 | 
						|
 | 
						|
    ret = krb5_store_int32(sp, sec_offset);
 | 
						|
    if (ret == 0)
 | 
						|
	ret = krb5_store_int32(sp, usec_offset);
 | 
						|
    if (ret) {
 | 
						|
	krb5_storage_free(sp);
 | 
						|
	return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = krb5_storage_to_data(sp, &payload);
 | 
						|
    if (ret) {
 | 
						|
	krb5_storage_free(sp);
 | 
						|
	return ret;
 | 
						|
    }
 | 
						|
 | 
						|
    krb5_storage_free(sp);
 | 
						|
 | 
						|
    newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload.data,
 | 
						|
		     payload.length, cache_id);
 | 
						|
    ret = newkey == -1 ? errno : 0;
 | 
						|
 | 
						|
    krb5_data_free(&payload);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_set_kdc_offset(krb5_context context, krb5_ccache id, krb5_deltat offset)
 | 
						|
{
 | 
						|
    krb5_krcache *data = KRCACHE(id);
 | 
						|
    key_serial_t cache_id;
 | 
						|
    krb5_error_code ret;
 | 
						|
 | 
						|
    heim_base_exchange_32(&cache_id, data->krc_cache_id);
 | 
						|
 
 | 
						|
    ret = save_time_offsets(context, cache_id, (int32_t)offset, 0);
 | 
						|
    if (ret == 0)
 | 
						|
	update_change_time(context, 0, data);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Retrieve and parse the key in the cache keyring containing time offsets. */
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_get_kdc_offset(krb5_context context,
 | 
						|
		    krb5_ccache id,
 | 
						|
		    krb5_deltat *offset)
 | 
						|
{
 | 
						|
    krb5_krcache *data = KRCACHE(id);
 | 
						|
    krb5_error_code ret = 0;
 | 
						|
    key_serial_t key, cache_id;
 | 
						|
    krb5_storage *sp = NULL;
 | 
						|
    krb5_data payload;
 | 
						|
    int32_t sec_offset, usec_offset;
 | 
						|
 | 
						|
    if (data == NULL)
 | 
						|
	return krb5_einval(context, 2);
 | 
						|
 | 
						|
    krb5_data_zero(&payload);
 | 
						|
    heim_base_exchange_32(&cache_id, data->krc_cache_id);
 | 
						|
 | 
						|
    if (cache_id == 0) {
 | 
						|
	ret = KRB5_FCC_NOFILE;
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    key = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, 0);
 | 
						|
    if (key == -1) {
 | 
						|
	ret = ENOENT;
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = keyctl_read_krb5_data(key, &payload);
 | 
						|
    if (ret) {
 | 
						|
	_krb5_debug(context, 10, "Reading time offsets key %d: %s\n",
 | 
						|
		    key, strerror(errno));
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    sp = krb5_storage_from_data(&payload);
 | 
						|
    if (sp == NULL) {
 | 
						|
	ret = KRB5_CC_IO;
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
 | 
						|
 | 
						|
    ret = krb5_ret_int32(sp, &sec_offset);
 | 
						|
    if (ret == 0)
 | 
						|
	krb5_ret_int32(sp, &usec_offset);
 | 
						|
    if (ret) {
 | 
						|
	ret = KRB5_CC_END;
 | 
						|
	goto cleanup;
 | 
						|
    }
 | 
						|
 | 
						|
    *offset = sec_offset;
 | 
						|
 | 
						|
cleanup:
 | 
						|
    krb5_storage_free(sp);
 | 
						|
    krb5_data_free(&payload);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
struct krcc_iter {
 | 
						|
    key_serial_t collection_id;
 | 
						|
    char *anchor_name;
 | 
						|
    char *collection_name;
 | 
						|
    char *subsidiary_name;
 | 
						|
    char *primary_name;
 | 
						|
    krb5_boolean first;
 | 
						|
    long num_keys;
 | 
						|
    long next_key;
 | 
						|
    key_serial_t *keys;
 | 
						|
};
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor)
 | 
						|
{
 | 
						|
    struct krcc_iter *iter;
 | 
						|
    krb5_error_code ret;
 | 
						|
    void *keys;
 | 
						|
    long size;
 | 
						|
 | 
						|
    *cursor = NULL;
 | 
						|
 | 
						|
    iter = calloc(1, sizeof(*iter));
 | 
						|
    if (iter == NULL) {
 | 
						|
	ret = krb5_enomem(context);
 | 
						|
	goto error;
 | 
						|
    }
 | 
						|
    iter->first = TRUE;
 | 
						|
 | 
						|
    ret = get_default(context, &iter->anchor_name, &iter->collection_name,
 | 
						|
		      &iter->subsidiary_name);
 | 
						|
    if (ret)
 | 
						|
	goto error;
 | 
						|
 | 
						|
    /* If there is no default collection, return an empty cursor. */
 | 
						|
    if (iter->anchor_name == NULL) {
 | 
						|
	*cursor = iter;
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    ret = get_collection(context, iter->anchor_name, iter->collection_name,
 | 
						|
			 &iter->collection_id);
 | 
						|
    if (ret)
 | 
						|
	goto error;
 | 
						|
 | 
						|
    if (iter->subsidiary_name == NULL) {
 | 
						|
	ret = get_primary_name(context, iter->anchor_name,
 | 
						|
			       iter->collection_name, iter->collection_id,
 | 
						|
			       &iter->primary_name);
 | 
						|
	if (ret)
 | 
						|
	    goto error;
 | 
						|
 | 
						|
	size = keyctl_read_alloc(iter->collection_id, &keys);
 | 
						|
	if (size == -1) {
 | 
						|
	    ret = errno;
 | 
						|
	    goto error;
 | 
						|
	}
 | 
						|
	iter->keys = keys;
 | 
						|
	iter->num_keys = size / sizeof(key_serial_t);
 | 
						|
    }
 | 
						|
 | 
						|
    *cursor = iter;
 | 
						|
 | 
						|
    return 0;
 | 
						|
 | 
						|
error:
 | 
						|
    krcc_end_cache_get(context, iter);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_get_cache_next(krb5_context context,
 | 
						|
		    krb5_cc_cursor cursor,
 | 
						|
		    krb5_ccache *cache)
 | 
						|
{
 | 
						|
    krb5_error_code ret;
 | 
						|
    struct krcc_iter *iter = cursor;
 | 
						|
    key_serial_t key, cache_id = 0;
 | 
						|
    const char *first_name, *keytype, *sep, *subsidiary_name;
 | 
						|
    size_t keytypelen;
 | 
						|
    char *description = NULL;
 | 
						|
 | 
						|
    *cache = NULL;
 | 
						|
 | 
						|
    /* No keyring available */
 | 
						|
    if (iter->collection_id == 0)
 | 
						|
	return KRB5_CC_END;
 | 
						|
 | 
						|
    if (iter->first) {
 | 
						|
	/*
 | 
						|
	 * Look for the primary cache for a collection cursor, or the
 | 
						|
	 * subsidiary cache for a subsidiary cursor.
 | 
						|
	 */
 | 
						|
	iter->first = FALSE;
 | 
						|
	first_name = (iter->primary_name != NULL) ? iter->primary_name :
 | 
						|
		     iter->subsidiary_name;
 | 
						|
	cache_id = keyctl_search(iter->collection_id, KRCC_KEY_TYPE_KEYRING,
 | 
						|
				 first_name, 0);
 | 
						|
	if (cache_id != -1) {
 | 
						|
	    return make_cache(context, iter->collection_id, cache_id,
 | 
						|
			      iter->anchor_name, iter->collection_name,
 | 
						|
			      first_name, cache);
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    /* A subsidiary cursor yields at most the first cache. */
 | 
						|
    if (iter->subsidiary_name != NULL)
 | 
						|
	return KRB5_CC_END;
 | 
						|
 | 
						|
    keytype = KRCC_KEY_TYPE_KEYRING ";";
 | 
						|
    keytypelen = strlen(keytype);
 | 
						|
 | 
						|
    for (ret = KRB5_CC_END; iter->next_key < iter->num_keys; iter->next_key++) {
 | 
						|
	free(description);
 | 
						|
	description = NULL;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Get the key description, which should have the form:
 | 
						|
	 *   typename;UID;GID;permissions;description
 | 
						|
	 */
 | 
						|
	key = iter->keys[iter->next_key];
 | 
						|
	if (keyctl_describe_alloc(key, &description) < 0)
 | 
						|
	    continue;
 | 
						|
	sep = strrchr(description, ';');
 | 
						|
	if (sep == NULL)
 | 
						|
	    continue;
 | 
						|
	subsidiary_name = sep + 1;
 | 
						|
 | 
						|
	/* Skip this key if it isn't a keyring. */
 | 
						|
	if (strncmp(description, keytype, keytypelen) != 0)
 | 
						|
	    continue;
 | 
						|
 | 
						|
	/* Don't repeat the primary cache. */
 | 
						|
	if (strcmp(subsidiary_name, iter->primary_name) == 0)
 | 
						|
	    continue;
 | 
						|
 | 
						|
	/* We found a valid key */
 | 
						|
	iter->next_key++;
 | 
						|
	ret = make_cache(context, iter->collection_id, key, iter->anchor_name,
 | 
						|
			 iter->collection_name, subsidiary_name, cache);
 | 
						|
	break;
 | 
						|
    }
 | 
						|
 | 
						|
    free(description);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor)
 | 
						|
{
 | 
						|
    struct krcc_iter *iter = cursor;
 | 
						|
 | 
						|
    if (iter != NULL) {
 | 
						|
	free(iter->anchor_name);
 | 
						|
	free(iter->collection_name);
 | 
						|
	free(iter->subsidiary_name);
 | 
						|
	free(iter->primary_name);
 | 
						|
	free(iter->keys);
 | 
						|
 | 
						|
	memset(iter, 0, sizeof(*iter));
 | 
						|
	free(iter);
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_set_default(krb5_context context, krb5_ccache id)
 | 
						|
{
 | 
						|
    krb5_krcache *data = KRCACHE(id);
 | 
						|
    krb5_error_code ret;
 | 
						|
    char *anchor_name, *collection_name, *subsidiary_name;
 | 
						|
    key_serial_t collection_id;
 | 
						|
 | 
						|
    if (data == NULL)
 | 
						|
	return krb5_einval(context, 2);
 | 
						|
 | 
						|
    ret = parse_residual(context, data->krc_name,
 | 
						|
			 &anchor_name, &collection_name, &subsidiary_name);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = get_collection(context, anchor_name, collection_name, &collection_id);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
    ret = set_primary_name(context, collection_id, subsidiary_name);
 | 
						|
    if (ret)
 | 
						|
	goto cleanup;
 | 
						|
 | 
						|
cleanup:
 | 
						|
    free(anchor_name);
 | 
						|
    free(collection_name);
 | 
						|
    free(subsidiary_name);
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Utility routine: called by krcc_* functions to keep
 | 
						|
 * result of krcc_last_change_time up to date.
 | 
						|
 */
 | 
						|
static void
 | 
						|
update_change_time(krb5_context context, krb5_timestamp now, krb5_krcache *data)
 | 
						|
{
 | 
						|
    krb5_timestamp old;
 | 
						|
 | 
						|
    if (now == 0)
 | 
						|
	krb5_timeofday(context, &now);
 | 
						|
 | 
						|
    old = heim_base_exchange_time_t(&data->krc_changetime, now);
 | 
						|
    if (old > now) /* don't go backwards */
 | 
						|
	heim_base_exchange_time_t(&data->krc_changetime, old + 1);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
move_key_to_new_keyring(key_serial_t parent, key_serial_t key,
 | 
						|
			char *desc, int desc_len, void *data)
 | 
						|
{
 | 
						|
    key_serial_t cache_id = *(key_serial_t *)data;
 | 
						|
 | 
						|
    if (parent) {
 | 
						|
	if (keyctl_link(key, cache_id) == -1 ||
 | 
						|
	    keyctl_unlink(key, parent) == -1)
 | 
						|
	    return -1;
 | 
						|
    }
 | 
						|
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/* Move contents of one ccache to another; destroys from cache */
 | 
						|
static krb5_error_code KRB5_CALLCONV
 | 
						|
krcc_move(krb5_context context, krb5_ccache from, krb5_ccache to)
 | 
						|
{
 | 
						|
    krb5_krcache *krfrom = KRCACHE(from);
 | 
						|
    krb5_krcache *krto = KRCACHE(to);
 | 
						|
    krb5_error_code ret;
 | 
						|
    krb5_timestamp now;
 | 
						|
    key_serial_t to_cache_id;
 | 
						|
 | 
						|
    if (krfrom == NULL || krto == NULL)
 | 
						|
	return krb5_einval(context, 2);
 | 
						|
 | 
						|
    ret = initialize_internal(context, to, NULL);
 | 
						|
    if (ret)
 | 
						|
	return ret;
 | 
						|
 | 
						|
    krb5_timeofday(context, &now);
 | 
						|
    heim_base_exchange_32(&to_cache_id, krto->krc_cache_id);
 | 
						|
 | 
						|
    if (krfrom->krc_cache_id != 0) {
 | 
						|
	ret = recursive_key_scan(krfrom->krc_cache_id,
 | 
						|
				 move_key_to_new_keyring, &to_cache_id);
 | 
						|
	if (ret)
 | 
						|
	    return KRB5_CC_IO;
 | 
						|
 | 
						|
	if (keyctl_unlink(krfrom->krc_cache_id, krfrom->krc_coll_id) == -1)
 | 
						|
	    return errno;
 | 
						|
 | 
						|
	heim_base_exchange_32(&krto->krc_princ_id, krfrom->krc_princ_id);
 | 
						|
    }
 | 
						|
 | 
						|
    update_change_time(context, now, krto);
 | 
						|
    krb5_cc_destroy(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 */
 |