Pared down from the git commits, with a local copy of k5memdup0() added in to cc_keyring, and a wrapper 'run' in to k5test.py. diff --git a/src/aclocal.m4 b/src/aclocal.m4 index 2c17e46..7be77c2 100644 --- a/src/aclocal.m4 +++ b/src/aclocal.m4 @@ -89,6 +89,7 @@ KRB5_AC_INITFINI KRB5_AC_ENABLE_THREADS KRB5_AC_FIND_DLOPEN KRB5_AC_KEYRING_CCACHE +KRB5_AC_PERSISTENT_KEYRING ])dnl dnl Maintainer mode, akin to what automake provides, 'cept we don't @@ -1664,3 +1659,12 @@ AC_DEFUN(KRB5_AC_KEYRING_CCACHE,[ ])) ])dnl dnl +dnl If libkeyutils supports persistent keyrings, use them +AC_DEFUN(KRB5_AC_PERSISTENT_KEYRING,[ + AC_CHECK_HEADERS([keyutils.h], + AC_CHECK_LIB(keyutils, keyctl_get_persistent, + [AC_DEFINE(HAVE_PERSISTENT_KEYRING, 1, + [Define if persistent keyrings are supported]) + ])) +])dnl +dnl diff --git a/src/lib/krb5/error_tables/k5e1_err.et b/src/lib/krb5/error_tables/k5e1_err.et index 98374ed..071b7f2 100644 --- a/src/lib/krb5/error_tables/k5e1_err.et +++ b/src/lib/krb5/error_tables/k5e1_err.et @@ -35,4 +35,7 @@ error_code KRB5_PLUGIN_BAD_MODULE_SPEC, "Invalid module specifier" error_code KRB5_PLUGIN_NAME_NOTFOUND, "Plugin module name not found" error_code KRB5KDC_ERR_DISCARD, "The KDC should discard this request" error_code KRB5_DCC_CANNOT_CREATE, "Can't create new subsidiary cache" +error_code KRB5_KCC_INVALID_ANCHOR, "Invalid keyring anchor name" +error_code KRB5_KCC_UNKNOWN_VERSION, "Unknown keyring collection version" +error_code KRB5_KCC_INVALID_UID, "Invalid UID in persistent keyring name" end diff --git a/src/lib/krb5/ccache/cc_keyring.c b/src/lib/krb5/ccache/cc_keyring.c index fd1bcec..795ccd6 100644 --- a/src/lib/krb5/ccache/cc_keyring.c +++ b/src/lib/krb5/ccache/cc_keyring.c @@ -56,17 +56,42 @@ */ /* - * Implementation of a credentials cache stored in the Linux keyring facility + * This file implements a collection-enabled credential cache type where the + * credentials are stored in the Linux keyring facility. * - * Some assumptions: + * A residual of this type can have three forms: + * anchor:collection:subsidiary + * anchor:collection + * collection * - * - A credentials cache "file" == a keyring with separate keys - * for the information in the ccache (see below) - * - A credentials cache keyring will contain only keys, - * not other keyrings - * - Each Kerberos ticket will have its own key within the ccache keyring - * - The principal information for the ccache is stored in a - * special key, which is not counted in the 'numkeys' count + * The anchor name is "process", "thread", or "legacy" and determines where we + * search for keyring collections. In the third form, the anchor name is + * presumed to be "legacy". The anchor keyring for legacy caches is the + * session keyring. + * + * If the subsidiary name is present, the residual identifies a single cache + * within a collection. Otherwise, the residual identifies the collection + * itself. When a residual identifying a collection is resolved, the + * collection's primary key is looked up (or initialized, using the collection + * name as the subsidiary name), and the resulting cache's name will use the + * first name form and will identify the primary cache. + * + * Keyring collections are named "_krb_" and are linked from the + * anchor keyring. The keys within a keyring collection are links to cache + * keyrings, plus a link to one user key named "krb_ccache:primary" which + * contains a serialized representation of the collection version (currently 1) + * and the primary name of the collection. + * + * Cache keyrings contain one user key per credential which contains a + * serialized representation of the credential. There is also one user key + * named "__krb5_princ__" which contains a serialized representation of the + * cache's default principal. + * + * If the anchor name is "legacy", then the initial primary cache (the one + * named with the collection name) is also linked to the session keyring, and + * we look for a cache in that location when initializing the collection. This + * extra link allows that cache to be visible to old versions of the KEYRING + * cache type, and allows us to see caches created by that code. */ #include "cc-int.h" @@ -101,7 +126,20 @@ debug_print(char *fmt, ...) #endif /* - * We always use "user" key type + * 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" @@ -117,20 +155,6 @@ debug_print(char *fmt, ...) #define KRCC_SPEC_PRINC_KEYNAME "__krb5_princ__" /* - * XXX The following two really belong in some external - * header since outside programs will need to use these - * same names. - */ -/* - * Special name for key to communicate key serial numbers - * This is used by the Linux gssd process to pass the - * user's keyring values it gets in an upcall. - * The format of the contents should be - * :: - */ -#define KRCC_SPEC_IDS_KEYNAME "_gssd_keyring_ids_" - -/* * Special name for the key to communicate the name(s) * of credentials caches to be used for requests. * This should currently contain a single name, but @@ -139,26 +163,55 @@ debug_print(char *fmt, ...) */ #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" + +/* + * 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" + #define KRB5_OK 0 /* Hopefully big enough to hold a serialized credential */ -#define GUESS_CRED_SIZE 4096 - -#define ALLOC(NUM,TYPE) \ - (((NUM) <= (((size_t)0-1)/ sizeof(TYPE))) \ - ? (TYPE *) calloc((NUM), sizeof(TYPE)) \ - : (errno = ENOMEM,(TYPE *) 0)) +#define MAX_CRED_SIZE (1024*1024) #define CHECK_N_GO(ret, errdest) if (ret != KRB5_OK) goto errdest #define CHECK(ret) if (ret != KRB5_OK) goto errout #define CHECK_OUT(ret) if (ret != KRB5_OK) return ret -typedef struct krb5_krcc_ring_ids { - key_serial_t session; - key_serial_t process; - key_serial_t thread; -} krb5_krcc_ring_ids_t; - typedef struct _krb5_krcc_cursor { int numkeys; @@ -169,7 +222,7 @@ typedef struct _krb5_krcc_cursor /* * This represents a credentials cache "file" - * where ring_id is the keyring serial number for + * where cache_id is the keyring serial number for * this credentials cache "file". Each key * in the keyring contains a separate key. */ @@ -177,12 +230,11 @@ typedef struct _krb5_krcc_data { char *name; /* Name for this credentials cache */ k5_cc_mutex lock; /* synchronization */ - key_serial_t parent_id; /* parent keyring of this ccache keyring */ - key_serial_t ring_id; /* keyring representing ccache */ + 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 */ - int numkeys; /* # of keys in this ring - * (does NOT include principal info) */ krb5_timestamp changetime; + krb5_boolean is_legacy_type; } krb5_krcc_data; /* Passed internally to assure we don't go past the bounds of our buffer */ @@ -190,6 +242,7 @@ typedef struct _krb5_krcc_buffer_cursor { char *bpp; char *endp; + size_t size; /* For dry-run length calculation */ } krb5_krcc_bc; /* Global mutex */ @@ -258,6 +311,29 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_lock static krb5_error_code KRB5_CALLCONV krb5_krcc_unlock (krb5_context context, krb5_ccache id); +static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_new +(krb5_context context, krb5_cc_ptcursor *cursor_out); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_next +(krb5_context context, krb5_cc_ptcursor cursor, krb5_ccache *cache_out); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_ptcursor_free +(krb5_context context, krb5_cc_ptcursor *cursor); + +static krb5_error_code KRB5_CALLCONV krb5_krcc_switch_to +(krb5_context context, krb5_ccache cache); + +/* Like k5memdup, but add a final null byte. */ +static inline void * +k5memdup0(const void *in, size_t len, krb5_error_code *code) +{ + void *ptr = k5alloc(len + 1, code); + + if (ptr != NULL && len > 0) + memcpy(ptr, in, len); + return ptr; +} + /* * Internal utility functions */ @@ -266,8 +331,9 @@ static krb5_error_code krb5_krcc_clearcache (krb5_context context, krb5_ccache id); static krb5_error_code krb5_krcc_new_data -(const char *, key_serial_t ring, key_serial_t parent_ring, - krb5_krcc_data **); +(const char *anchor_name, const char *collection_name, + const char *subsidiary_name, key_serial_t cache_id, + key_serial_t collection_id, krb5_krcc_data **datapp); static krb5_error_code krb5_krcc_save_principal (krb5_context context, krb5_ccache id, krb5_principal princ); @@ -275,100 +341,480 @@ static krb5_error_code krb5_krcc_save_principal static krb5_error_code krb5_krcc_retrieve_principal (krb5_context context, krb5_ccache id, krb5_principal * princ); -static int krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p); - /* Routines to parse a key from a keyring into a cred structure */ static krb5_error_code krb5_krcc_parse -(krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len, - krb5_krcc_bc * bc); +(krb5_context, krb5_pointer buf, unsigned int len, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_cred -(krb5_context context, krb5_ccache id, krb5_creds * creds, - char *payload, int psize); +(krb5_context context, krb5_creds * creds, char *payload, int psize); static krb5_error_code krb5_krcc_parse_principal -(krb5_context context, krb5_ccache id, krb5_principal * princ, - krb5_krcc_bc * bc); +(krb5_context context, krb5_principal * princ, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_keyblock -(krb5_context context, krb5_ccache id, krb5_keyblock * keyblock, - krb5_krcc_bc * bc); +(krb5_context context, krb5_keyblock * keyblock, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_times -(krb5_context context, krb5_ccache id, krb5_ticket_times * t, - krb5_krcc_bc * bc); +(krb5_context context, krb5_ticket_times * t, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_krb5data -(krb5_context context, krb5_ccache id, krb5_data * data, - krb5_krcc_bc * bc); +(krb5_context context, krb5_data * data, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_int32 -(krb5_context context, krb5_ccache id, krb5_int32 * i, krb5_krcc_bc * bc); +(krb5_context context, krb5_int32 * i, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_octet -(krb5_context context, krb5_ccache id, krb5_octet * octet, - krb5_krcc_bc * bc); +(krb5_context context, krb5_octet * octet, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_addrs -(krb5_context context, krb5_ccache id, krb5_address *** a, - krb5_krcc_bc * bc); +(krb5_context context, krb5_address *** a, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_addr -(krb5_context context, krb5_ccache id, krb5_address * a, - krb5_krcc_bc * bc); +(krb5_context context, krb5_address * a, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_authdata -(krb5_context context, krb5_ccache id, krb5_authdata *** ad, - krb5_krcc_bc * bc); +(krb5_context context, krb5_authdata *** ad, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_authdatum -(krb5_context context, krb5_ccache id, krb5_authdata * ad, - krb5_krcc_bc * bc); +(krb5_context context, krb5_authdata * ad, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_parse_ui_2 -(krb5_context, krb5_ccache id, krb5_ui_2 * i, krb5_krcc_bc * bc); +(krb5_context, krb5_ui_2 * i, krb5_krcc_bc * bc); /* Routines to unparse a cred structure into keyring key */ static krb5_error_code krb5_krcc_unparse -(krb5_context, krb5_ccache id, krb5_pointer buf, unsigned int len, - krb5_krcc_bc * bc); -static krb5_error_code krb5_krcc_unparse_cred -(krb5_context context, krb5_ccache id, krb5_creds * creds, +(krb5_context, krb5_pointer buf, unsigned int len, krb5_krcc_bc * bc); +static krb5_error_code krb5_krcc_unparse_cred_alloc +(krb5_context context, krb5_creds * creds, char **datapp, unsigned int *lenptr); +static krb5_error_code krb5_krcc_unparse_cred +(krb5_context context, krb5_creds * creds, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_principal -(krb5_context, krb5_ccache id, krb5_principal princ, krb5_krcc_bc * bc); +(krb5_context, krb5_principal princ, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_keyblock -(krb5_context, krb5_ccache id, krb5_keyblock * keyblock, - krb5_krcc_bc * bc); +(krb5_context, krb5_keyblock * keyblock, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_times -(krb5_context, krb5_ccache id, krb5_ticket_times * t, krb5_krcc_bc * bc); +(krb5_context, krb5_ticket_times * t, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_krb5data -(krb5_context, krb5_ccache id, krb5_data * data, krb5_krcc_bc * bc); +(krb5_context, krb5_data * data, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_int32 -(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); +(krb5_context, krb5_int32 i, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_octet -(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); +(krb5_context, krb5_int32 i, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_addrs -(krb5_context, krb5_ccache, krb5_address ** a, krb5_krcc_bc * bc); +(krb5_context, krb5_address ** a, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_addr -(krb5_context, krb5_ccache, krb5_address * a, krb5_krcc_bc * bc); +(krb5_context, krb5_address * a, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_authdata -(krb5_context, krb5_ccache, krb5_authdata ** ad, krb5_krcc_bc * bc); +(krb5_context, krb5_authdata ** ad, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_authdatum -(krb5_context, krb5_ccache, krb5_authdata * ad, krb5_krcc_bc * bc); +(krb5_context, krb5_authdata * ad, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_ui_4 -(krb5_context, krb5_ccache id, krb5_ui_4 i, krb5_krcc_bc * bc); +(krb5_context, krb5_ui_4 i, krb5_krcc_bc * bc); static krb5_error_code krb5_krcc_unparse_ui_2 -(krb5_context, krb5_ccache id, krb5_int32 i, krb5_krcc_bc * bc); +(krb5_context, krb5_int32 i, krb5_krcc_bc * bc); static void krb5_krcc_update_change_time (krb5_krcc_data *); +static krb5_error_code +krb5_krcc_parse_index(krb5_context context, krb5_int32 *version, + char **primary, void *payload, int psize); +static krb5_error_code +krb5_krcc_unparse_index(krb5_context context, krb5_int32 version, + const char *primary, void **datapp, int *lenptr); + /* Note the following is a stub function for Linux */ extern krb5_error_code krb5_change_cache(void); /* - * Determine how many keys exist in a ccache keyring. - * Subtracts out the "hidden" key holding the principal information. + * 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 + +/* + * 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; + 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, NULL, 10); + if (errno) + 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 = KEY_SPEC_SESSION_KEYRING; + } 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 = KEY_SPEC_SESSION_KEYRING; + } 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) +{ + krb5_error_code ret; + key_serial_t key; + void *payload = NULL; + int payloadlen; + + ret = krb5_krcc_unparse_index(context, KRCC_COLLECTION_VERSION, + subsidiary_name, &payload, &payloadlen); + if (ret) + return ret; + key = add_key(KRCC_KEY_TYPE_USER, KRCC_COLLECTION_PRIMARY, + payload, payloadlen, collection_id); + free(payload); + return (key == -1) ? errno : 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; + krb5_int32 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 = krb5_krcc_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 int KRB5_CALLCONV -krb5_krcc_getkeycount(key_serial_t cred_ring) +static krb5_error_code +unique_keyring(krb5_context context, key_serial_t collection_id, + char **subsidiary_out, key_serial_t *cache_id_out) { - int res, nkeys; + 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; + } + } - res = keyctl_read(cred_ring, NULL, 0); - if (res > 0) - nkeys = (res / sizeof(key_serial_t)) - 1; - else - nkeys = 0; - return(nkeys); + 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 = KRB5_OK; +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; + + 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) + 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); + return (key == -1) ? errno : 0; } /* @@ -388,24 +834,40 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ) { + krb5_krcc_data *data = (krb5_krcc_data *)id->data; krb5_error_code kret; + const char *cache_name, *p; DEBUG_PRINT(("krb5_krcc_initialize: entered\n")); - kret = k5_cc_mutex_lock(context, &((krb5_krcc_data *) id->data)->lock); - if (kret) - return kret; + k5_cc_mutex_lock(context, &data->lock); kret = krb5_krcc_clearcache(context, id); if (kret != KRB5_OK) 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; + kret = find_or_create_keyring(data->collection_id, 0, cache_name, + &data->cache_id); + if (kret) + 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, KEY_SPEC_SESSION_KEYRING); + kret = krb5_krcc_save_principal(context, id, princ); if (kret == KRB5_OK) krb5_change_cache(); out: - k5_cc_mutex_unlock(context, &((krb5_krcc_data *) id->data)->lock); + k5_cc_mutex_unlock(context, &data->lock); return kret; } @@ -460,14 +922,14 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id) d = (krb5_krcc_data *) id->data; - DEBUG_PRINT(("krb5_krcc_clearcache: ring_id %d, princ_id %d, " - "numkeys is %d\n", d->ring_id, d->princ_id, d->numkeys)); + DEBUG_PRINT(("krb5_krcc_clearcache: cache_id %d, princ_id %d\n", + d->cache_id, d->princ_id)); - res = keyctl_clear(d->ring_id); - if (res != 0) { - return errno; + if (d->cache_id) { + res = keyctl_clear(d->cache_id); + if (res != 0) + return errno; } - d->numkeys = 0; d->princ_id = 0; krb5_krcc_update_change_time(d); @@ -484,7 +946,7 @@ krb5_krcc_clearcache(krb5_context context, krb5_ccache id) static krb5_error_code KRB5_CALLCONV krb5_krcc_destroy(krb5_context context, krb5_ccache id) { - krb5_error_code kret; + krb5_error_code kret = 0; krb5_krcc_data *d; int res; @@ -492,30 +954,67 @@ krb5_krcc_destroy(krb5_context context, krb5_ccache id) d = (krb5_krcc_data *) id->data; - kret = k5_cc_mutex_lock(context, &d->lock); - if (kret) - return kret; + k5_cc_mutex_lock(context, &d->lock); krb5_krcc_clearcache(context, id); - free(d->name); - res = keyctl_unlink(d->ring_id, d->parent_id); - if (res < 0) { - kret = errno; - DEBUG_PRINT(("krb5_krcc_destroy: unlinking key %d from ring %d: %s", - d->ring_id, d->parent_id, error_message(errno))); - goto cleanup; + if (d->cache_id) { + res = keyctl_unlink(d->cache_id, d->collection_id); + if (res < 0) { + kret = errno; + DEBUG_PRINT(("unlinking key %d from ring %d: %s", + d->cache_id, d->collection_id, error_message(errno))); + } + /* If this is a legacy cache, unlink it from the session anchor. */ + if (is_legacy_cache_name(d->name)) + (void)keyctl_unlink(d->cache_id, KEY_SPEC_SESSION_KEYRING); } -cleanup: + k5_cc_mutex_unlock(context, &d->lock); k5_cc_mutex_destroy(&d->lock); + free(d->name); free(d); free(id); krb5_change_cache(); - return KRB5_OK; + return kret; } +/* Create a cache handle for a cache ID. */ +static krb5_error_code +make_cache(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_ccache ccache = NULL; + krb5_krcc_data *d; + 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 = krb5_krcc_new_data(anchor_name, collection_name, subsidiary_name, + cache_id, collection_id, &d); + if (ret) { + free(ccache); + return ret; + } + + d->princ_id = pkey; + ccache->ops = &krb5_krcc_ops; + ccache->data = d; + ccache->magic = KV5M_CCACHE; + *cache_out = ccache; + return 0; +} /* * Requires: @@ -538,101 +1037,42 @@ cleanup: */ static krb5_error_code KRB5_CALLCONV -krb5_krcc_resolve(krb5_context context, krb5_ccache * id, const char *full_residual) +krb5_krcc_resolve(krb5_context context, krb5_ccache *id, const char *residual) { - krb5_ccache lid; - krb5_error_code kret; - krb5_krcc_data *d; - key_serial_t key; - key_serial_t pkey = 0; - int nkeys = 0; - int res; - krb5_krcc_ring_ids_t ids; - key_serial_t ring_id; - const char *residual; + krb5_error_code ret; + key_serial_t collection_id, cache_id; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; - DEBUG_PRINT(("krb5_krcc_resolve: entered with name '%s'\n", - full_residual)); + 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; - res = krb5_krcc_get_ring_ids(&ids); - if (res) { - kret = EINVAL; - DEBUG_PRINT(("krb5_krcc_resolve: Error getting ring id values!\n")); - return kret; + if (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; } - if (strncmp(full_residual, "thread:", 7) == 0) { - residual = full_residual + 7; - ring_id = ids.thread; - } else if (strncmp(full_residual, "process:", 8) == 0) { - residual = full_residual + 8; - ring_id = ids.process; - } else { - residual = full_residual; - ring_id = ids.session; - } + /* 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; - DEBUG_PRINT(("krb5_krcc_resolve: searching ring %d for residual '%s'\n", - ring_id, residual)); + ret = make_cache(collection_id, cache_id, anchor_name, collection_name, + subsidiary_name, id); - /* - * Use keyctl_search instead of request_key. If we're supposed - * to be looking for a process ccache, we shouldn't find a - * thread ccache. - * XXX But should we look in the session ring if we don't find it - * in the process ring? Same goes for thread. Should we look in - * the process and session rings if not found in the thread ring? - * - */ - key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, residual, 0); - if (key < 0) { - key = add_key(KRCC_KEY_TYPE_KEYRING, residual, NULL, 0, ring_id); - if (key < 0) { - kret = errno; - DEBUG_PRINT(("krb5_krcc_resolve: Error adding new " - "keyring '%s': %s\n", residual, strerror(errno))); - return kret; - } - DEBUG_PRINT(("krb5_krcc_resolve: new keyring '%s', " - "key %d, added to keyring %d\n", - residual, key, ring_id)); - } else { - DEBUG_PRINT(("krb5_krcc_resolve: found existing " - "key %d, with name '%s' in keyring %d\n", - key, residual, ring_id)); - /* Determine key containing principal information */ - pkey = keyctl_search(key, KRCC_KEY_TYPE_USER, - KRCC_SPEC_PRINC_KEYNAME, 0); - if (pkey < 0) { - DEBUG_PRINT(("krb5_krcc_resolve: Error locating principal " - "info for existing ccache in ring %d: %s\n", - key, strerror(errno))); - pkey = 0; - } - /* Determine how many keys exist */ - nkeys = krb5_krcc_getkeycount(key); - } - - lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); - if (lid == NULL) - return KRB5_CC_NOMEM; - - - kret = krb5_krcc_new_data(residual, key, ring_id, &d); - if (kret) { - free(lid); - return kret; - } - - DEBUG_PRINT(("krb5_krcc_resolve: ring_id %d, princ_id %d, " - "nkeys %d\n", key, pkey, nkeys)); - d->princ_id = pkey; - d->numkeys = nkeys; - lid->ops = &krb5_krcc_ops; - lid->data = d; - lid->magic = KV5M_CCACHE; - *id = lid; - return KRB5_OK; +cleanup: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + return ret; } /* @@ -653,47 +1093,37 @@ krb5_krcc_start_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor * cursor) { krb5_krcc_cursor krcursor; - krb5_error_code kret; krb5_krcc_data *d; - unsigned int size; - int res; + void *keys; + long size; DEBUG_PRINT(("krb5_krcc_start_seq_get: entered\n")); d = id->data; - kret = k5_cc_mutex_lock(context, &d->lock); - if (kret) - return kret; - - /* - * Determine how many keys currently exist and update numkeys. - * We cannot depend on the current value of numkeys because - * the ccache may have been updated elsewhere - */ - d->numkeys = krb5_krcc_getkeycount(d->ring_id); + k5_cc_mutex_lock(context, &d->lock); - size = sizeof(*krcursor) + ((d->numkeys + 1) * sizeof(key_serial_t)); - - krcursor = (krb5_krcc_cursor) malloc(size); - if (krcursor == NULL) { + if (!d->cache_id) { k5_cc_mutex_unlock(context, &d->lock); - return KRB5_CC_NOMEM; + return KRB5_FCC_NOFILE; } - krcursor->keys = (key_serial_t *) ((char *) krcursor + sizeof(*krcursor)); - res = keyctl_read(d->ring_id, (char *) krcursor->keys, - ((d->numkeys + 1) * sizeof(key_serial_t))); - if (res < 0 || res > ((d->numkeys + 1) * sizeof(key_serial_t))) { - DEBUG_PRINT(("Read %d bytes from keyring, numkeys %d: %s\n", - res, d->numkeys, strerror(errno))); - free(krcursor); + size = keyctl_read_alloc(d->cache_id, &keys); + if (size == -1) { + DEBUG_PRINT(("Error getting from keyring: %s\n", strerror(errno))); k5_cc_mutex_unlock(context, &d->lock); return KRB5_CC_IO; } - krcursor->numkeys = d->numkeys; - krcursor->currkey = 0; + krcursor = calloc(1, sizeof(*krcursor)); + if (krcursor == NULL) { + free(keys); + k5_cc_mutex_unlock(context, &d->lock); + return KRB5_CC_NOMEM; + } + krcursor->princ_id = d->princ_id; + krcursor->numkeys = size / sizeof(key_serial_t); + krcursor->keys = keys; k5_cc_mutex_unlock(context, &d->lock); *cursor = (krb5_cc_cursor) krcursor; @@ -741,14 +1171,14 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id, memset(creds, 0, sizeof(krb5_creds)); /* If we're pointing past the end of the keys array, there are no more */ - if (krcursor->currkey > krcursor->numkeys) + if (krcursor->currkey >= krcursor->numkeys) return KRB5_CC_END; /* If we're pointing at the entry with the principal, skip it */ if (krcursor->keys[krcursor->currkey] == krcursor->princ_id) { krcursor->currkey++; /* Check if we have now reached the end */ - if (krcursor->currkey > krcursor->numkeys) + if (krcursor->currkey >= krcursor->numkeys) return KRB5_CC_END; } @@ -763,7 +1193,7 @@ krb5_krcc_next_cred(krb5_context context, krb5_ccache id, } krcursor->currkey++; - kret = krb5_krcc_parse_cred(context, id, creds, payload, psize); + kret = krb5_krcc_parse_cred(context, creds, payload, psize); freepayload: if (payload) free(payload); @@ -787,19 +1217,24 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_end_seq_get(krb5_context context, krb5_ccache id, krb5_cc_cursor * cursor) { + krb5_krcc_cursor krcursor = (krb5_krcc_cursor)*cursor; DEBUG_PRINT(("krb5_krcc_end_seq_get: entered\n")); - free(*cursor); - *cursor = 0L; + if (krcursor != NULL) { + free(krcursor->keys); + free(krcursor); + } + *cursor = NULL; return KRB5_OK; } /* Utility routine: Creates the back-end data for a keyring cache. Call with the global list lock held. */ -static krb5_error_code -krb5_krcc_new_data(const char *name, key_serial_t ring, - key_serial_t parent_ring, krb5_krcc_data ** datapp) +static krb5_error_code +krb5_krcc_new_data(const char *anchor_name, const char *collection_name, + const char *subsidiary_name, key_serial_t cache_id, + key_serial_t collection_id, krb5_krcc_data **datapp) { krb5_error_code kret; krb5_krcc_data *d; @@ -814,17 +1249,18 @@ krb5_krcc_new_data(const char *name, key_serial_t ring, return kret; } - d->name = strdup(name); - if (d->name == NULL) { + kret = make_subsidiary_residual(anchor_name, collection_name, + subsidiary_name, &d->name); + if (kret) { k5_cc_mutex_destroy(&d->lock); free(d); - return KRB5_CC_NOMEM; + return kret; } d->princ_id = 0; - d->ring_id = ring; - d->parent_id = parent_ring; - d->numkeys = 0; + d->cache_id = cache_id; + d->collection_id = collection_id; d->changetime = 0; + d->is_legacy_type = (strcmp(anchor_name, KRCC_LEGACY_ANCHOR) == 0); krb5_krcc_update_change_time(d); *datapp = d; @@ -846,82 +1282,73 @@ krb5_krcc_new_data(const char *name, key_serial_t ring, static krb5_error_code KRB5_CALLCONV krb5_krcc_generate_new(krb5_context context, krb5_ccache * id) { - krb5_ccache lid; - char uniquename[8]; + krb5_ccache lid = NULL; krb5_error_code kret; + char *anchor_name = NULL, *collection_name = NULL, *subsidiary_name = NULL; + char *new_subsidiary_name = NULL, *new_residual = NULL; krb5_krcc_data *d; - key_serial_t ring_id = KEY_SPEC_SESSION_KEYRING; - key_serial_t key; + key_serial_t collection_id; + key_serial_t cache_id = 0; DEBUG_PRINT(("krb5_krcc_generate_new: entered\n")); + /* Determine the collection in which we will create the cache.*/ + kret = get_default(context, &anchor_name, &collection_name, + &subsidiary_name); + if (kret) + return kret; + if (anchor_name == NULL) { + kret = parse_residual(KRCC_DEFAULT_UNIQUE_COLLECTION, &anchor_name, + &collection_name, &subsidiary_name); + if (kret) + return kret; + } + if (subsidiary_name != NULL) { + krb5_set_error_message(context, KRB5_DCC_CANNOT_CREATE, + _("Can't create new subsidiary cache because " + "default cache is already a subsdiary")); + kret = KRB5_DCC_CANNOT_CREATE; + goto cleanup; + } + /* Allocate memory */ lid = (krb5_ccache) malloc(sizeof(struct _krb5_ccache)); - if (lid == NULL) - return KRB5_CC_NOMEM; + if (lid == NULL) { + kret = ENOMEM; + goto cleanup; + } lid->ops = &krb5_krcc_ops; - kret = k5_cc_mutex_lock(context, &krb5int_krcc_mutex); - if (kret) { - free(lid); - return kret; - } - -/* XXX These values are platform-specific and should not be here! */ -/* XXX There is a bug in FC5 where these are not included in errno.h */ -#ifndef ENOKEY -#define ENOKEY 126 /* Required key not available */ -#endif -#ifndef EKEYEXPIRED -#define EKEYEXPIRED 127 /* Key has expired */ -#endif -#ifndef EKEYREVOKED -#define EKEYREVOKED 128 /* Key has been revoked */ -#endif -#ifndef EKEYREJECTED -#define EKEYREJECTED 129 /* Key was rejected by service */ -#endif + /* Make a unique keyring within the chosen collection. */ + kret = get_collection(anchor_name, collection_name, &collection_id); + if (kret) + goto cleanup; + kret = unique_keyring(context, collection_id, &new_subsidiary_name, + &cache_id); + if (kret) + goto cleanup; - /* - * Loop until we successfully create a new ccache keyring with - * a unique name, or we get an error. - */ - while (1) { - kret = krb5int_random_string(context, uniquename, sizeof(uniquename)); - if (kret) { - k5_cc_mutex_unlock(context, &krb5int_krcc_mutex); - free(lid); - return kret; - } + kret = krb5_krcc_new_data(anchor_name, collection_name, + new_subsidiary_name, cache_id, collection_id, + &d); + if (kret) + goto cleanup; - DEBUG_PRINT(("krb5_krcc_generate_new: searching for name '%s'\n", - uniquename)); - key = keyctl_search(ring_id, KRCC_KEY_TYPE_KEYRING, uniquename, 0); - /*XXX*/ DEBUG_PRINT(("krb5_krcc_generate_new: after searching for '%s', key = %d, errno = %d\n", uniquename, key, errno)); - if (key < 0 && errno == ENOKEY) { - /* name does not already exist, create it to reserve the name */ - key = add_key(KRCC_KEY_TYPE_KEYRING, uniquename, NULL, 0, ring_id); - if (key < 0) { - kret = errno; - DEBUG_PRINT(("krb5_krcc_generate_new: '%s' trying to " - "create '%s'\n", strerror(errno), uniquename)); - k5_cc_mutex_unlock(context, &krb5int_krcc_mutex); - return kret; - } - break; - } - } + lid->data = d; + krb5_change_cache(); - kret = krb5_krcc_new_data(uniquename, key, ring_id, &d); - k5_cc_mutex_unlock(context, &krb5int_krcc_mutex); +cleanup: + free(anchor_name); + free(collection_name); + free(subsidiary_name); + free(new_subsidiary_name); + free(new_residual); if (kret) { free(lid); return kret; } - lid->data = d; *id = lid; - krb5_change_cache(); return KRB5_OK; } @@ -1023,14 +1450,16 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds) krb5_krcc_data *d = (krb5_krcc_data *) id->data; char *payload = NULL; unsigned int payloadlen; - key_serial_t newkey; char *keyname = NULL; DEBUG_PRINT(("krb5_krcc_store: entered\n")); - kret = k5_cc_mutex_lock(context, &d->lock); - if (kret) - return kret; + k5_cc_mutex_lock(context, &d->lock); + + if (!d->cache_id) { + k5_cc_mutex_unlock(context, &d->lock); + return KRB5_FCC_NOFILE; + } /* Get the service principal name and use it as the key name */ kret = krb5_unparse_name(context, creds->server, &keyname); @@ -1040,24 +1469,19 @@ krb5_krcc_store(krb5_context context, krb5_ccache id, krb5_creds * creds) } /* Serialize credential into memory */ - kret = krb5_krcc_unparse_cred(context, id, creds, &payload, &payloadlen); + kret = krb5_krcc_unparse_cred_alloc(context, creds, &payload, &payloadlen); if (kret != KRB5_OK) goto errout; /* Add new key (credentials) into keyring */ DEBUG_PRINT(("krb5_krcc_store: adding new key '%s' to keyring %d\n", - keyname, d->ring_id)); - newkey = add_key(KRCC_KEY_TYPE_USER, keyname, payload, - payloadlen, d->ring_id); - if (newkey < 0) { - kret = errno; - DEBUG_PRINT(("Error adding user key '%s': %s\n", - keyname, strerror(kret))); - } else { - d->numkeys++; - kret = KRB5_OK; - krb5_krcc_update_change_time(d); - } + keyname, d->cache_id)); + kret = add_cred_key(keyname, payload, payloadlen, d->cache_id, + d->is_legacy_type); + if (kret) + goto errout; + + krb5_krcc_update_change_time(d); errout: if (keyname) @@ -1073,36 +1497,30 @@ static krb5_error_code KRB5_CALLCONV krb5_krcc_last_change_time(krb5_context context, krb5_ccache id, krb5_timestamp *change_time) { - krb5_error_code ret = 0; krb5_krcc_data *data = (krb5_krcc_data *) id->data; - *change_time = 0; - - ret = k5_cc_mutex_lock(context, &data->lock); - if (!ret) { - *change_time = data->changetime; - k5_cc_mutex_unlock(context, &data->lock); - } - - return ret; + k5_cc_mutex_lock(context, &data->lock); + *change_time = data->changetime; + k5_cc_mutex_unlock(context, &data->lock); + return 0; } static krb5_error_code KRB5_CALLCONV krb5_krcc_lock(krb5_context context, krb5_ccache id) { - krb5_error_code ret = 0; krb5_krcc_data *data = (krb5_krcc_data *) id->data; - ret = k5_cc_mutex_lock(context, &data->lock); - return ret; + + k5_cc_mutex_lock(context, &data->lock); + return 0; } static krb5_error_code KRB5_CALLCONV krb5_krcc_unlock(krb5_context context, krb5_ccache id) { - krb5_error_code ret = 0; krb5_krcc_data *data = (krb5_krcc_data *) id->data; - ret = k5_cc_mutex_unlock(context, &data->lock); - return ret; + + k5_cc_mutex_unlock(context, &data->lock); + return 0; } @@ -1112,7 +1530,7 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id, { krb5_krcc_data *d; krb5_error_code kret; - char *payload; + char *payload = NULL; key_serial_t newkey; unsigned int payloadsize; krb5_krcc_bc bc; @@ -1121,14 +1539,19 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id, d = (krb5_krcc_data *) id->data; - payload = malloc(GUESS_CRED_SIZE); + /* Do a dry run first to calculate the size. */ + bc.bpp = bc.endp = NULL; + bc.size = 0; + kret = krb5_krcc_unparse_principal(context, princ, &bc); + CHECK_N_GO(kret, errout); + + /* Allocate a buffer and serialize for real. */ + payload = malloc(bc.size); if (payload == NULL) return KRB5_CC_NOMEM; - bc.bpp = payload; - bc.endp = payload + GUESS_CRED_SIZE; - - kret = krb5_krcc_unparse_principal(context, id, princ, &bc); + bc.endp = payload + bc.size; + kret = krb5_krcc_unparse_principal(context, princ, &bc); CHECK_N_GO(kret, errout); /* Add new key into keyring */ @@ -1140,14 +1563,14 @@ krb5_krcc_save_principal(krb5_context context, krb5_ccache id, rc = krb5_unparse_name(context, princ, &princname); DEBUG_PRINT(("krb5_krcc_save_principal: adding new key '%s' " "to keyring %d for principal '%s'\n", - KRCC_SPEC_PRINC_KEYNAME, d->ring_id, + KRCC_SPEC_PRINC_KEYNAME, d->cache_id, rc ? "" : princname)); if (rc == 0) krb5_free_unparsed_name(context, princname); } #endif newkey = add_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_PRINC_KEYNAME, payload, - payloadsize, d->ring_id); + payloadsize, d->cache_id); if (newkey < 0) { kret = errno; DEBUG_PRINT(("Error adding principal key: %s\n", strerror(kret))); @@ -1172,11 +1595,9 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id, int psize; krb5_krcc_bc bc; - kret = k5_cc_mutex_lock(context, &d->lock); - if (kret) - return kret; + k5_cc_mutex_lock(context, &d->lock); - if (!d->princ_id) { + if (!d->cache_id || !d->princ_id) { princ = 0L; kret = KRB5_FCC_NOFILE; goto errout; @@ -1191,7 +1612,7 @@ krb5_krcc_retrieve_principal(krb5_context context, krb5_ccache id, } bc.bpp = payload; bc.endp = (char *)payload + psize; - kret = krb5_krcc_parse_principal(context, id, princ, &bc); + kret = krb5_krcc_parse_principal(context, princ, &bc); errout: if (payload) @@ -1200,57 +1621,195 @@ errout: return kret; } -static int -krb5_krcc_get_ring_ids(krb5_krcc_ring_ids_t *p) -{ - key_serial_t ids_key; - char ids_buf[128]; - key_serial_t session, process, thread; - long val; +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; +}; - DEBUG_PRINT(("krb5_krcc_get_ring_ids: entered\n")); +static krb5_error_code KRB5_CALLCONV +krb5_krcc_ptcursor_new(krb5_context context, krb5_cc_ptcursor *cursor_out) +{ + struct krcc_ptcursor_data *data; + krb5_cc_ptcursor cursor; + krb5_error_code ret; + long size; + + *cursor_out = NULL; + + cursor = k5alloc(sizeof(struct krb5_cc_ptcursor_s), &ret); + if (cursor == NULL) + return ENOMEM; + data = k5alloc(sizeof(struct krcc_ptcursor_data), &ret); + if (data == NULL) + goto error; + cursor->ops = &krb5_krcc_ops; + cursor->data = data; + data->first = TRUE; + + ret = get_default(context, &data->anchor_name, &data->collection_name, + &data->subsidiary_name); + if (ret) + goto error; + + /* If there is no default collection, return an empty cursor. */ + if (data->anchor_name == NULL) { + *cursor_out = cursor; + return 0; + } - if (!p) - return EINVAL; + ret = get_collection(data->anchor_name, data->collection_name, + &data->collection_id); + if (ret) + goto error; + + if (data->subsidiary_name == NULL) { + ret = get_primary_name(context, data->anchor_name, + data->collection_name, data->collection_id, + &data->primary_name); + if (ret) + goto error; + + size = keyctl_read_alloc(data->collection_id, (void **)&data->keys); + if (size == -1) { + ret = errno; + goto error; + } + data->num_keys = size / sizeof(key_serial_t); + } - /* Use the defaults in case we find no ids key */ - p->session = KEY_SPEC_SESSION_KEYRING; - p->process = KEY_SPEC_PROCESS_KEYRING; - p->thread = KEY_SPEC_THREAD_KEYRING; + *cursor_out = cursor; + return 0; - /* - * Note that in the "normal" case, this will not be found. - * The Linux gssd creates this key while creating a - * context to communicate the user's key serial numbers. - */ - ids_key = request_key(KRCC_KEY_TYPE_USER, KRCC_SPEC_IDS_KEYNAME, NULL, 0); - if (ids_key < 0) - goto out; +error: + krb5_krcc_ptcursor_free(context, &cursor); + return ret; +} - DEBUG_PRINT(("krb5_krcc_get_ring_ids: processing '%s' key %d\n", - KRCC_SPEC_IDS_KEYNAME, ids_key)); - /* - * Read and parse the ids file - */ - memset(ids_buf, '\0', sizeof(ids_buf)); - val = keyctl_read(ids_key, ids_buf, sizeof(ids_buf)); - if (val > sizeof(ids_buf)) - goto out; +static krb5_error_code KRB5_CALLCONV +krb5_krcc_ptcursor_next(krb5_context context, krb5_cc_ptcursor cursor, + krb5_ccache *cache_out) +{ + krb5_error_code ret; + struct krcc_ptcursor_data *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; + + data = cursor->data; + + /* No keyring available */ + if (data->collection_id == 0) + return 0; + + if (data->first) { + /* Look for the primary cache for a collection cursor, or the + * subsidiary cache for a subsidiary cursor. */ + data->first = FALSE; + first_name = (data->primary_name != NULL) ? data->primary_name : + data->subsidiary_name; + cache_id = keyctl_search(data->collection_id, KRCC_KEY_TYPE_KEYRING, + first_name, 0); + if (cache_id != -1) { + return make_cache(data->collection_id, cache_id, data->anchor_name, + data->collection_name, first_name, cache_out); + } + } - val = sscanf(ids_buf, "%d:%d:%d", &session, &process, &thread); - if (val != 3) - goto out; + /* A subsidiary cursor yields at most the first cache. */ + if (data->subsidiary_name != NULL) + return 0; + + keytype = KRCC_KEY_TYPE_KEYRING ";"; + keytypelen = strlen(keytype); + + for (; data->next_key < data->num_keys; data->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 = data->keys[data->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, data->primary_name) == 0) + continue; + + /* We found a valid key */ + data->next_key++; + ret = make_cache(data->collection_id, key, data->anchor_name, + data->collection_name, subsidiary_name, cache_out); + free(description); + return ret; + } - p->session = session; - p->process = process; - p->thread = thread; + free(description); + return 0; +} -out: - DEBUG_PRINT(("krb5_krcc_get_ring_ids: returning %d:%d:%d\n", - p->session, p->process, p->thread)); +static krb5_error_code KRB5_CALLCONV +krb5_krcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor) +{ + struct krcc_ptcursor_data *data = (*cursor)->data; + + if (data != NULL) { + free(data->anchor_name); + free(data->collection_name); + free(data->subsidiary_name); + free(data->primary_name); + free(data->keys); + free(data); + } + free(*cursor); + *cursor = NULL; return 0; } +static krb5_error_code KRB5_CALLCONV +krb5_krcc_switch_to(krb5_context context, krb5_ccache cache) +{ + krb5_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; +} + /* * =============================================================== * INTERNAL functions to parse a credential from a key payload @@ -1271,8 +1830,8 @@ out: * KRB5_CC_END - there were not len bytes available */ static krb5_error_code -krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf, - unsigned int len, krb5_krcc_bc * bc) +krb5_krcc_parse(krb5_context context, krb5_pointer buf, unsigned int len, + krb5_krcc_bc * bc) { DEBUG_PRINT(("krb5_krcc_parse: entered\n")); @@ -1290,8 +1849,8 @@ krb5_krcc_parse(krb5_context context, krb5_ccache id, krb5_pointer buf, * and parse it into a credential structure. */ static krb5_error_code -krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds, - char *payload, int psize) +krb5_krcc_parse_cred(krb5_context context, krb5_creds * creds, char *payload, + int psize) { krb5_error_code kret; krb5_octet octet; @@ -1301,36 +1860,36 @@ krb5_krcc_parse_cred(krb5_context context, krb5_ccache id, krb5_creds * creds, /* Parse the pieces of the credential */ bc.bpp = payload; bc.endp = bc.bpp + psize; - kret = krb5_krcc_parse_principal(context, id, &creds->client, &bc); + kret = krb5_krcc_parse_principal(context, &creds->client, &bc); CHECK_N_GO(kret, out); - kret = krb5_krcc_parse_principal(context, id, &creds->server, &bc); + kret = krb5_krcc_parse_principal(context, &creds->server, &bc); CHECK_N_GO(kret, cleanclient); - kret = krb5_krcc_parse_keyblock(context, id, &creds->keyblock, &bc); + kret = krb5_krcc_parse_keyblock(context, &creds->keyblock, &bc); CHECK_N_GO(kret, cleanserver); - kret = krb5_krcc_parse_times(context, id, &creds->times, &bc); + kret = krb5_krcc_parse_times(context, &creds->times, &bc); CHECK_N_GO(kret, cleanserver); - kret = krb5_krcc_parse_octet(context, id, &octet, &bc); + kret = krb5_krcc_parse_octet(context, &octet, &bc); CHECK_N_GO(kret, cleanserver); creds->is_skey = octet; - kret = krb5_krcc_parse_int32(context, id, &int32, &bc); + kret = krb5_krcc_parse_int32(context, &int32, &bc); CHECK_N_GO(kret, cleanserver); creds->ticket_flags = int32; - kret = krb5_krcc_parse_addrs(context, id, &creds->addresses, &bc); + kret = krb5_krcc_parse_addrs(context, &creds->addresses, &bc); CHECK_N_GO(kret, cleanblock); - kret = krb5_krcc_parse_authdata(context, id, &creds->authdata, &bc); + kret = krb5_krcc_parse_authdata(context, &creds->authdata, &bc); CHECK_N_GO(kret, cleanaddrs); - kret = krb5_krcc_parse_krb5data(context, id, &creds->ticket, &bc); + kret = krb5_krcc_parse_krb5data(context, &creds->ticket, &bc); CHECK_N_GO(kret, cleanauthdata); - kret = krb5_krcc_parse_krb5data(context, id, &creds->second_ticket, &bc); + kret = krb5_krcc_parse_krb5data(context, &creds->second_ticket, &bc); CHECK_N_GO(kret, cleanticket); kret = KRB5_OK; @@ -1355,8 +1914,8 @@ out: } static krb5_error_code -krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, - krb5_principal * princ, krb5_krcc_bc * bc) +krb5_krcc_parse_principal(krb5_context context, krb5_principal * princ, + krb5_krcc_bc * bc) { krb5_error_code kret; register krb5_principal tmpprinc; @@ -1364,12 +1923,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, int i; /* Read principal type */ - kret = krb5_krcc_parse_int32(context, id, &type, bc); + kret = krb5_krcc_parse_int32(context, &type, bc); if (kret != KRB5_OK) return kret; /* Read the number of components */ - kret = krb5_krcc_parse_int32(context, id, &length, bc); + kret = krb5_krcc_parse_int32(context, &length, bc); if (kret != KRB5_OK) return kret; @@ -1380,12 +1939,7 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, if (tmpprinc == NULL) return KRB5_CC_NOMEM; if (length) { - size_t msize = length; - if (msize != length) { - free(tmpprinc); - return KRB5_CC_NOMEM; - } - tmpprinc->data = ALLOC(msize, krb5_data); + tmpprinc->data = calloc(length, sizeof(krb5_data)); if (tmpprinc->data == 0) { free(tmpprinc); return KRB5_CC_NOMEM; @@ -1396,15 +1950,12 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, tmpprinc->length = length; tmpprinc->type = type; - kret = krb5_krcc_parse_krb5data(context, id, - krb5_princ_realm(context, tmpprinc), bc); + kret = krb5_krcc_parse_krb5data(context, &tmpprinc->realm, bc); i = 0; CHECK(kret); for (i = 0; i < length; i++) { - kret = krb5_krcc_parse_krb5data(context, id, - krb5_princ_component(context, tmpprinc, - i), bc); + kret = krb5_krcc_parse_krb5data(context, &tmpprinc->data[i], bc); CHECK(kret); } *princ = tmpprinc; @@ -1412,16 +1963,16 @@ krb5_krcc_parse_principal(krb5_context context, krb5_ccache id, errout: while (--i >= 0) - free(krb5_princ_component(context, tmpprinc, i)->data); - free(krb5_princ_realm(context, tmpprinc)->data); + free(tmpprinc->data[i].data); + free(tmpprinc->realm.data); free(tmpprinc->data); free(tmpprinc); return kret; } static krb5_error_code -krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id, - krb5_keyblock * keyblock, krb5_krcc_bc * bc) +krb5_krcc_parse_keyblock(krb5_context context, krb5_keyblock * keyblock, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_ui_2 ui2; @@ -1430,26 +1981,22 @@ krb5_krcc_parse_keyblock(krb5_context context, krb5_ccache id, keyblock->magic = KV5M_KEYBLOCK; keyblock->contents = 0; - kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + kret = krb5_krcc_parse_ui_2(context, &ui2, bc); CHECK(kret); keyblock->enctype = ui2; - kret = krb5_krcc_parse_int32(context, id, &int32, bc); + kret = krb5_krcc_parse_int32(context, &int32, bc); CHECK(kret); if (int32 < 0) return KRB5_CC_NOMEM; keyblock->length = int32; - /* Overflow check. */ - if (keyblock->length != int32) - return KRB5_CC_NOMEM; if (keyblock->length == 0) return KRB5_OK; - keyblock->contents = ALLOC(keyblock->length, krb5_octet); + keyblock->contents = malloc(keyblock->length); if (keyblock->contents == NULL) return KRB5_CC_NOMEM; - kret = krb5_krcc_parse(context, id, keyblock->contents, - keyblock->length, bc); + kret = krb5_krcc_parse(context, keyblock->contents, keyblock->length, bc); CHECK(kret); return KRB5_OK; @@ -1460,25 +2007,25 @@ errout: } static krb5_error_code -krb5_krcc_parse_times(krb5_context context, krb5_ccache id, - krb5_ticket_times * t, krb5_krcc_bc * bc) +krb5_krcc_parse_times(krb5_context context, krb5_ticket_times * t, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 i; - kret = krb5_krcc_parse_int32(context, id, &i, bc); + kret = krb5_krcc_parse_int32(context, &i, bc); CHECK(kret); t->authtime = i; - kret = krb5_krcc_parse_int32(context, id, &i, bc); + kret = krb5_krcc_parse_int32(context, &i, bc); CHECK(kret); t->starttime = i; - kret = krb5_krcc_parse_int32(context, id, &i, bc); + kret = krb5_krcc_parse_int32(context, &i, bc); CHECK(kret); t->endtime = i; - kret = krb5_krcc_parse_int32(context, id, &i, bc); + kret = krb5_krcc_parse_int32(context, &i, bc); CHECK(kret); t->renew_till = i; @@ -1488,8 +2035,8 @@ errout: } static krb5_error_code -krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id, - krb5_data * data, krb5_krcc_bc * bc) +krb5_krcc_parse_krb5data(krb5_context context, krb5_data * data, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 len; @@ -1497,12 +2044,12 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id, data->magic = KV5M_DATA; data->data = 0; - kret = krb5_krcc_parse_int32(context, id, &len, bc); + kret = krb5_krcc_parse_int32(context, &len, bc); CHECK(kret); if (len < 0) return KRB5_CC_NOMEM; data->length = len; - if (data->length != len || data->length + 1 == 0) + if (data->length + 1 == 0) return KRB5_CC_NOMEM; if (data->length == 0) { @@ -1514,8 +2061,7 @@ krb5_krcc_parse_krb5data(krb5_context context, krb5_ccache id, if (data->data == NULL) return KRB5_CC_NOMEM; - kret = krb5_krcc_parse(context, id, data->data, (unsigned) data->length, - bc); + kret = krb5_krcc_parse(context, data->data, (unsigned) data->length, bc); CHECK(kret); data->data[data->length] = 0; /* Null terminate, just in case.... */ @@ -1527,13 +2073,12 @@ errout: } static krb5_error_code -krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i, - krb5_krcc_bc * bc) +krb5_krcc_parse_int32(krb5_context context, krb5_int32 * i, krb5_krcc_bc * bc) { krb5_error_code kret; unsigned char buf[4]; - kret = krb5_krcc_parse(context, id, buf, 4, bc); + kret = krb5_krcc_parse(context, buf, 4, bc); if (kret) return kret; *i = load_32_be(buf); @@ -1541,15 +2086,14 @@ krb5_krcc_parse_int32(krb5_context context, krb5_ccache id, krb5_int32 * i, } static krb5_error_code -krb5_krcc_parse_octet(krb5_context context, krb5_ccache id, krb5_octet * i, - krb5_krcc_bc * bc) +krb5_krcc_parse_octet(krb5_context context, krb5_octet * i, krb5_krcc_bc * bc) { - return krb5_krcc_parse(context, id, (krb5_pointer) i, 1, bc); + return krb5_krcc_parse(context, (krb5_pointer) i, 1, bc); } static krb5_error_code -krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id, - krb5_address *** addrs, krb5_krcc_bc * bc) +krb5_krcc_parse_addrs(krb5_context context, krb5_address *** addrs, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 length; @@ -1559,18 +2103,17 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id, *addrs = 0; /* Read the number of components */ - kret = krb5_krcc_parse_int32(context, id, &length, bc); + kret = krb5_krcc_parse_int32(context, &length, bc); CHECK(kret); /* * Make *addrs able to hold length pointers to krb5_address structs * Add one extra for a null-terminated list */ - msize = length; - msize += 1; - if (msize == 0 || msize - 1 != length || length < 0) + msize = (size_t)length + 1; + if (msize == 0 || length < 0) return KRB5_CC_NOMEM; - *addrs = ALLOC(msize, krb5_address *); + *addrs = calloc(msize, sizeof(krb5_address *)); if (*addrs == NULL) return KRB5_CC_NOMEM; @@ -1580,7 +2123,7 @@ krb5_krcc_parse_addrs(krb5_context context, krb5_ccache id, krb5_free_addresses(context, *addrs); return KRB5_CC_NOMEM; } - kret = krb5_krcc_parse_addr(context, id, (*addrs)[i], bc); + kret = krb5_krcc_parse_addr(context, (*addrs)[i], bc); CHECK(kret); } @@ -1592,7 +2135,7 @@ errout: } static krb5_error_code -krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr, +krb5_krcc_parse_addr(krb5_context context, krb5_address * addr, krb5_krcc_bc * bc) { krb5_error_code kret; @@ -1602,22 +2145,15 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr, addr->magic = KV5M_ADDRESS; addr->contents = 0; - kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + kret = krb5_krcc_parse_ui_2(context, &ui2, bc); CHECK(kret); addr->addrtype = ui2; - kret = krb5_krcc_parse_int32(context, id, &int32, bc); + kret = krb5_krcc_parse_int32(context, &int32, bc); CHECK(kret); if ((int32 & VALID_INT_BITS) != int32) /* Overflow int??? */ return KRB5_CC_NOMEM; addr->length = int32; - /* - * Length field is "unsigned int", which may be smaller - * than 32 bits. - */ - if (addr->length != int32) - return KRB5_CC_NOMEM; /* XXX */ - if (addr->length == 0) return KRB5_OK; @@ -1625,7 +2161,7 @@ krb5_krcc_parse_addr(krb5_context context, krb5_ccache id, krb5_address * addr, if (addr->contents == NULL) return KRB5_CC_NOMEM; - kret = krb5_krcc_parse(context, id, addr->contents, addr->length, bc); + kret = krb5_krcc_parse(context, addr->contents, addr->length, bc); CHECK(kret); return KRB5_OK; @@ -1636,8 +2172,8 @@ errout: } static krb5_error_code -krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id, - krb5_authdata *** a, krb5_krcc_bc * bc) +krb5_krcc_parse_authdata(krb5_context context, krb5_authdata *** a, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 length; @@ -1647,7 +2183,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id, *a = 0; /* Read the number of components */ - kret = krb5_krcc_parse_int32(context, id, &length, bc); + kret = krb5_krcc_parse_int32(context, &length, bc); CHECK(kret); if (length == 0) @@ -1657,11 +2193,10 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id, * Make *a able to hold length pointers to krb5_authdata structs * Add one extra for a null-terminated list */ - msize = length; - msize += 1; - if (msize == 0 || msize - 1 != length || length < 0) + msize = (size_t)length + 1; + if (msize == 0 || length < 0) return KRB5_CC_NOMEM; - *a = ALLOC(msize, krb5_authdata *); + *a = calloc(msize, sizeof(krb5_authdata *)); if (*a == NULL) return KRB5_CC_NOMEM; @@ -1672,7 +2207,7 @@ krb5_krcc_parse_authdata(krb5_context context, krb5_ccache id, *a = NULL; return KRB5_CC_NOMEM; } - kret = krb5_krcc_parse_authdatum(context, id, (*a)[i], bc); + kret = krb5_krcc_parse_authdatum(context, (*a)[i], bc); CHECK(kret); } @@ -1686,8 +2221,8 @@ errout: } static krb5_error_code -krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id, - krb5_authdata * a, krb5_krcc_bc * bc) +krb5_krcc_parse_authdatum(krb5_context context, krb5_authdata * a, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 int32; @@ -1696,21 +2231,14 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id, a->magic = KV5M_AUTHDATA; a->contents = NULL; - kret = krb5_krcc_parse_ui_2(context, id, &ui2, bc); + kret = krb5_krcc_parse_ui_2(context, &ui2, bc); CHECK(kret); a->ad_type = (krb5_authdatatype) ui2; - kret = krb5_krcc_parse_int32(context, id, &int32, bc); + kret = krb5_krcc_parse_int32(context, &int32, bc); CHECK(kret); if ((int32 & VALID_INT_BITS) != int32) /* Overflow int??? */ return KRB5_CC_NOMEM; a->length = int32; - /* - * Value could have gotten truncated if int is - * smaller than 32 bits. - */ - if (a->length != int32) - return KRB5_CC_NOMEM; /* XXX */ - if (a->length == 0) return KRB5_OK; @@ -1718,7 +2246,7 @@ krb5_krcc_parse_authdatum(krb5_context context, krb5_ccache id, if (a->contents == NULL) return KRB5_CC_NOMEM; - kret = krb5_krcc_parse(context, id, a->contents, a->length, bc); + kret = krb5_krcc_parse(context, a->contents, a->length, bc); CHECK(kret); return KRB5_OK; @@ -1730,13 +2258,12 @@ errout: } static krb5_error_code -krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i, - krb5_krcc_bc * bc) +krb5_krcc_parse_ui_2(krb5_context context, krb5_ui_2 * i, krb5_krcc_bc * bc) { krb5_error_code kret; unsigned char buf[2]; - kret = krb5_krcc_parse(context, id, buf, 2, bc); + kret = krb5_krcc_parse(context, buf, 2, bc); if (kret) return kret; *i = load_16_be(buf); @@ -1756,9 +2283,15 @@ krb5_krcc_parse_ui_2(krb5_context context, krb5_ccache id, krb5_ui_2 * i, * system errors */ static krb5_error_code -krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf, - unsigned int len, krb5_krcc_bc * bc) +krb5_krcc_unparse(krb5_context context, krb5_pointer buf, unsigned int len, + krb5_krcc_bc * bc) { + if (bc->bpp == NULL) { + /* This is a dry run; just increase size and return. */ + bc->size += len; + return KRB5_OK; + } + if (bc->bpp + len > bc->endp) return KRB5_CC_WRITE; @@ -1769,29 +2302,26 @@ krb5_krcc_unparse(krb5_context context, krb5_ccache id, krb5_pointer buf, } static krb5_error_code -krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id, - krb5_principal princ, krb5_krcc_bc * bc) +krb5_krcc_unparse_principal(krb5_context context, krb5_principal princ, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_int32 i, length, tmp, type; - type = krb5_princ_type(context, princ); - tmp = length = krb5_princ_size(context, princ); + type = princ->type; + tmp = length = princ->length; - kret = krb5_krcc_unparse_int32(context, id, type, bc); + kret = krb5_krcc_unparse_int32(context, type, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_int32(context, id, tmp, bc); + kret = krb5_krcc_unparse_int32(context, tmp, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_krb5data(context, id, - krb5_princ_realm(context, princ), bc); + kret = krb5_krcc_unparse_krb5data(context, &princ->realm, bc); CHECK_OUT(kret); for (i = 0; i < length; i++) { - kret = krb5_krcc_unparse_krb5data(context, id, - krb5_princ_component(context, princ, - i), bc); + kret = krb5_krcc_unparse_krb5data(context, &princ->data[i], bc); CHECK_OUT(kret); } @@ -1799,67 +2329,65 @@ krb5_krcc_unparse_principal(krb5_context context, krb5_ccache id, } static krb5_error_code -krb5_krcc_unparse_keyblock(krb5_context context, krb5_ccache id, - krb5_keyblock * keyblock, krb5_krcc_bc * bc) +krb5_krcc_unparse_keyblock(krb5_context context, krb5_keyblock * keyblock, + krb5_krcc_bc * bc) { krb5_error_code kret; - kret = krb5_krcc_unparse_ui_2(context, id, keyblock->enctype, bc); + kret = krb5_krcc_unparse_ui_2(context, keyblock->enctype, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_ui_4(context, id, keyblock->length, bc); + kret = krb5_krcc_unparse_ui_4(context, keyblock->length, bc); CHECK_OUT(kret); - return krb5_krcc_unparse(context, id, (char *) keyblock->contents, + return krb5_krcc_unparse(context, (char *) keyblock->contents, keyblock->length, bc); } static krb5_error_code -krb5_krcc_unparse_times(krb5_context context, krb5_ccache id, - krb5_ticket_times * t, krb5_krcc_bc * bc) +krb5_krcc_unparse_times(krb5_context context, krb5_ticket_times * t, + krb5_krcc_bc * bc) { krb5_error_code kret; - kret = krb5_krcc_unparse_int32(context, id, t->authtime, bc); + kret = krb5_krcc_unparse_int32(context, t->authtime, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_int32(context, id, t->starttime, bc); + kret = krb5_krcc_unparse_int32(context, t->starttime, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_int32(context, id, t->endtime, bc); + kret = krb5_krcc_unparse_int32(context, t->endtime, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_int32(context, id, t->renew_till, bc); + kret = krb5_krcc_unparse_int32(context, t->renew_till, bc); CHECK_OUT(kret); return 0; } static krb5_error_code -krb5_krcc_unparse_krb5data(krb5_context context, krb5_ccache id, - krb5_data * data, krb5_krcc_bc * bc) +krb5_krcc_unparse_krb5data(krb5_context context, krb5_data * data, + krb5_krcc_bc * bc) { krb5_error_code kret; - kret = krb5_krcc_unparse_ui_4(context, id, data->length, bc); + kret = krb5_krcc_unparse_ui_4(context, data->length, bc); CHECK_OUT(kret); - return krb5_krcc_unparse(context, id, data->data, data->length, bc); + return krb5_krcc_unparse(context, data->data, data->length, bc); } static krb5_error_code -krb5_krcc_unparse_int32(krb5_context context, krb5_ccache id, krb5_int32 i, - krb5_krcc_bc * bc) +krb5_krcc_unparse_int32(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc) { - return krb5_krcc_unparse_ui_4(context, id, (krb5_ui_4) i, bc); + return krb5_krcc_unparse_ui_4(context, (krb5_ui_4) i, bc); } static krb5_error_code -krb5_krcc_unparse_octet(krb5_context context, krb5_ccache id, krb5_int32 i, - krb5_krcc_bc * bc) +krb5_krcc_unparse_octet(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc) { krb5_octet ibuf; ibuf = (krb5_octet) i; - return krb5_krcc_unparse(context, id, (char *) &ibuf, 1, bc); + return krb5_krcc_unparse(context, (char *) &ibuf, 1, bc); } static krb5_error_code -krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id, - krb5_address ** addrs, krb5_krcc_bc * bc) +krb5_krcc_unparse_addrs(krb5_context context, krb5_address ** addrs, + krb5_krcc_bc * bc) { krb5_error_code kret; krb5_address **temp; @@ -1872,10 +2400,10 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id, length += 1; } - kret = krb5_krcc_unparse_int32(context, id, length, bc); + kret = krb5_krcc_unparse_int32(context, length, bc); CHECK_OUT(kret); for (i = 0; i < length; i++) { - kret = krb5_krcc_unparse_addr(context, id, addrs[i], bc); + kret = krb5_krcc_unparse_addr(context, addrs[i], bc); CHECK_OUT(kret); } @@ -1883,21 +2411,21 @@ krb5_krcc_unparse_addrs(krb5_context context, krb5_ccache id, } static krb5_error_code -krb5_krcc_unparse_addr(krb5_context context, krb5_ccache id, - krb5_address * addr, krb5_krcc_bc * bc) +krb5_krcc_unparse_addr(krb5_context context, krb5_address * addr, + krb5_krcc_bc * bc) { krb5_error_code kret; - kret = krb5_krcc_unparse_ui_2(context, id, addr->addrtype, bc); + kret = krb5_krcc_unparse_ui_2(context, addr->addrtype, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_ui_4(context, id, addr->length, bc); + kret = krb5_krcc_unparse_ui_4(context, addr->length, bc); CHECK_OUT(kret); - return krb5_krcc_unparse(context, id, (char *) addr->contents, + return krb5_krcc_unparse(context, (char *) addr->contents, addr->length, bc); } static krb5_error_code -krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id, +krb5_krcc_unparse_authdata(krb5_context context, krb5_authdata ** a, krb5_krcc_bc * bc) { krb5_error_code kret; @@ -1909,47 +2437,45 @@ krb5_krcc_unparse_authdata(krb5_context context, krb5_ccache id, length++; } - kret = krb5_krcc_unparse_int32(context, id, length, bc); + kret = krb5_krcc_unparse_int32(context, length, bc); CHECK_OUT(kret); for (i = 0; i < length; i++) { - kret = krb5_krcc_unparse_authdatum(context, id, a[i], bc); + kret = krb5_krcc_unparse_authdatum(context, a[i], bc); CHECK_OUT(kret); } return KRB5_OK; } static krb5_error_code -krb5_krcc_unparse_authdatum(krb5_context context, krb5_ccache id, - krb5_authdata * a, krb5_krcc_bc * bc) +krb5_krcc_unparse_authdatum(krb5_context context, krb5_authdata * a, + krb5_krcc_bc * bc) { krb5_error_code kret; - kret = krb5_krcc_unparse_ui_2(context, id, a->ad_type, bc); + kret = krb5_krcc_unparse_ui_2(context, a->ad_type, bc); CHECK_OUT(kret); - kret = krb5_krcc_unparse_ui_4(context, id, a->length, bc); + kret = krb5_krcc_unparse_ui_4(context, a->length, bc); CHECK_OUT(kret); - return krb5_krcc_unparse(context, id, (krb5_pointer) a->contents, + return krb5_krcc_unparse(context, (krb5_pointer) a->contents, a->length, bc); } static krb5_error_code -krb5_krcc_unparse_ui_4(krb5_context context, krb5_ccache id, krb5_ui_4 i, - krb5_krcc_bc * bc) +krb5_krcc_unparse_ui_4(krb5_context context, krb5_ui_4 i, krb5_krcc_bc * bc) { unsigned char buf[4]; store_32_be(i, buf); - return krb5_krcc_unparse(context, id, buf, 4, bc); + return krb5_krcc_unparse(context, buf, 4, bc); } static krb5_error_code -krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i, - krb5_krcc_bc * bc) +krb5_krcc_unparse_ui_2(krb5_context context, krb5_int32 i, krb5_krcc_bc * bc) { unsigned char buf[2]; store_16_be(i, buf); - return krb5_krcc_unparse(context, id, buf, 2, bc); + return krb5_krcc_unparse(context, buf, 2, bc); } /* @@ -1965,11 +2491,55 @@ krb5_krcc_unparse_ui_2(krb5_context context, krb5_ccache id, krb5_int32 i, * Caller is responsible for freeing returned buffer. */ static krb5_error_code -krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id, - krb5_creds * creds, char **datapp, unsigned int *lenptr) +krb5_krcc_unparse_cred(krb5_context context, krb5_creds * creds, + krb5_krcc_bc *bc) { krb5_error_code kret; - char *buf; + + kret = krb5_krcc_unparse_principal(context, creds->client, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_principal(context, creds->server, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_keyblock(context, &creds->keyblock, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_times(context, &creds->times, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_octet(context, (krb5_int32) creds->is_skey, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_int32(context, creds->ticket_flags, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_addrs(context, creds->addresses, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_authdata(context, creds->authdata, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_krb5data(context, &creds->ticket, bc); + CHECK_OUT(kret); + CHECK(kret); + + kret = krb5_krcc_unparse_krb5data(context, &creds->second_ticket, bc); + CHECK_OUT(kret); + + /* Success! */ + kret = KRB5_OK; + +errout: + return kret; +} + +static krb5_error_code +krb5_krcc_unparse_cred_alloc(krb5_context context, krb5_creds * creds, + char **datapp, unsigned int *lenptr) +{ + krb5_error_code kret; + char *buf = NULL; krb5_krcc_bc bc; if (!creds || !datapp || !lenptr) @@ -1978,43 +2548,102 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id, *datapp = NULL; *lenptr = 0; - buf = malloc(GUESS_CRED_SIZE); + /* Do a dry run first to calculate the size. */ + bc.bpp = bc.endp = NULL; + bc.size = 0; + kret = krb5_krcc_unparse_cred(context, creds, &bc); + CHECK(kret); + if (bc.size > MAX_CRED_SIZE) + return KRB5_CC_WRITE; + + /* Allocate a buffer and unparse for real. */ + buf = malloc(bc.size); if (buf == NULL) return KRB5_CC_NOMEM; - bc.bpp = buf; - bc.endp = buf + GUESS_CRED_SIZE; + bc.endp = buf + bc.size; + kret = krb5_krcc_unparse_cred(context, creds, &bc); + CHECK(kret); - kret = krb5_krcc_unparse_principal(context, id, creds->client, &bc); - CHECK_N_GO(kret, errout); + /* Success! */ + *datapp = buf; + *lenptr = bc.bpp - buf; + buf = NULL; + kret = KRB5_OK; - kret = krb5_krcc_unparse_principal(context, id, creds->server, &bc); - CHECK_N_GO(kret, errout); +errout: + free(buf); + return kret; +} - kret = krb5_krcc_unparse_keyblock(context, id, &creds->keyblock, &bc); - CHECK_N_GO(kret, errout); +static krb5_error_code +krb5_krcc_parse_index(krb5_context context, krb5_int32 *version, + char **primary, void *payload, int psize) +{ + krb5_error_code kret; + krb5_krcc_bc bc; + krb5_data data; - kret = krb5_krcc_unparse_times(context, id, &creds->times, &bc); - CHECK_N_GO(kret, errout); + bc.bpp = payload; + bc.endp = bc.bpp + psize; - kret = krb5_krcc_unparse_octet(context, id, (krb5_int32) creds->is_skey, - &bc); - CHECK_N_GO(kret, errout); + kret = krb5_krcc_parse_int32(context, version, &bc); + CHECK_OUT(kret); - kret = krb5_krcc_unparse_int32(context, id, creds->ticket_flags, &bc); - CHECK_N_GO(kret, errout); + kret = krb5_krcc_parse_krb5data(context, &data, &bc); + CHECK_OUT(kret); - kret = krb5_krcc_unparse_addrs(context, id, creds->addresses, &bc); - CHECK_N_GO(kret, errout); + *primary = (char *)data.data; + return KRB5_OK; +} - kret = krb5_krcc_unparse_authdata(context, id, creds->authdata, &bc); - CHECK_N_GO(kret, errout); +static krb5_error_code +krb5_krcc_unparse_index_internal(krb5_context context, krb5_int32 version, + const char *primary, krb5_krcc_bc *bc) +{ + krb5_error_code kret; + krb5_data data; - kret = krb5_krcc_unparse_krb5data(context, id, &creds->ticket, &bc); - CHECK_N_GO(kret, errout); + data.length = strlen(primary) + 1; + data.data = (void *)primary; - kret = krb5_krcc_unparse_krb5data(context, id, &creds->second_ticket, &bc); - CHECK_N_GO(kret, errout); + kret = krb5_krcc_unparse_int32(context, version, bc); + CHECK_OUT(kret); + + kret = krb5_krcc_unparse_krb5data(context, &data, bc); + CHECK_OUT(kret); + + return KRB5_OK; +} + +static krb5_error_code +krb5_krcc_unparse_index(krb5_context context, krb5_int32 version, + const char *primary, void **datapp, int *lenptr) +{ + krb5_error_code kret; + krb5_krcc_bc bc; + char *buf; + + if (!primary || !datapp || !lenptr) + return EINVAL; + + *datapp = NULL; + *lenptr = 0; + + /* Do a dry run first to calculate the size. */ + bc.bpp = bc.endp = NULL; + bc.size = 0; + kret = krb5_krcc_unparse_index_internal(context, version, primary, &bc); + CHECK_OUT(kret); + + buf = malloc(bc.size); + if (buf == NULL) + return ENOMEM; + + bc.bpp = buf; + bc.endp = buf + bc.size; + kret = krb5_krcc_unparse_index_internal(context, version, primary, &bc); + CHECK(kret); /* Success! */ *datapp = buf; @@ -2022,6 +2651,8 @@ krb5_krcc_unparse_cred(krb5_context context, krb5_ccache id, kret = KRB5_OK; errout: + if (kret) + free(buf); return kret; } @@ -2065,15 +2696,15 @@ const krb5_cc_ops krb5_krcc_ops = { krb5_krcc_remove_cred, krb5_krcc_set_flags, krb5_krcc_get_flags, /* added after 1.4 release */ - NULL, - NULL, - NULL, + krb5_krcc_ptcursor_new, + krb5_krcc_ptcursor_next, + krb5_krcc_ptcursor_free, NULL, /* move */ krb5_krcc_last_change_time, /* lastchange */ NULL, /* wasdefault */ krb5_krcc_lock, krb5_krcc_unlock, - NULL, /* switch_to */ + krb5_krcc_switch_to, }; #else /* !USE_KEYRING_CCACHE */ diff --git a/src/lib/krb5/ccache/t_cc.c b/src/lib/krb5/ccache/t_cc.c index e14ae7f..6069cab 100644 --- a/src/lib/krb5/ccache/t_cc.c +++ b/src/lib/krb5/ccache/t_cc.c @@ -25,6 +25,7 @@ */ #include "k5-int.h" +#include "cc-int.h" #include #include #include "autoconf.h" @@ -331,14 +332,14 @@ check_registered(krb5_context context, const char *prefix) if(kret != KRB5_OK) { if(kret == KRB5_CC_UNKNOWN_TYPE) return 0; - com_err("Checking on credential type", kret,prefix); + com_err("Checking on credential type", kret, "%s", prefix); fflush(stderr); return 0; } kret = krb5_cc_close(context, id); if(kret != KRB5_OK) { - com_err("Checking on credential type - closing", kret,prefix); + com_err("Checking on credential type - closing", kret, "%s", prefix); fflush(stderr); } @@ -425,8 +426,8 @@ main(void) test_misc(context); do_test(context, ""); - if(check_registered(context, "KEYRING:")) - do_test(context, "KEYRING:"); + if (check_registered(context, "KEYRING:process:")) + do_test(context, "KEYRING:process:"); else printf("Skiping KEYRING: test - unregistered type\n"); diff --git a/src/lib/krb5/ccache/t_cccol.c b/src/lib/krb5/ccache/t_cccol.c new file mode 100644 index 0000000..444806e --- /dev/null +++ b/src/lib/krb5/ccache/t_cccol.c @@ -0,0 +1,363 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* lib/krb5/ccache/t_cccol.py - Test ccache collection via API */ +/* + * Copyright (C) 2013 by the Massachusetts Institute of Technology. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, + * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED + * OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include +#include +#include +#include +#include + +static krb5_context ctx; + +/* Check that code is 0. Display an error message first if it is not. */ +static void +check(krb5_error_code code) +{ + const char *errmsg; + + if (code != 0) { + errmsg = krb5_get_error_message(ctx, code); + fprintf(stderr, "%s\n", errmsg); + krb5_free_error_message(ctx, errmsg); + } + assert(code == 0); +} + +/* Construct a list of the names of each credential cache in the collection. */ +static void +get_collection_names(char ***list_out, size_t *count_out) +{ + krb5_cccol_cursor cursor; + krb5_ccache cache; + char **list = NULL; + size_t count = 0; + char *name; + + check(krb5_cccol_cursor_new(ctx, &cursor)); + while (1) { + check(krb5_cccol_cursor_next(ctx, cursor, &cache)); + if (cache == NULL) + break; + check(krb5_cc_get_full_name(ctx, cache, &name)); + krb5_cc_close(ctx, cache); + list = realloc(list, (count + 1) * sizeof(*list)); + assert(list != NULL); + list[count++] = name; + } + krb5_cccol_cursor_free(ctx, &cursor); + *list_out = list; + *count_out = count; +} + +/* Return true if list contains name. */ +static krb5_boolean +in_list(char **list, size_t count, const char *name) +{ + size_t i; + + for (i = 0; i < count; i++) { + if (strcmp(list[i], name) == 0) + return TRUE; + } + return FALSE; +} + +/* Release the memory for a list of credential cache names. */ +static void +free_list(char **list, size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) + krb5_free_string(ctx, list[i]); + free(list); +} + +/* + * Check that the cache names within the current collection begin with first + * (unless first is NULL), that the other elements match the remaining + * arguments in some order. others must be the number of additional cache + * names. + */ +static void +check_collection(const char *first, size_t others, ...) +{ + va_list ap; + char **list; + size_t count, i; + const char *name; + + get_collection_names(&list, &count); + if (first != NULL) { + assert(strcmp(first, list[0]) == 0); + assert(count == others + 1); + } else { + assert(count == others); + } + va_start(ap, others); + for (i = 0; i < others; i++) { + name = va_arg(ap, const char *); + assert(in_list(list, count, name)); + } + va_end(ap); + free_list(list, count); +} + +/* Check that the name of cache matches expected_name. */ +static void +check_name(krb5_ccache cache, const char *expected_name) +{ + char *name; + + check(krb5_cc_get_full_name(ctx, cache, &name)); + assert(strcmp(name, expected_name) == 0); + krb5_free_string(ctx, name); +} + +/* Check that when collection_name is resolved, the resulting cache's name + * matches expected_name. */ +static void +check_primary_name(const char *collection_name, const char *expected_name) +{ + krb5_ccache cache; + + check(krb5_cc_resolve(ctx, collection_name, &cache)); + check_name(cache, expected_name); + krb5_cc_close(ctx, cache); +} + +/* Check that when name is resolved, the resulting cache's principal matches + * expected_princ, or has no principal if expected_princ is NULL. */ +static void +check_princ(const char *name, krb5_principal expected_princ) +{ + krb5_ccache cache; + krb5_principal princ; + + check(krb5_cc_resolve(ctx, name, &cache)); + if (expected_princ != NULL) { + check(krb5_cc_get_principal(ctx, cache, &princ)); + assert(krb5_principal_compare(ctx, princ, expected_princ)); + krb5_free_principal(ctx, princ); + } else { + assert(krb5_cc_get_principal(ctx, cache, &princ) != 0); + } + krb5_cc_close(ctx, cache); +} + +/* Check that krb5_cc_cache_match on princ returns a cache whose name matches + * expected_name, or that the match fails if expected_name is NULL. */ +static void +check_match(krb5_principal princ, const char *expected_name) +{ + krb5_ccache cache; + + if (expected_name != NULL) { + check(krb5_cc_cache_match(ctx, princ, &cache)); + check_name(cache, expected_name); + krb5_cc_close(ctx, cache); + } else { + assert(krb5_cc_cache_match(ctx, princ, &cache) != 0); + } +} + +int +main(int argc, char **argv) +{ + krb5_ccache ccinitial, ccu1, ccu2; + krb5_principal princ1, princ2, princ3; + const char *collection_name, *typename; + char *initial_primary_name, *unique1_name, *unique2_name; + + /* + * Get the collection name from the command line. This is a ccache name + * with collection semantics, like DIR:/path/to/directory. This test + * program assumes that the collection is empty to start with. + */ + assert(argc == 2); + collection_name = argv[1]; + + /* + * Set the default ccache for the context to be the collection name, so the + * library can find the collection. + */ + check(krb5_init_context(&ctx)); + check(krb5_cc_set_default_name(ctx, collection_name)); + + /* + * Resolve the collection name. Since the collection is empty, this should + * generate a subsidiary name of an uninitialized cache. Getting the name + * of the resulting cache should give us the subsidiary name, not the + * collection name. This resulting subsidiary name should be consistent if + * we resolve the collection name again, and the collection should still be + * empty since we haven't initialized the cache. + */ + check(krb5_cc_resolve(ctx, collection_name, &ccinitial)); + check(krb5_cc_get_full_name(ctx, ccinitial, &initial_primary_name)); + assert(strcmp(initial_primary_name, collection_name) != 0); + check_primary_name(collection_name, initial_primary_name); + check_collection(NULL, 0); + check_princ(collection_name, NULL); + check_princ(initial_primary_name, NULL); + + /* + * Before initializing the primary ccache, generate and initialize two + * unique caches of the collection's type. Check that the cache names + * resolve to the generated caches and appear in the collection. (They + * might appear before being initialized; that's not currently considered + * important). The primary cache for the collection should remain as the + * unitialized cache from the previous step. + */ + typename = krb5_cc_get_type(ctx, ccinitial); + check(krb5_cc_new_unique(ctx, typename, NULL, &ccu1)); + check(krb5_cc_get_full_name(ctx, ccu1, &unique1_name)); + check(krb5_parse_name(ctx, "princ1@X", &princ1)); + check(krb5_cc_initialize(ctx, ccu1, princ1)); + check_princ(unique1_name, princ1); + check_match(princ1, unique1_name); + check_collection(NULL, 1, unique1_name); + check(krb5_cc_new_unique(ctx, typename, NULL, &ccu2)); + check(krb5_cc_get_full_name(ctx, ccu2, &unique2_name)); + check(krb5_parse_name(ctx, "princ2@X", &princ2)); + check(krb5_cc_initialize(ctx, ccu2, princ2)); + check_princ(unique2_name, princ2); + check_match(princ1, unique1_name); + check_match(princ2, unique2_name); + check_collection(NULL, 2, unique1_name, unique2_name); + assert(strcmp(unique1_name, initial_primary_name) != 0); + assert(strcmp(unique1_name, collection_name) != 0); + assert(strcmp(unique2_name, initial_primary_name) != 0); + assert(strcmp(unique2_name, collection_name) != 0); + assert(strcmp(unique2_name, unique1_name) != 0); + check_primary_name(collection_name, initial_primary_name); + + /* + * Initialize the initial primary cache. Make sure it didn't change names, + * that the previously retrieved name and the collection name both resolve + * to the initialized cache, and that it now appears first in the + * collection. + */ + check(krb5_parse_name(ctx, "princ3@X", &princ3)); + check(krb5_cc_initialize(ctx, ccinitial, princ3)); + check_name(ccinitial, initial_primary_name); + check_princ(initial_primary_name, princ3); + check_princ(collection_name, princ3); + check_match(princ3, initial_primary_name); + check_collection(initial_primary_name, 2, unique1_name, unique2_name); + + /* + * Switch the primary cache to each cache we have open. One each switch, + * check the primary name, check that the collection resolves to the + * expected cache, and check that the new primary name appears first in the + * collection. + */ + check(krb5_cc_switch(ctx, ccu1)); + check_primary_name(collection_name, unique1_name); + check_princ(collection_name, princ1); + check_collection(unique1_name, 2, initial_primary_name, unique2_name); + check(krb5_cc_switch(ctx, ccu2)); + check_primary_name(collection_name, unique2_name); + check_princ(collection_name, princ2); + check_collection(unique2_name, 2, initial_primary_name, unique1_name); + check(krb5_cc_switch(ctx, ccinitial)); + check_primary_name(collection_name, initial_primary_name); + check_princ(collection_name, princ3); + check_collection(initial_primary_name, 2, unique1_name, unique2_name); + + /* + * Temporarily set the context default ccache to a subsidiary name, and + * check that iterating over the collection yields that subsidiary cache + * and no others. + */ + check(krb5_cc_set_default_name(ctx, unique1_name)); + check_collection(unique1_name, 0); + check(krb5_cc_set_default_name(ctx, collection_name)); + + /* + * Destroy the primary cache. Make sure this causes both the initial + * primary name and the collection name to resolve to an uninitialized + * cache. Make sure the primary name doesn't change and doesn't appear in + * the collection any more. + */ + check(krb5_cc_destroy(ctx, ccinitial)); + check_princ(initial_primary_name, NULL); + check_princ(collection_name, NULL); + check_primary_name(collection_name, initial_primary_name); + check_match(princ1, unique1_name); + check_match(princ2, unique2_name); + check_match(princ3, NULL); + check_collection(NULL, 2, unique1_name, unique2_name); + + /* + * Switch to the first unique cache after destroying the primary cache. + * Check that the collection name resolves to this cache and that the new + * primary name appears first in the collection. + */ + check(krb5_cc_switch(ctx, ccu1)); + check_primary_name(collection_name, unique1_name); + check_princ(collection_name, princ1); + check_collection(unique1_name, 1, unique2_name); + + /* + * Destroy the second unique cache (which is not the current primary), + * check that it is on longer initialized, and check that it no longer + * appears in the collection. Check that destroying the non-primary cache + * doesn't affect the primary name. + */ + check(krb5_cc_destroy(ctx, ccu2)); + check_princ(unique2_name, NULL); + check_match(princ2, NULL); + check_collection(unique1_name, 0); + check_primary_name(collection_name, unique1_name); + check_match(princ1, unique1_name); + check_princ(collection_name, princ1); + + /* + * Destroy the first unique cache. Check that the collection is empty and + * still has the same primary name. + */ + check(krb5_cc_destroy(ctx, ccu1)); + check_princ(unique1_name, NULL); + check_princ(collection_name, NULL); + check_primary_name(collection_name, unique1_name); + check_match(princ1, NULL); + check_collection(NULL, 0); + + krb5_free_string(ctx, initial_primary_name); + krb5_free_string(ctx, unique1_name); + krb5_free_string(ctx, unique2_name); + krb5_free_principal(ctx, princ1); + krb5_free_principal(ctx, princ2); + krb5_free_principal(ctx, princ3); + krb5_free_context(ctx); + return 0; +} diff --git a/src/lib/krb5/ccache/t_cccol.py b/src/lib/krb5/ccache/t_cccol.py index 8c459dd..e762625 100644 --- a/src/lib/krb5/ccache/t_cccol.py +++ b/src/lib/krb5/ccache/t_cccol.py @@ -1,6 +1,46 @@ #!/usr/bin/python from k5test import * +realm = K5Realm(create_kdb=False) + +keyctl = which('keyctl') +out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1) +test_keyring = (keyctl is not None and + 'Unknown credential cache type' not in out) + +# Run the collection test program against each collection-enabled type. +realm.run(['./t_cccol', 'DIR:' + os.path.join(realm.testdir, 'cc')]) +if test_keyring: + # Use the test directory as the collection name to avoid colliding + # with other build trees. + cname = realm.testdir + + # Remove any keys left behind by previous failed test runs. + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) + realm.run(['keyctl', 'purge', 'keyring', cname]) + out = realm.run(['keyctl', 'list', '@u']) + if ('keyring: _krb_' + cname + '\n') in out: + id = realm.run(['keyctl', 'search', '@u', 'keyring', '_krb_' + cname]) + realm.run(['keyctl', 'unlink', id.strip(), '@u']) + + # Run test program over each subtype, cleaning up as we go. Don't + # test the persistent subtype, since it supports only one + # collection and might be in actual use. + realm.run(['./t_cccol', 'KEYRING:' + cname]) + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) + realm.run(['./t_cccol', 'KEYRING:legacy:' + cname]) + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) + realm.run(['./t_cccol', 'KEYRING:session:' + cname]) + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) + realm.run(['./t_cccol', 'KEYRING:user:' + cname]) + id = realm.run(['keyctl', 'search', '@u', 'keyring', '_krb_' + cname]) + realm.run(['keyctl', 'unlink', id.strip(), '@u']) + realm.run(['./t_cccol', 'KEYRING:process:abcd']) + realm.run(['./t_cccol', 'KEYRING:thread:abcd']) + +realm.stop() + +# Test cursor semantics using real ccaches. realm = K5Realm(create_host=False) realm.addprinc('alice', password('alice')) @@ -11,12 +51,25 @@ dccname = 'DIR:%s' % ccdir duser = 'DIR::%s/tkt1' % ccdir dalice = 'DIR::%s/tkt2' % ccdir dbob = 'DIR::%s/tkt3' % ccdir +dnoent = 'DIR::%s/noent' % ccdir realm.kinit('user', password('user'), flags=['-c', duser]) realm.kinit('alice', password('alice'), flags=['-c', dalice]) realm.kinit('bob', password('bob'), flags=['-c', dbob]) +if test_keyring: + cname = realm.testdir + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) + krccname = 'KEYRING:session:' + cname + kruser = '%s:tkt1' % krccname + kralice = '%s:tkt2' % krccname + krbob = '%s:tkt3' % krccname + krnoent = '%s:noent' % krccname + realm.kinit('user', password('user'), flags=['-c', kruser]) + realm.kinit('alice', password('alice'), flags=['-c', kralice]) + realm.kinit('bob', password('bob'), flags=['-c', krbob]) + def cursor_test(testname, args, expected): - outlines = realm.run_as_client(['./t_cccursor'] + args).splitlines() + outlines = realm.run(['./t_cccursor'] + args).splitlines() outlines.sort() expected.sort() if outlines != expected: @@ -30,21 +83,33 @@ cursor_test('file-default2', [realm.ccache], [fccname]) cursor_test('file-default3', [fccname], [fccname]) cursor_test('dir', [dccname], [duser, dalice, dbob]) +cursor_test('dir-subsidiary', [duser], [duser]) +cursor_test('dir-nofile', [dnoent], []) + +if test_keyring: + cursor_test('keyring', [krccname], [kruser, kralice, krbob]) + cursor_test('keyring-subsidiary', [kruser], [kruser]) + cursor_test('keyring-noent', [krnoent], []) mfoo = 'MEMORY:foo' mbar = 'MEMORY:bar' cursor_test('filemem', [fccname, mfoo, mbar], [fccname, mfoo, mbar]) cursor_test('dirmem', [dccname, mfoo], [duser, dalice, dbob, mfoo]) +if test_keyring: + cursor_test('keyringmem', [krccname, mfoo], [kruser, kralice, krbob, mfoo]) # Test krb5_cccol_have_content. -realm.run_as_client(['./t_cccursor', dccname, 'CONTENT']) -realm.run_as_client(['./t_cccursor', fccname, 'CONTENT']) -realm.run_as_client(['./t_cccursor', realm.ccache, 'CONTENT']) -realm.run_as_client(['./t_cccursor', mfoo, 'CONTENT'], expected_code=1) +realm.run(['./t_cccursor', dccname, 'CONTENT']) +realm.run(['./t_cccursor', fccname, 'CONTENT']) +realm.run(['./t_cccursor', realm.ccache, 'CONTENT']) +realm.run(['./t_cccursor', mfoo, 'CONTENT'], expected_code=1) +if test_keyring: + realm.run(['./t_cccursor', krccname, 'CONTENT']) + realm.run(['keyctl', 'purge', 'keyring', '_krb_' + cname]) # Make sure FILE doesn't yield a nonexistent default cache. -realm.run_as_client([kdestroy]) +realm.run([kdestroy]) cursor_test('noexist', [], []) -realm.run_as_client(['./t_cccursor', fccname, 'CONTENT'], expected_code=1) +realm.run(['./t_cccursor', fccname, 'CONTENT'], expected_code=1) success('Renewing credentials') diff --git a/src/util/k5test.py b/src/util/k5test.py index 3400154..aead832 100644 --- a/src/util/k5test.py +++ b/src/util/k5test.py @@ -142,6 +133,9 @@ Scripts may use the following functions and variables: added newline) in testlog, and write it to stdout if running verbosely. +* which(progname): Return the location of progname in the executable + path, or None if it is not found. + * password(name): Return a weakly random password based on name. The password will be consistent across calls with the same name. @@ -388,6 +374,16 @@ def output(msg, force_verbose=False): sys.stdout.write(msg) +# Return the location of progname in the executable path, or None if +# it is not found. +def which(progname): + for dir in os.environ["PATH"].split(os.pathsep): + path = os.path.join(dir, progname) + if os.access(path, os.X_OK): + return path + return None + + def password(name): """Choose a weakly random password from name, consistent across calls.""" return name + str(os.getpid()) @@ -880,6 +880,11 @@ class K5Realm(object): env['KPROP_PORT'] = str(self.portbase + 3) return env + def run(self, args, env=None, **keywords): + if env is None: + env = self.env_client + return _run_cmd(args, env, **keywords) + def run_as_client(self, args, **keywords): return _run_cmd(args, self.env_client, **keywords) diff --git a/src/lib/krb5/ccache/Makefile.in b/src/lib/krb5/ccache/Makefile.in index f64226b..ad53e65 100644 --- a/src/lib/krb5/ccache/Makefile.in +++ b/src/lib/krb5/ccache/Makefile.in @@ -71,6 +66,7 @@ SRCS= $(srcdir)/ccbase.c \ EXTRADEPSRCS= \ $(srcdir)/t_cc.c \ + $(srcdir)/t_cccol.c \ $(srcdir)/t_cccursor.c ##DOS##OBJS=$(OBJS) $(OUTPRE)ccfns.$(OBJEXT) @@ -108,6 +104,10 @@ T_CC_OBJS=t_cc.o t_cc: $(T_CC_OBJS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o t_cc $(T_CC_OBJS) $(KRB5_BASE_LIBS) +T_CCCOL_OBJS = t_cccol.o +t_cccol: $(T_CCCOL_OBJS) $(KRB5_BASE_DEPLIBS) + $(CC_LINK) -o $@ $(T_CCCOL_OBJS) $(KRB5_BASE_LIBS) + T_CCCURSOR_OBJS = t_cccursor.o t_cccursor: $(T_CCCURSOR_OBJS) $(KRB5_BASE_DEPLIBS) $(CC_LINK) -o $@ $(T_CCCURSOR_OBJS) $(KRB5_BASE_LIBS) @@ -116,11 +116,11 @@ check-unix:: t_cc KRB5_CONFIG=$(srcdir)/t_krb5.conf ; export KRB5_CONFIG ;\ $(RUN_SETUP) $(VALGRIND) ./t_cc -check-pytests:: t_cccursor +check-pytests:: t_cccursor t_cccol $(RUNPYTEST) $(srcdir)/t_cccol.py $(PYTESTFLAGS) clean-unix:: - $(RM) t_cc t_cc.o t_cccursor t_cccursor.o + $(RM) t_cc t_cc.o t_cccursor t_cccursor.o t_cccol t_cccol.o ##WIN32## $(OUTPRE)cc_mslsa.$(OBJEXT): cc_mslsa.c $(top_srcdir)/include/k5-int.h $(BUILDTOP)/include/krb5/osconf.h $(BUILDTOP)/include/krb5/autoconf.h $(BUILDTOP)/include/krb5.h $(COM_ERR_DEPS)