1
0
mirror of https://github.com/samba-team/samba.git synced 2025-01-25 06:04:04 +03:00
Joseph Sutton 51569b3152 third_party/heimdal: import lorikeet-heimdal-202203010107 (commit 0e7a12404c388e831fe6933fcc3c86e7eb334825)
NOTE: THIS COMMIT WON'T COMPILE/WORK ON ITS OWN!

BUG: https://bugzilla.samba.org/show_bug.cgi?id=14995

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Stefan Metzmacher <metze@samba.org>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2022-03-01 22:34:34 +00:00

2076 lines
55 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 heim_base_atomic(key_serial_t) atomic_key_serial_t;
typedef union _krb5_krcache_and_princ_id {
heim_base_atomic(uint64_t) krcu_cache_and_princ_id;
struct {
atomic_key_serial_t cache_id; /* keyring ID representing ccache */
atomic_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 */
char *krc_collection;
char *krc_subsidiary;
heim_base_atomic(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, atomic_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,
atomic_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,
atomic_key_serial_t *pkey)
{
key_serial_t key;
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;
}
}
heim_base_atomic_store(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;
if (residual == NULL)
residual = "";
/* 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;
} 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 ? subsidiary_name : "tkt") < 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,
atomic_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;
heim_base_atomic_init(pcollection_id, 0);
if (!anchor_name || !collection_name)
return KRB5_KCC_INVALID_ANCHOR;
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));
ids.krcu_cache_and_princ_id = heim_base_atomic_load(&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_atomic_store(&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_krcache *data = KRCACHE(id);
krb5_error_code ret;
if (data == NULL)
return krb5_einval(context, 2);
if (princ == NULL)
return KRB5_CC_BADNAME;
ret = initialize_internal(context, id, princ);
if (ret == 0)
update_change_time(context, 0, data);
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_subsidiary);
free(data->krc_collection);
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,
atomic_key_serial_t *pcache_id)
{
int res;
key_serial_t cache_id = heim_base_atomic_load(pcache_id);
_krb5_debug(context, 10, "clear_cache_keyring: cache_id %d\n", cache_id);
if (cache_id != 0) {
res = keyctl_clear(cache_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;
heim_base_atomic_store(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());
}
heim_base_atomic_store(&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_2(krb5_context context,
krb5_ccache *id,
const char *residual,
const char *sub)
{
krb5_error_code ret;
atomic_key_serial_t collection_id;
key_serial_t 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;
if (sub) {
free(subsidiary_name);
if ((subsidiary_name = strdup(sub)) == NULL) {
ret = krb5_enomem(context);
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);
cache_id = heim_base_atomic_load(&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;
}
krcursor->princ_id = heim_base_atomic_load(&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 ||
(data->krc_collection = strdup(collection_name)) == NULL ||
(data->krc_subsidiary = strdup(subsidiary_name ? subsidiary_name : "tkt")) == NULL) {
if (data) {
free(data->krc_collection);
free(data->krc_name);
}
free(data);
if (ret == 0)
ret = krb5_enomem(context);
return ret;
}
heim_base_atomic_init(&data->krc_princ_id, 0);
heim_base_atomic_init(&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;
atomic_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 krb5_error_code KRB5_CALLCONV
krcc_get_name_2(krb5_context context,
krb5_ccache id,
const char **name,
const char **collection_name,
const char **subsidiary_name)
{
krb5_krcache *data = KRCACHE(id);
if (data == NULL)
return krb5_einval(context, 2);
if (name)
*name = data->krc_name;
if (collection_name)
*collection_name = data->krc_collection;
if (subsidiary_name)
*subsidiary_name = data->krc_subsidiary;
return 0;
}
/* 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));
ids.krcu_cache_and_princ_id = heim_base_atomic_load(&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));
ids.krcu_cache_and_princ_id = heim_base_atomic_load(&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);
cache_id = heim_base_atomic_load(&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);
*change_time = heim_base_atomic_load(&data->krc_changetime);
return 0;
}
static krb5_error_code
save_principal(krb5_context context,
key_serial_t cache_id,
krb5_const_principal princ,
atomic_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;
heim_base_atomic_store(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;
if (data == NULL)
return krb5_einval(context, 2);
cache_id = heim_base_atomic_load(&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 = 0;
if (data == NULL)
return krb5_einval(context, 2);
krb5_data_zero(&payload);
cache_id = heim_base_atomic_load(&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_enomem(context);;
goto cleanup;
}
krb5_storage_set_byteorder(sp, KRB5_STORAGE_BYTEORDER_BE);
ret = krb5_ret_int32(sp, &sec_offset);
/*
* We can't output nor use the usec_offset here, so we don't bother to read
* it, though we do write it.
*/
cleanup:
*offset = sec_offset;
krb5_storage_free(sp);
krb5_data_free(&payload);
return ret;
}
struct krcc_iter {
atomic_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 (iter->primary_name &&
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;
atomic_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_atomic_store(&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);
to_cache_id = heim_base_atomic_load(&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_5,
"KEYRING",
NULL,
NULL,
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,
krcc_get_name_2,
krcc_resolve_2
};
#endif /* HAVE_KEYUTILS_H */