summaryrefslogtreecommitdiff
path: root/src/lib/krb5/ccache/cc_keyring.c
diff options
context:
space:
mode:
authorCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
committerCy Schubert <cy@FreeBSD.org>2017-07-07 17:03:42 +0000
commit33a9b234e7087f573ef08cd7318c6497ba08b439 (patch)
treed0ea40ad3bf5463a3c55795977c71bcb7d781b4b /src/lib/krb5/ccache/cc_keyring.c
Notes
Diffstat (limited to 'src/lib/krb5/ccache/cc_keyring.c')
-rw-r--r--src/lib/krb5/ccache/cc_keyring.c1755
1 files changed, 1755 insertions, 0 deletions
diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c
new file mode 100644
index 0000000000000..4fe3f0d6f1f22
--- /dev/null
+++ b/src/lib/krb5/ccache/cc_keyring.c
@@ -0,0 +1,1755 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/ccache/cc_keyring.c */
+/*
+ * Copyright (c) 2006
+ * The Regents of the University of Michigan
+ * ALL RIGHTS RESERVED
+ *
+ * Permission is granted to use, copy, create derivative works
+ * and redistribute this software and such derivative works
+ * for any purpose, so long as the name of The University of
+ * Michigan is not used in any advertising or publicity
+ * pertaining to the use of distribution of this software
+ * without specific, written prior authorization. If the
+ * above copyright notice or any other identification of the
+ * University of Michigan is included in any copy of any
+ * portion of this software, then the disclaimer below must
+ * also be included.
+ *
+ * THIS SOFTWARE IS PROVIDED AS IS, WITHOUT REPRESENTATION
+ * FROM THE UNIVERSITY OF MICHIGAN AS TO ITS FITNESS FOR ANY
+ * PURPOSE, AND WITHOUT WARRANTY BY THE UNIVERSITY OF
+ * MICHIGAN OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING
+ * WITHOUT LIMITATION THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE
+ * REGENTS OF THE UNIVERSITY OF MICHIGAN SHALL NOT BE LIABLE
+ * FOR ANY DAMAGES, INCLUDING SPECIAL, INDIRECT, INCIDENTAL, OR
+ * CONSEQUENTIAL DAMAGES, WITH RESPECT TO ANY CLAIM ARISING
+ * OUT OF OR IN CONNECTION WITH THE USE OF THE SOFTWARE, EVEN
+ * IF IT HAS BEEN OR IS HEREAFTER ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGES.
+ */
+/*
+ * Copyright 1990,1991,1992,1993,1994,2000,2004 Massachusetts Institute of
+ * Technology. All Rights Reserved.
+ *
+ * Original stdio support copyright 1995 by Cygnus Support.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original M.I.T. software.
+ * M.I.T. makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+/*
+ * 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 "cc-int.h"
+
+#ifdef USE_KEYRING_CCACHE
+
+#include <errno.h>
+#include <keyutils.h>
+
+#ifdef DEBUG
+#define KRCC_DEBUG 1
+#endif
+
+#if KRCC_DEBUG
+void debug_print(char *fmt, ...); /* prototype to silence warning */
+#include <syslog.h>
+#define DEBUG_PRINT(x) debug_print x
+void
+debug_print(char *fmt, ...)
+{
+ va_list ap;
+ va_start(ap, fmt);
+#ifdef DEBUG_STDERR
+ vfprintf(stderr, fmt, ap);
+#else
+ vsyslog(LOG_ERR, fmt, ap);
+#endif
+ va_end(ap);
+}
+#else
+#define DEBUG_PRINT(x)
+#endif
+
+/*
+ * We 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_PERSISTENT_KEYRING
+#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"
+
+typedef struct _krcc_cursor
+{
+ int numkeys;
+ int currkey;
+ key_serial_t princ_id;
+ key_serial_t offsets_id;
+ key_serial_t *keys;
+} *krcc_cursor;
+
+/*
+ * 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.
+ */
+typedef struct _krcc_data
+{
+ char *name; /* Name for this credentials cache */
+ k5_cc_mutex lock; /* synchronization */
+ key_serial_t collection_id; /* collection containing this cache keyring */
+ key_serial_t cache_id; /* keyring representing ccache */
+ key_serial_t princ_id; /* key holding principal info */
+ krb5_timestamp changetime;
+ krb5_boolean is_legacy_type;
+} krcc_data;
+
+/* Global mutex */
+k5_cc_mutex krb5int_krcc_mutex = K5_CC_MUTEX_PARTIAL_INITIALIZER;
+
+extern const krb5_cc_ops krb5_krcc_ops;
+
+static const char *KRB5_CALLCONV
+krcc_get_name(krb5_context context, krb5_ccache id);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_start_seq_get(krb5_context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor);
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor);
+
+static krb5_error_code clear_cache_keyring(krb5_context context,
+ krb5_ccache id);
+
+static krb5_error_code make_krcc_data(const char *anchor_name,
+ const char *collection_name,
+ const char *subsidiary_name,
+ key_serial_t cache_id, key_serial_t
+ collection_id, krcc_data **datapp);
+
+static krb5_error_code save_principal(krb5_context context, krb5_ccache id,
+ krb5_principal princ);
+
+static krb5_error_code save_time_offsets(krb5_context context, krb5_ccache id,
+ int32_t time_offset,
+ int32_t usec_offset);
+
+static krb5_error_code get_time_offsets(krb5_context context, krb5_ccache id,
+ int32_t *time_offset,
+ int32_t *usec_offset);
+
+static void krcc_update_change_time(krcc_data *d);
+
+/* Note the following is a stub function for Linux */
+extern krb5_error_code krb5_change_cache(void);
+
+/*
+ * 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_PERSISTENT_KEYRING
+#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()
+{
+ 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 *key_out)
+{
+ key_serial_t key;
+
+ *key_out = -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)
+ return errno;
+ if (keyctl_link(key, parent) == -1)
+ return errno;
+ } else {
+ key = add_key(KRCC_KEY_TYPE_KEYRING, name, NULL, 0, parent);
+ if (key == -1)
+ return errno;
+ }
+ }
+ *key_out = 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(const char *residual, char **anchor_name_out,
+ char **collection_name_out, char **subsidiary_name_out)
+{
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ const char *sep;
+
+ *anchor_name_out = 0;
+ *collection_name_out = NULL;
+ *subsidiary_name_out = 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 oom;
+ } else {
+ anchor_name = k5memdup0(residual, sep - residual, &ret);
+ if (anchor_name == NULL)
+ goto oom;
+ 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 oom;
+ subsidiary_name = NULL;
+ } else {
+ collection_name = k5memdup0(residual, sep - residual, &ret);
+ if (collection_name == NULL)
+ goto oom;
+ subsidiary_name = strdup(sep + 1);
+ if (subsidiary_name == NULL)
+ goto oom;
+ }
+
+ *anchor_name_out = anchor_name;
+ *collection_name_out = collection_name;
+ *subsidiary_name_out = subsidiary_name;
+ return 0;
+
+oom:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ return ENOMEM;
+}
+
+/*
+ * 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(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 **anchor_name_out,
+ char **collection_name_out, char **subsidiary_name_out)
+{
+ const char *defname;
+
+ *anchor_name_out = *collection_name_out = *subsidiary_name_out = NULL;
+ defname = krb5_cc_default_name(context);
+ if (defname == NULL || strncmp(defname, "KEYRING:", 8) != 0)
+ return 0;
+ return parse_residual(defname + 8, anchor_name_out, collection_name_out,
+ subsidiary_name_out);
+}
+
+/* Create a residual identifying a subsidiary cache. */
+static krb5_error_code
+make_subsidiary_residual(const char *anchor_name, const char *collection_name,
+ const char *subsidiary_name, char **residual_out)
+{
+ if (asprintf(residual_out, "%s:%s:%s", anchor_name, collection_name,
+ subsidiary_name) < 0) {
+ *residual_out = NULL;
+ return ENOMEM;
+ }
+ return 0;
+}
+
+/* Retrieve or create a keyring for collection_name within the anchor, and set
+ * *collection_id_out to its serial number. */
+static krb5_error_code
+get_collection(const char *anchor_name, const char *collection_name,
+ key_serial_t *collection_id_out)
+{
+ krb5_error_code ret;
+ key_serial_t persistent_id, anchor_id, possess_id = 0;
+ char *ckname, *cnend;
+ long uidnum;
+
+ *collection_id_out = 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 = 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,
+ collection_id_out);
+ }
+
+ 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 ENOMEM;
+ ret = find_or_create_keyring(anchor_id, possess_id, ckname,
+ collection_id_out);
+ 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)
+{
+ key_serial_t key;
+ uint32_t len = strlen(subsidiary_name), plen = 8 + len;
+ unsigned char *payload;
+
+ payload = malloc(plen);
+ if (payload == NULL)
+ return ENOMEM;
+ store_32_be(KRCC_COLLECTION_VERSION, payload);
+ store_32_be(len, payload + 4);
+ memcpy(payload + 8, subsidiary_name, len);
+ key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY,
+ payload, plen, collection_id);
+ free(payload);
+ return (key == -1) ? errno : 0;
+}
+
+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;
+ uint32_t len;
+
+ if (psize < 8)
+ return KRB5_CC_END;
+
+ *version = load_32_be(payload);
+ len = load_32_be(payload + 4);
+ if (len > psize - 8)
+ return KRB5_CC_END;
+ *primary = k5memdup0(payload + 8, len, &ret);
+ return (*primary == NULL) ? ret : 0;
+}
+
+/*
+ * Get or initialize the primary name within collection_id and set
+ * *subsidiary_out 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 **subsidiary_out)
+{
+ krb5_error_code ret;
+ key_serial_t primary_id, legacy;
+ void *payload = NULL;
+ int payloadlen;
+ int32_t version;
+ char *subsidiary_name = NULL;
+
+ *subsidiary_out = 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 = ENOMEM;
+ 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;
+ }
+ }
+
+ *subsidiary_out = subsidiary_name;
+ subsidiary_name = NULL;
+
+cleanup:
+ free(payload);
+ free(subsidiary_name);
+ return ret;
+}
+
+/*
+ * Create a keyring with a unique random name within collection_id. Set
+ * *subsidiary to its name and *cache_id_out to its key serial number.
+ */
+static krb5_error_code
+unique_keyring(krb5_context context, key_serial_t collection_id,
+ char **subsidiary_out, key_serial_t *cache_id_out)
+{
+ 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;
+
+ *subsidiary_out = NULL;
+ *cache_id_out = 0;
+
+ memcpy(uniquename, KRCC_NAME_PREFIX, sizeof(KRCC_NAME_PREFIX));
+ k5_cc_mutex_lock(context, &krb5int_krcc_mutex);
+
+ /* Loop until we successfully create a new ccache keyring with
+ * a unique name, or we get an error. Limit to 100 tries. */
+ tries = 100;
+ while (tries-- > 0) {
+ ret = krb5int_random_string(context, uniquename + prefixlen,
+ KRCC_NAME_RAND_CHARS);
+ if (ret)
+ goto cleanup;
+
+ key = keyctl_search(collection_id, KRCC_KEY_TYPE_KEYRING, uniquename,
+ 0);
+ if (key < 0) {
+ /* 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 < 0) {
+ ret = errno;
+ goto cleanup;
+ }
+ break;
+ }
+ }
+
+ if (tries <= 0) {
+ ret = KRB5_CC_BADNAME;
+ goto cleanup;
+ }
+
+ *subsidiary_out = strdup(uniquename);
+ if (*subsidiary_out == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ *cache_id_out = key;
+ ret = 0;
+cleanup:
+ k5_cc_mutex_unlock(context, &krb5int_krcc_mutex);
+ return ret;
+}
+
+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 *key_out)
+{
+ key_serial_t key;
+
+ *key_out = -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) {
+ *key_out = 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;
+ *key_out = key;
+ return 0;
+}
+
+static void
+update_keyring_expiration(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+ krb5_cc_cursor cursor;
+ krb5_creds creds;
+ krb5_timestamp now, 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 keys and find out the highest endtime time.
+ */
+
+ /* Find the maximum endtime of all creds in the cache. */
+ if (krcc_start_seq_get(context, id, &cursor) != 0)
+ return;
+ for (;;) {
+ if (krcc_next_cred(context, id, &cursor, &creds) != 0)
+ break;
+ if (creds.times.endtime > endtime)
+ endtime = creds.times.endtime;
+ krb5_free_cred_contents(context, &creds);
+ }
+ (void)krcc_end_seq_get(context, id, &cursor);
+
+ if (endtime == 0) /* No creds with end times */
+ return;
+
+ if (krb5_timeofday(context, &now) != 0)
+ 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(data->cache_id, timeout);
+}
+
+/* Create or overwrite the cache keyring, and set the default principal. */
+static krb5_error_code KRB5_CALLCONV
+krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krcc_data *data = (krcc_data *)id->data;
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_error_code ret;
+ const char *cache_name, *p;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ ret = clear_cache_keyring(context, id);
+ if (ret)
+ goto out;
+
+ if (!data->cache_id) {
+ /* The key didn't exist at resolve time. Check again and create the
+ * key if it still isn't there. */
+ p = strrchr(data->name, ':');
+ cache_name = (p != NULL) ? p + 1 : data->name;
+ ret = find_or_create_keyring(data->collection_id, 0, cache_name,
+ &data->cache_id);
+ if (ret)
+ goto out;
+ }
+
+ /* 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(data->name))
+ (void)keyctl_link(data->cache_id, session_write_anchor());
+
+ ret = save_principal(context, id, princ);
+
+ /* 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 (!is_legacy_cache_name(data->name) &&
+ (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+ ret = save_time_offsets(context, id, os_ctx->time_offset,
+ os_ctx->usec_offset);
+ }
+
+ if (ret == 0)
+ krb5_change_cache();
+
+out:
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Release the ccache handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_close(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_destroy(&data->lock);
+ free(data->name);
+ free(data);
+ free(id);
+ return 0;
+}
+
+/* Clear out a ccache keyring, unlinking all keys within it. Call with the
+ * mutex locked. */
+static krb5_error_code
+clear_cache_keyring(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+ int res;
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ DEBUG_PRINT(("clear_cache_keyring: cache_id %d, princ_id %d\n",
+ data->cache_id, data->princ_id));
+
+ if (data->cache_id) {
+ res = keyctl_clear(data->cache_id);
+ if (res != 0)
+ return errno;
+ }
+ data->princ_id = 0;
+ krcc_update_change_time(data);
+
+ return 0;
+}
+
+/* Destroy the cache keyring and release the handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_destroy(krb5_context context, krb5_ccache id)
+{
+ krb5_error_code ret = 0;
+ krcc_data *data = id->data;
+ int res;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ clear_cache_keyring(context, id);
+ if (data->cache_id) {
+ res = keyctl_unlink(data->cache_id, data->collection_id);
+ if (res < 0) {
+ ret = errno;
+ DEBUG_PRINT(("unlinking key %d from ring %d: %s", data->cache_id,
+ data->collection_id, error_message(errno)));
+ }
+ /* If this is a legacy cache, unlink it from the session anchor. */
+ if (is_legacy_cache_name(data->name))
+ (void)keyctl_unlink(data->cache_id, session_write_anchor());
+ }
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ k5_cc_mutex_destroy(&data->lock);
+ free(data->name);
+ free(data);
+ free(id);
+ krb5_change_cache();
+ 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_out)
+{
+ krb5_error_code ret;
+ krb5_os_context os_ctx = &context->os_context;
+ krb5_ccache ccache = NULL;
+ krcc_data *data;
+ key_serial_t pkey = 0;
+
+ /* Determine the key containing principal information, if present. */
+ pkey = keyctl_search(cache_id, KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME,
+ 0);
+ if (pkey < 0)
+ pkey = 0;
+
+ ccache = malloc(sizeof(struct _krb5_ccache));
+ if (!ccache)
+ return ENOMEM;
+
+ ret = make_krcc_data(anchor_name, collection_name, subsidiary_name,
+ cache_id, collection_id, &data);
+ if (ret) {
+ free(ccache);
+ return ret;
+ }
+
+ data->princ_id = pkey;
+ ccache->ops = &krb5_krcc_ops;
+ ccache->data = data;
+ ccache->magic = KV5M_CCACHE;
+ *cache_out = ccache;
+
+ /* Look up time offsets if necessary. */
+ if ((context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) &&
+ !(os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)) {
+ if (get_time_offsets(context, ccache, &os_ctx->time_offset,
+ &os_ctx->usec_offset) == 0) {
+ os_ctx->os_flags &= ~KRB5_OS_TOFFSET_TIME;
+ os_ctx->os_flags |= KRB5_OS_TOFFSET_VALID;
+ }
+ }
+
+ 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(residual, &anchor_name, &collection_name,
+ &subsidiary_name);
+ if (ret)
+ goto cleanup;
+ ret = get_collection(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;
+}
+
+/* Prepare for a sequential iteration over the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_start_seq_get(krb5_context context, krb5_ccache id,
+ krb5_cc_cursor *cursor)
+{
+ krcc_cursor krcursor;
+ krcc_data *data = id->data;
+ void *keys;
+ long size;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_FCC_NOFILE;
+ }
+
+ size = keyctl_read_alloc(data->cache_id, &keys);
+ if (size == -1) {
+ DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno)));
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_CC_IO;
+ }
+
+ krcursor = calloc(1, sizeof(*krcursor));
+ if (krcursor == NULL) {
+ free(keys);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_CC_NOMEM;
+ }
+
+ krcursor->princ_id = data->princ_id;
+ krcursor->offsets_id = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER,
+ KRCC_TIME_OFFSETS, 0);
+ krcursor->numkeys = size / sizeof(key_serial_t);
+ krcursor->keys = keys;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ *cursor = krcursor;
+ return 0;
+}
+
+/* Get the next credential from the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_next_cred(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor,
+ krb5_creds *creds)
+{
+ krcc_cursor krcursor;
+ krb5_error_code ret;
+ int psize;
+ void *payload = NULL;
+
+ memset(creds, 0, sizeof(krb5_creds));
+
+ /* The cursor has the entire list of keys. (Note that we don't support
+ * remove_cred.) */
+ krcursor = *cursor;
+ if (krcursor == NULL)
+ return KRB5_CC_END;
+
+ /* If we're pointing past the end of the keys array, there are no more. */
+ 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++;
+ /* Check if we have now reached the end */
+ if (krcursor->currkey >= krcursor->numkeys)
+ return KRB5_CC_END;
+ }
+
+ /* Read the key; the right size buffer will be allocated and returned. */
+ psize = keyctl_read_alloc(krcursor->keys[krcursor->currkey], &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Error reading key %d: %s\n",
+ krcursor->keys[krcursor->currkey],
+ strerror(errno)));
+ return KRB5_FCC_NOFILE;
+ }
+ krcursor->currkey++;
+
+ /* Unmarshal the credential using the file ccache version 4 format. */
+ ret = k5_unmarshal_cred(payload, psize, 4, creds);
+ free(payload);
+ return ret;
+}
+
+/* Release an iteration cursor. */
+static krb5_error_code KRB5_CALLCONV
+krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor *cursor)
+{
+ 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
+make_krcc_data(const char *anchor_name, const char *collection_name,
+ const char *subsidiary_name, key_serial_t cache_id,
+ key_serial_t collection_id, krcc_data **data_out)
+{
+ krb5_error_code ret;
+ krcc_data *data;
+
+ *data_out = NULL;
+
+ data = malloc(sizeof(krcc_data));
+ if (data == NULL)
+ return KRB5_CC_NOMEM;
+
+ ret = k5_cc_mutex_init(&data->lock);
+ if (ret) {
+ free(data);
+ return ret;
+ }
+
+ ret = make_subsidiary_residual(anchor_name, collection_name,
+ subsidiary_name, &data->name);
+ if (ret) {
+ k5_cc_mutex_destroy(&data->lock);
+ free(data);
+ return ret;
+ }
+ data->princ_id = 0;
+ data->cache_id = cache_id;
+ data->collection_id = collection_id;
+ data->changetime = 0;
+ data->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0);
+ krcc_update_change_time(data);
+
+ *data_out = data;
+ return 0;
+}
+
+/* Create a new keyring cache with a unique name. */
+static krb5_error_code KRB5_CALLCONV
+krcc_generate_new(krb5_context context, krb5_ccache *id_out)
+{
+ krb5_ccache id = NULL;
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ char *new_subsidiary_name = NULL, *new_residual = NULL;
+ krcc_data *data;
+ key_serial_t collection_id;
+ key_serial_t cache_id = 0;
+
+ *id_out = NULL;
+
+ /* 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(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name,
+ &collection_name, &subsidiary_name);
+ if (ret)
+ return ret;
+ }
+ if (subsidiary_name != NULL) {
+ k5_setmsg(context, KRB5_DCC_CANNOT_CREATE,
+ _("Can't create new subsidiary cache because default cache "
+ "is already a subsidiary"));
+ ret = KRB5_DCC_CANNOT_CREATE;
+ goto cleanup;
+ }
+
+ /* Allocate memory */
+ id = malloc(sizeof(struct _krb5_ccache));
+ if (id == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+
+ id->ops = &krb5_krcc_ops;
+
+ /* Make a unique keyring within the chosen collection. */
+ ret = get_collection(anchor_name, collection_name, &collection_id);
+ if (ret)
+ goto cleanup;
+ ret = unique_keyring(context, collection_id, &new_subsidiary_name,
+ &cache_id);
+ if (ret)
+ goto cleanup;
+
+ ret = make_krcc_data(anchor_name, collection_name, new_subsidiary_name,
+ cache_id, collection_id, &data);
+ if (ret)
+ goto cleanup;
+
+ id->data = data;
+ krb5_change_cache();
+
+cleanup:
+ free(anchor_name);
+ free(collection_name);
+ free(subsidiary_name);
+ free(new_subsidiary_name);
+ free(new_residual);
+ if (ret) {
+ free(id);
+ return ret;
+ }
+ *id_out = id;
+ return 0;
+}
+
+/* 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 ((krcc_data *)id->data)->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_out)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret;
+ void *payload = NULL;
+ int psize;
+
+ *princ_out = NULL;
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id || !data->princ_id) {
+ ret = KRB5_FCC_NOFILE;
+ k5_setmsg(context, ret, _("Credentials cache keyring '%s' not found"),
+ data->name);
+ goto errout;
+ }
+
+ psize = keyctl_read_alloc(data->princ_id, &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Reading principal key %d: %s\n",
+ data->princ_id, strerror(errno)));
+ ret = KRB5_CC_IO;
+ goto errout;
+ }
+
+ /* Unmarshal the principal using the file ccache version 4 format. */
+ ret = k5_unmarshal_princ(payload, psize, 4, princ_out);
+
+errout:
+ free(payload);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+/* Search for a credential within the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_retrieve(krb5_context context, krb5_ccache id,
+ krb5_flags whichfields, krb5_creds *mcreds,
+ krb5_creds *creds)
+{
+ return k5_cc_retrieve_cred_default(context, id, whichfields, mcreds,
+ creds);
+}
+
+/* Non-functional stub for removing a cred from the cache keyring. */
+static krb5_error_code KRB5_CALLCONV
+krcc_remove_cred(krb5_context context, krb5_ccache cache,
+ krb5_flags flags, krb5_creds *creds)
+{
+ return KRB5_CC_NOSUPP;
+}
+
+/* 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;
+}
+
+/* Get the current operational flags (of which we have none) for the cache. */
+static krb5_error_code KRB5_CALLCONV
+krcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags_out)
+{
+ *flags_out = 0;
+ 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;
+ krcc_data *data = id->data;
+ struct k5buf buf = EMPTY_K5BUF;
+ char *keyname = NULL;
+ key_serial_t cred_key;
+ krb5_timestamp now;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ k5_cc_mutex_unlock(context, &data->lock);
+ return KRB5_FCC_NOFILE;
+ }
+
+ /* Get the service principal name and use it as the key name */
+ ret = krb5_unparse_name(context, creds->server, &keyname);
+ if (ret)
+ goto errout;
+
+ /* Serialize credential using the file ccache version 4 format. */
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_cred(&buf, 4, creds);
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto errout;
+
+ /* Add new key (credentials) into keyring */
+ DEBUG_PRINT(("krcc_store: adding new key '%s' to keyring %d\n",
+ keyname, data->cache_id));
+ ret = add_cred_key(keyname, buf.data, buf.len, data->cache_id,
+ data->is_legacy_type, &cred_key);
+ if (ret)
+ goto errout;
+
+ krcc_update_change_time(data);
+
+ /* Set appropriate timeouts on cache keys. */
+ ret = krb5_timeofday(context, &now);
+ if (ret)
+ goto errout;
+
+ if (creds->times.endtime > now)
+ (void)keyctl_set_timeout(cred_key, creds->times.endtime - now);
+
+ update_keyring_expiration(context, id);
+
+errout:
+ k5_buf_free(&buf);
+ krb5_free_unparsed_name(context, keyname);
+ k5_cc_mutex_unlock(context, &data->lock);
+ 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_last_change_time(krb5_context context, krb5_ccache id,
+ krb5_timestamp *change_time)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ *change_time = data->changetime;
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+/* Lock the cache handle against other threads. (This does not lock the cache
+ * keyring against other processes.) */
+static krb5_error_code KRB5_CALLCONV
+krcc_lock(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_lock(context, &data->lock);
+ return 0;
+}
+
+/* Unlock the cache handle. */
+static krb5_error_code KRB5_CALLCONV
+krcc_unlock(krb5_context context, krb5_ccache id)
+{
+ krcc_data *data = id->data;
+
+ k5_cc_mutex_unlock(context, &data->lock);
+ return 0;
+}
+
+static krb5_error_code
+save_principal(krb5_context context, krb5_ccache id, krb5_principal princ)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret;
+ struct k5buf buf;
+ key_serial_t newkey;
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ /* Serialize princ using the file ccache version 4 format. */
+ k5_buf_init_dynamic(&buf);
+ k5_marshal_princ(&buf, 4, princ);
+ if (k5_buf_status(&buf) != 0)
+ return ENOMEM;
+
+ /* Add new key into keyring */
+#ifdef KRCC_DEBUG
+ {
+ krb5_error_code rc;
+ char *princname = NULL;
+ rc = krb5_unparse_name(context, princ, &princname);
+ DEBUG_PRINT(("save_principal: adding new key '%s' "
+ "to keyring %d for principal '%s'\n",
+ KRCC_SPEC_PRINC_KEYNAME, data->cache_id,
+ rc ? "<unknown>" : princname));
+ if (rc == 0)
+ krb5_free_unparsed_name(context, princname);
+ }
+#endif
+ newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, buf.data,
+ buf.len, data->cache_id);
+ if (newkey < 0) {
+ ret = errno;
+ DEBUG_PRINT(("Error adding principal key: %s\n", strerror(ret)));
+ } else {
+ data->princ_id = newkey;
+ ret = 0;
+ krcc_update_change_time(data);
+ }
+
+ k5_buf_free(&buf);
+ return ret;
+}
+
+/* Add a key to the cache keyring containing the given time offsets. */
+static krb5_error_code
+save_time_offsets(krb5_context context, krb5_ccache id, int32_t time_offset,
+ int32_t usec_offset)
+{
+ krcc_data *data = id->data;
+ key_serial_t newkey;
+ unsigned char payload[8];
+
+ k5_cc_mutex_assert_locked(context, &data->lock);
+
+ /* Prepare the payload. */
+ store_32_be(time_offset, payload);
+ store_32_be(usec_offset, payload + 4);
+
+ /* Add new key into keyring. */
+ newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS, payload, 8,
+ data->cache_id);
+ if (newkey == -1)
+ return errno;
+ krcc_update_change_time(data);
+ return 0;
+}
+
+/* Retrieve and parse the key in the cache keyring containing time offsets. */
+static krb5_error_code
+get_time_offsets(krb5_context context, krb5_ccache id, int32_t *time_offset,
+ int32_t *usec_offset)
+{
+ krcc_data *data = id->data;
+ krb5_error_code ret = 0;
+ key_serial_t key;
+ void *payload = NULL;
+ int psize;
+
+ k5_cc_mutex_lock(context, &data->lock);
+
+ if (!data->cache_id) {
+ ret = KRB5_FCC_NOFILE;
+ goto errout;
+ }
+
+ key = keyctl_search(data->cache_id, KRCC_KEY_TYPE_USER, KRCC_TIME_OFFSETS,
+ 0);
+ if (key == -1) {
+ ret = ENOENT;
+ goto errout;
+ }
+
+ psize = keyctl_read_alloc(key, &payload);
+ if (psize == -1) {
+ DEBUG_PRINT(("Reading time offsets key %d: %s\n",
+ key, strerror(errno)));
+ ret = KRB5_CC_IO;
+ goto errout;
+ }
+
+ if (psize < 8) {
+ ret = KRB5_CC_END;
+ goto errout;
+ }
+ *time_offset = load_32_be(payload);
+ *usec_offset = load_32_be((char *)payload + 4);
+
+errout:
+ free(payload);
+ k5_cc_mutex_unlock(context, &data->lock);
+ return ret;
+}
+
+struct krcc_ptcursor_data {
+ 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_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out)
+{
+ struct krcc_ptcursor_data *ptd;
+ krb5_cc_ptcursor cursor;
+ krb5_error_code ret;
+ void *keys;
+ long size;
+
+ *cursor_out = NULL;
+
+ cursor = k5alloc(sizeof(*cursor), &ret);
+ if (cursor == NULL)
+ return ENOMEM;
+ ptd = k5alloc(sizeof(*ptd), &ret);
+ if (ptd == NULL)
+ goto error;
+ cursor->ops = &krb5_krcc_ops;
+ cursor->data = ptd;
+ ptd->first = TRUE;
+
+ ret = get_default(context, &ptd->anchor_name, &ptd->collection_name,
+ &ptd->subsidiary_name);
+ if (ret)
+ goto error;
+
+ /* If there is no default collection, return an empty cursor. */
+ if (ptd->anchor_name == NULL) {
+ *cursor_out = cursor;
+ return 0;
+ }
+
+ ret = get_collection(ptd->anchor_name, ptd->collection_name,
+ &ptd->collection_id);
+ if (ret)
+ goto error;
+
+ if (ptd->subsidiary_name == NULL) {
+ ret = get_primary_name(context, ptd->anchor_name,
+ ptd->collection_name, ptd->collection_id,
+ &ptd->primary_name);
+ if (ret)
+ goto error;
+
+ size = keyctl_read_alloc(ptd->collection_id, &keys);
+ if (size == -1) {
+ ret = errno;
+ goto error;
+ }
+ ptd->keys = keys;
+ ptd->num_keys = size / sizeof(key_serial_t);
+ }
+
+ *cursor_out = cursor;
+ return 0;
+
+error:
+ krcc_ptcursor_free(context, &cursor);
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor,
+ krb5_ccache *cache_out)
+{
+ krb5_error_code ret;
+ struct krcc_ptcursor_data *ptd = cursor->data;
+ key_serial_t key, cache_id = 0;
+ const char *first_name, *keytype, *sep, *subsidiary_name;
+ size_t keytypelen;
+ char *description = NULL;
+
+ *cache_out = NULL;
+
+ /* No keyring available */
+ if (ptd->collection_id == 0)
+ return 0;
+
+ if (ptd->first) {
+ /* Look for the primary cache for a collection cursor, or the
+ * subsidiary cache for a subsidiary cursor. */
+ ptd->first = FALSE;
+ first_name = (ptd->primary_name != NULL) ? ptd->primary_name :
+ ptd->subsidiary_name;
+ cache_id = keyctl_search(ptd->collection_id, KRCC_KEY_TYPE_KEYRING,
+ first_name, 0);
+ if (cache_id != -1) {
+ return make_cache(context, ptd->collection_id, cache_id,
+ ptd->anchor_name, ptd->collection_name,
+ first_name, cache_out);
+ }
+ }
+
+ /* A subsidiary cursor yields at most the first cache. */
+ if (ptd->subsidiary_name != NULL)
+ return 0;
+
+ keytype = KRCC_KEY_TYPE_KEYRING ";";
+ keytypelen = strlen(keytype);
+
+ for (; ptd->next_key < ptd->num_keys; ptd->next_key++) {
+ /* Free any previously retrieved key description. */
+ free(description);
+ description = NULL;
+
+ /*
+ * Get the key description, which should have the form:
+ * typename;UID;GID;permissions;description
+ */
+ key = ptd->keys[ptd->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, ptd->primary_name) == 0)
+ continue;
+
+ /* We found a valid key */
+ ptd->next_key++;
+ ret = make_cache(context, ptd->collection_id, key, ptd->anchor_name,
+ ptd->collection_name, subsidiary_name, cache_out);
+ free(description);
+ return ret;
+ }
+
+ free(description);
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
+{
+ struct krcc_ptcursor_data *ptd = (*cursor)->data;
+
+ if (ptd != NULL) {
+ free(ptd->anchor_name);
+ free(ptd->collection_name);
+ free(ptd->subsidiary_name);
+ free(ptd->primary_name);
+ free(ptd->keys);
+ free(ptd);
+ }
+ free(*cursor);
+ *cursor = NULL;
+ return 0;
+}
+
+static krb5_error_code KRB5_CALLCONV
+krcc_switch_to(krb5_context context, krb5_ccache cache)
+{
+ krcc_data *data = cache->data;
+ krb5_error_code ret;
+ char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL;
+ key_serial_t collection_id;
+
+ ret = parse_residual(data->name, &anchor_name, &collection_name,
+ &subsidiary_name);
+ if (ret)
+ goto cleanup;
+ ret = get_collection(anchor_name, collection_name, &collection_id);
+ if (ret)
+ goto cleanup;
+ ret = set_primary_name(context, collection_id, subsidiary_name);
+
+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.
+ * Value monotonically increases -- based on but not guaranteed to be actual
+ * system time.
+ */
+
+static void
+krcc_update_change_time(krcc_data *data)
+{
+ krb5_timestamp now_time = time(NULL);
+ data->changetime = (data->changetime >= now_time) ?
+ data->changetime + 1 : now_time;
+}
+
+/*
+ * ccache implementation storing credentials in the Linux keyring facility
+ * The default is to put them at the session keyring level.
+ * If "KEYRING:process:" or "KEYRING:thread:" is specified, then they will
+ * be stored at the process or thread level respectively.
+ */
+const krb5_cc_ops krb5_krcc_ops = {
+ 0,
+ "KEYRING",
+ krcc_get_name,
+ krcc_resolve,
+ krcc_generate_new,
+ krcc_initialize,
+ krcc_destroy,
+ krcc_close,
+ krcc_store,
+ krcc_retrieve,
+ krcc_get_principal,
+ krcc_start_seq_get,
+ krcc_next_cred,
+ krcc_end_seq_get,
+ krcc_remove_cred,
+ krcc_set_flags,
+ krcc_get_flags, /* added after 1.4 release */
+ krcc_ptcursor_new,
+ krcc_ptcursor_next,
+ krcc_ptcursor_free,
+ NULL, /* move */
+ krcc_last_change_time, /* lastchange */
+ NULL, /* wasdefault */
+ krcc_lock,
+ krcc_unlock,
+ krcc_switch_to,
+};
+
+#else /* !USE_KEYRING_CCACHE */
+
+/*
+ * Export this, but it shouldn't be used.
+ */
+const krb5_cc_ops krb5_krcc_ops = {
+ 0,
+ "KEYRING",
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL, /* added after 1.4 release */
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+};
+#endif /* USE_KEYRING_CCACHE */