From 3f5a348287646d65700854650fe668b9c4249013 Mon Sep 17 00:00:00 2001 From: Greg Hudson Date: Fri, 3 Sep 2021 11:36:01 -0400 Subject: [PATCH] Improve gss_store_cred() behavior Select an output credential cache using similar logic to kinit. Do not require the target cache to be initialized. Try to use the per-thread cache set by gss_krb5_ccache_name() if no output cache was specified via a cred store. When the destination is a collection, honor the default_cred flag by switching the primary cache to the selected output cache. When the destination is not a collection, ignore the default_cred flag. (Previously the default_cred flag was mandatory for gss_store_cred() even though it is an advisory flag, and ignored for gss_store_cred_into() even if no ccache was specified in the cred store.) Honor the overwrite_cred flag by refusing to replace an initialized cache if it is not set. Stop using gss_acquire_cred() for this purpose as it could go out and fetch credentials from a client keytab. Perform atomic replacement of the target cache when possible, using krb5_cc_move(). Add a test harness for calling gss_store_cred() or gss_store_cred_into() and a suite of tests. Fix a broken trace log message for krb5_cc_move() and update the expected trace logs for an existing t_credstore.py test. ticket: 8010 --- .gitignore | 1 + doc/appdev/gssapi.rst | 15 +++- src/include/k5-trace.h | 2 +- src/lib/gssapi/krb5/store_cred.c | 145 ++++++++++++++----------------- src/tests/gssapi/Makefile.in | 14 +-- src/tests/gssapi/t_credstore.py | 4 +- src/tests/gssapi/t_store_cred.c | 114 ++++++++++++++++++++++++ src/tests/gssapi/t_store_cred.py | 80 +++++++++++++++++ 8 files changed, 285 insertions(+), 90 deletions(-) create mode 100644 src/tests/gssapi/t_store_cred.c create mode 100644 src/tests/gssapi/t_store_cred.py diff --git a/.gitignore b/.gitignore index 45c8bbabe9..a1ba832632 100644 --- a/.gitignore +++ b/.gitignore @@ -470,6 +470,7 @@ local.properties /src/tests/gssapi/t_saslname /src/tests/gssapi/t_spnego /src/tests/gssapi/t_srcattrs +/src/tests/gssapi/t_store_cred /src/tests/gssapi/t_inq_ctx /src/tests/hammer/kdc5_hammer diff --git a/doc/appdev/gssapi.rst b/doc/appdev/gssapi.rst index d26c9fe86a..339fd6c7c1 100644 --- a/doc/appdev/gssapi.rst +++ b/doc/appdev/gssapi.rst @@ -252,10 +252,8 @@ The following options are supported by the krb5 mechanism: * **ccache**: For acquiring initiator credentials, the name of the :ref:`credential cache ` to which the handle will - refer. For storing credentials, the name of the cache where the - credentials should be stored. If a collection name is given, the - primary cache of the collection will be used; this behavior may - change in future releases to select a cache from the collection. + refer. For storing credentials, the name of the cache or collection + where the credentials will be stored (see below). * **client_keytab**: For acquiring initiator credentials, the name of the :ref:`keytab ` which will be used, if @@ -285,6 +283,15 @@ The following options are supported by the krb5 mechanism: the empty string. If the empty string is given, any ``host`` service principal in the keytab may be used. (New in release 1.19.) +In release 1.20 or later, if a collection name is specified for +**cache** in a call to gss_store_cred_into(), an existing cache for +the client principal within the collection will be selected, or a new +cache will be created within the collection. If *overwrite_cred* is +false and the selected credential cache already exists, a +**GSS_S_DUPLICATE_ELEMENT** error will be returned. If *default_cred* +is true, the primary cache of the collection will be switched to the +selected cache. + Importing and exporting credentials ----------------------------------- diff --git a/src/include/k5-trace.h b/src/include/k5-trace.h index 79b5a7a854..7bf0c45993 100644 --- a/src/include/k5-trace.h +++ b/src/include/k5-trace.h @@ -119,7 +119,7 @@ void krb5int_trace(krb5_context context, const char *fmt, ...); TRACE(c, "Initializing {ccache} with default princ {princ}", \ cache, princ) #define TRACE_CC_MOVE(c, src, dst) \ - TRACE(c, "Moving contents of ccache {src} to {dst}", src, dst) + TRACE(c, "Moving ccache {ccache} to {ccache}", src, dst) #define TRACE_CC_NEW_UNIQUE(c, type) \ TRACE(c, "Resolving unique ccache of type {str}", type) #define TRACE_CC_REMOVE(c, cache, creds) \ diff --git a/src/lib/gssapi/krb5/store_cred.c b/src/lib/gssapi/krb5/store_cred.c index 96eb1c9e34..1e168bc5da 100644 --- a/src/lib/gssapi/krb5/store_cred.c +++ b/src/lib/gssapi/krb5/store_cred.c @@ -27,36 +27,6 @@ #include "k5-int.h" #include "gssapiP_krb5.h" -static int -has_unexpired_creds(krb5_gss_cred_id_t kcred, - const gss_OID desired_mech, - int default_cred, - gss_const_key_value_set_t cred_store) -{ - OM_uint32 major_status, minor; - gss_name_t cred_name; - gss_OID_set_desc desired_mechs; - gss_cred_id_t tmp_cred = GSS_C_NO_CREDENTIAL; - OM_uint32 time_rec; - - desired_mechs.count = 1; - desired_mechs.elements = (gss_OID)desired_mech; - - if (default_cred) - cred_name = GSS_C_NO_NAME; - else - cred_name = (gss_name_t)kcred->name; - - major_status = krb5_gss_acquire_cred_from(&minor, cred_name, 0, - &desired_mechs, GSS_C_INITIATE, - cred_store, &tmp_cred, NULL, - &time_rec); - - krb5_gss_release_cred(&minor, &tmp_cred); - - return (GSS_ERROR(major_status) || time_rec); -} - static OM_uint32 copy_initiator_creds(OM_uint32 *minor_status, gss_cred_id_t input_cred_handle, @@ -66,26 +36,19 @@ copy_initiator_creds(OM_uint32 *minor_status, gss_const_key_value_set_t cred_store) { OM_uint32 major_status; - krb5_error_code code; + krb5_error_code ret; krb5_gss_cred_id_t kcred = NULL; krb5_context context = NULL; - krb5_ccache ccache = NULL; - const char *ccache_name; + krb5_ccache cache = NULL, defcache = NULL, mcc = NULL; + krb5_principal princ = NULL; + krb5_boolean switch_to_cache = FALSE; + const char *ccache_name, *deftype; *minor_status = 0; - if (!default_cred && cred_store == GSS_C_NO_CRED_STORE) { - *minor_status = G_STORE_NON_DEFAULT_CRED_NOSUPP; - major_status = GSS_S_FAILURE; - goto cleanup; - } - - code = krb5_gss_init_context(&context); - if (code != 0) { - *minor_status = code; - major_status = GSS_S_FAILURE; - goto cleanup; - } + ret = krb5_gss_init_context(&context); + if (ret) + goto kerr_cleanup; major_status = krb5_gss_validate_cred_1(minor_status, input_cred_handle, @@ -101,52 +64,69 @@ copy_initiator_creds(OM_uint32 *minor_status, goto cleanup; } - if (!overwrite_cred && - has_unexpired_creds(kcred, desired_mech, default_cred, cred_store)) { - major_status = GSS_S_DUPLICATE_ELEMENT; - goto cleanup; - } - major_status = kg_value_from_cred_store(cred_store, KRB5_CS_CCACHE_URN, &ccache_name); if (GSS_ERROR(major_status)) goto cleanup; if (ccache_name != NULL) { - code = krb5_cc_resolve(context, ccache_name, &ccache); - if (code != 0) { - *minor_status = code; - major_status = GSS_S_FAILURE; + ret = krb5_cc_set_default_name(context, ccache_name); + if (ret) + goto kerr_cleanup; + } else { + major_status = kg_sync_ccache_name(context, minor_status); + if (major_status != GSS_S_COMPLETE) goto cleanup; - } - code = krb5_cc_initialize(context, ccache, - kcred->name->princ); - if (code != 0) { - *minor_status = code; - major_status = GSS_S_FAILURE; - goto cleanup; - } } - if (ccache == NULL) { - if (!default_cred) { - *minor_status = G_STORE_NON_DEFAULT_CRED_NOSUPP; - major_status = GSS_S_FAILURE; + /* Resolve the default ccache and get its type. */ + ret = krb5_cc_default(context, &defcache); + if (ret) + goto kerr_cleanup; + deftype = krb5_cc_get_type(context, defcache); + + if (krb5_cc_support_switch(context, deftype)) { + /* Use an existing or new cache within the collection. */ + ret = krb5_cc_cache_match(context, kcred->name->princ, &cache); + if (!ret && !overwrite_cred) { + major_status = GSS_S_DUPLICATE_ELEMENT; goto cleanup; } - code = krb5int_cc_default(context, &ccache); - if (code != 0) { - *minor_status = code; - major_status = GSS_S_FAILURE; + if (ret == KRB5_CC_NOTFOUND) + ret = krb5_cc_new_unique(context, deftype, NULL, &cache); + if (ret) + goto kerr_cleanup; + switch_to_cache = default_cred; + } else { + /* Use the default cache. */ + cache = defcache; + defcache = NULL; + ret = krb5_cc_get_principal(context, cache, &princ); + krb5_free_principal(context, princ); + if (!ret && !overwrite_cred) { + major_status = GSS_S_DUPLICATE_ELEMENT; goto cleanup; } } - code = krb5_cc_copy_creds(context, kcred->ccache, ccache); - if (code != 0) { - *minor_status = code; - major_status = GSS_S_FAILURE; - goto cleanup; + ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc); + if (ret) + goto kerr_cleanup; + ret = krb5_cc_initialize(context, mcc, kcred->name->princ); + if (ret) + goto kerr_cleanup; + ret = krb5_cc_copy_creds(context, kcred->ccache, mcc); + if (ret) + goto kerr_cleanup; + ret = krb5_cc_move(context, mcc, cache); + if (ret) + goto kerr_cleanup; + mcc = NULL; + + if (switch_to_cache) { + ret = krb5_cc_switch(context, cache); + if (ret) + goto kerr_cleanup; } *minor_status = 0; @@ -155,11 +135,20 @@ copy_initiator_creds(OM_uint32 *minor_status, cleanup: if (kcred != NULL) k5_mutex_unlock(&kcred->lock); - if (ccache != NULL) - krb5_cc_close(context, ccache); + if (defcache != NULL) + krb5_cc_close(context, defcache); + if (cache != NULL) + krb5_cc_close(context, cache); + if (mcc != NULL) + krb5_cc_destroy(context, mcc); krb5_free_context(context); return major_status; + +kerr_cleanup: + *minor_status = ret; + major_status = GSS_S_FAILURE; + goto cleanup; } OM_uint32 KRB5_CALLCONV diff --git a/src/tests/gssapi/Makefile.in b/src/tests/gssapi/Makefile.in index 23f7d0e9bb..4cac8cb2d3 100644 --- a/src/tests/gssapi/Makefile.in +++ b/src/tests/gssapi/Makefile.in @@ -19,7 +19,7 @@ SRCS= $(srcdir)/ccinit.c $(srcdir)/ccrefresh.c $(srcdir)/common.c \ $(srcdir)/t_lifetime.c $(srcdir)/t_namingexts.c $(srcdir)/t_oid.c \ $(srcdir)/t_pcontok.c $(srcdir)/t_prf.c $(srcdir)/t_s4u.c \ $(srcdir)/t_s4u2proxy_krb5.c $(srcdir)/t_saslname.c \ - $(srcdir)/t_spnego.c $(srcdir)/t_srcattrs.c + $(srcdir)/t_spnego.c $(srcdir)/t_srcattrs.c $(srcdir)/t_store_cred.c OBJS= ccinit.o ccrefresh.o common.o reload.o t_accname.o t_add_cred.o \ t_bindings.o t_ccselect.o t_ciflags.o t_context.o t_credstore.o \ @@ -27,7 +27,7 @@ OBJS= ccinit.o ccrefresh.o common.o reload.o t_accname.o t_add_cred.o \ t_imp_cred.o t_imp_name.o t_invalid.o t_inq_cred.o t_inq_ctx.o \ t_inq_mechs_name.o t_iov.o t_lifetime.o t_namingexts.o t_oid.o \ t_pcontok.o t_prf.o t_s4u.o t_s4u2proxy_krb5.o t_saslname.o \ - t_spnego.o t_srcattrs.o + t_spnego.o t_srcattrs.o t_store_cred.o COMMON_DEPS= common.o $(GSS_DEPLIBS) $(KRB5_BASE_DEPLIBS) COMMON_LIBS= common.o $(GSS_LIBS) $(KRB5_BASE_LIBS) @@ -36,7 +36,7 @@ all: ccinit ccrefresh t_accname t_add_cred t_bindings t_ccselect t_ciflags \ t_context t_credstore t_enctypes t_err t_export_cred t_export_name \ t_gssexts t_imp_cred t_imp_name t_invalid t_inq_cred t_inq_ctx \ t_inq_mechs_name t_iov t_lifetime t_namingexts t_oid t_pcontok t_prf \ - t_s4u t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs + t_s4u t_s4u2proxy_krb5 t_saslname t_spnego t_srcattrs t_store_cred check-unix: t_oid reload $(RUN_TEST) ./t_invalid @@ -48,8 +48,10 @@ check-unix: t_oid reload check-pytests: ccinit ccrefresh t_accname t_add_cred t_bindings t_ccselect \ t_ciflags t_context t_credstore t_enctypes t_err t_export_cred \ t_export_name t_imp_cred t_inq_cred t_inq_ctx t_inq_mechs_name t_iov \ - t_lifetime t_pcontok t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs + t_lifetime t_pcontok t_s4u t_s4u2proxy_krb5 t_spnego t_srcattrs \ + t_store_cred $(RUNPYTEST) $(srcdir)/t_gssapi.py $(PYTESTFLAGS) + $(RUNPYTEST) $(srcdir)/t_store_cred.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_credstore.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_bindings.py $(PYTESTFLAGS) $(RUNPYTEST) $(srcdir)/t_ccselect.py $(PYTESTFLAGS) @@ -124,6 +126,8 @@ t_spnego: t_spnego.o $(COMMON_DEPS) $(CC_LINK) -o $@ t_spnego.o $(COMMON_LIBS) t_srcattrs: t_srcattrs.o $(COMMON_DEPS) $(CC_LINK) -o $@ t_srcattrs.o $(COMMON_LIBS) +t_store_cred: t_store_cred.o $(COMMON_DEPS) + $(CC_LINK) -o $@ t_store_cred.o $(COMMON_LIBS) clean: $(RM) ccinit ccrefresh reload t_accname t_add_cred t_bindings @@ -131,4 +135,4 @@ clean: $(RM) t_export_cred t_export_name t_gssexts t_imp_cred t_imp_name $(RM) t_invalid t_inq_cred t_inq_ctx t_inq_mechs_name t_iov t_lifetime $(RM) t_namingexts t_oid t_pcontok t_prf t_s4u t_s4u2proxy_krb5 - $(RM) t_saslname t_spnego t_srcattrs + $(RM) t_saslname t_spnego t_srcattrs t_store_cred diff --git a/src/tests/gssapi/t_credstore.py b/src/tests/gssapi/t_credstore.py index f666e5e207..ec59dd8dac 100644 --- a/src/tests/gssapi/t_credstore.py +++ b/src/tests/gssapi/t_credstore.py @@ -9,8 +9,8 @@ service_cs = 'service/cs@%s' % realm.realm realm.addprinc(service_cs) realm.extract_keytab(service_cs, servicekeytab) realm.kinit(service_cs, None, ['-k', '-t', servicekeytab]) -msgs = ('Storing %s -> %s in %s' % (service_cs, realm.krbtgt_princ, - storagecache), +msgs = ('Storing %s -> %s in MEMORY:' % (service_cs, realm.krbtgt_princ), + 'Moving ccache MEMORY:', 'Retrieving %s from FILE:%s' % (service_cs, servicekeytab)) realm.run(['./t_credstore', '-s', 'p:' + service_cs, 'ccache', storagecache, 'keytab', servicekeytab], expected_trace=msgs) diff --git a/src/tests/gssapi/t_store_cred.c b/src/tests/gssapi/t_store_cred.c new file mode 100644 index 0000000000..b053e52438 --- /dev/null +++ b/src/tests/gssapi/t_store_cred.c @@ -0,0 +1,114 @@ +/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ +/* tests/gssapi/t_store_cred.c - gss_store_cred() test harness */ +/* + * Copyright (C) 2021 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. + */ + +/* + * Usage: t_store_cred [-d] [-i] [-o] src_ccname [dest_ccname] + * + * Acquires creds from src_ccname using gss_acquire_cred_from() and then stores + * them, using gss_store_cred_into() if -i is specified or gss_store_cred() + * otherwise. If dest_ccname is specified with -i, it is included in the cred + * store for the store operation; if it is specified without -i, it is set with + * gss_krb5_ccache_name() before the store operation. If -d and/or -o are + * specified they set the default_cred and overwrite_cred flags to true + * respectively. + */ + +#include "k5-platform.h" +#include +#include "common.h" + +int +main(int argc, char *argv[]) +{ + OM_uint32 major, minor; + gss_key_value_set_desc store; + gss_key_value_element_desc elem; + gss_cred_id_t cred; + krb5_boolean def = FALSE, into = FALSE, overwrite = FALSE; + const char *src_ccname, *dest_ccname; + int c; + + /* Parse arguments. */ + while ((c = getopt(argc, argv, "dio")) != -1) { + switch (c) { + case 'd': + def = TRUE; + break; + case 'i': + into = TRUE; + break; + case 'o': + overwrite = TRUE; + break; + default: + abort(); + } + } + argc -= optind; + argv += optind; + assert(argc == 1 || argc == 2); + src_ccname = argv[0]; + dest_ccname = argv[1]; + + elem.key = "ccache"; + elem.value = src_ccname; + store.count = 1; + store.elements = &elem; + major = gss_acquire_cred_from(&minor, GSS_C_NO_NAME, GSS_C_INDEFINITE, + &mechset_krb5, GSS_C_INITIATE, &store, &cred, + NULL, NULL); + check_gsserr("acquire_cred", major, minor); + + if (into) { + if (dest_ccname != NULL) { + elem.key = "ccache"; + elem.value = dest_ccname; + store.count = 1; + } else { + store.count = 0; + } + major = gss_store_cred_into(&minor, cred, GSS_C_INITIATE, &mech_krb5, + overwrite, def, &store, NULL, NULL); + check_gsserr("store_cred_into", major, minor); + } else { + if (dest_ccname != NULL) { + major = gss_krb5_ccache_name(&minor, dest_ccname, NULL); + check_gsserr("ccache_name", major, minor); + } + major = gss_store_cred(&minor, cred, GSS_C_INITIATE, &mech_krb5, + overwrite, def, NULL, NULL); + check_gsserr("store_cred", major, minor); + } + + gss_release_cred(&minor, &cred); + return 0; +} diff --git a/src/tests/gssapi/t_store_cred.py b/src/tests/gssapi/t_store_cred.py new file mode 100644 index 0000000000..00e80cf38d --- /dev/null +++ b/src/tests/gssapi/t_store_cred.py @@ -0,0 +1,80 @@ +from k5test import * + +realm = K5Realm(create_user=False) + +alice = 'alice@' + realm.realm +bob = 'bob@' + realm.realm +cc_alice = realm.ccache + '.alice' +cc_bob = realm.ccache + '.bob' +realm.addprinc(alice) +realm.addprinc(bob) +realm.extract_keytab(alice, realm.keytab) +realm.extract_keytab(bob, realm.keytab) +realm.kinit(alice, flags=['-k', '-c', cc_alice]) +realm.kinit(bob, flags=['-k', '-c', cc_bob]) + +mark('FILE, default output ccache') +realm.run(['./t_store_cred', cc_alice]) +realm.klist(alice) +# Overwriting should fail by default, whether or not the principal matches. +realm.run(['./t_store_cred', cc_alice], expected_code=1, + expected_msg='The requested credential element already exists') +realm.run(['./t_store_cred', cc_bob], expected_code=1, + expected_msg='The requested credential element already exists') +# Overwriting should succeed with overwrite_cred set. +realm.run(['./t_store_cred', '-o', cc_bob]) +realm.klist(bob) +# default_cred has no effect without a collection. +realm.run(['./t_store_cred', '-d', '-o', cc_alice]) +realm.klist(alice) + +mark('FILE, gss_krb5_ccache_name()') +cc_alternate = realm.ccache + '.alternate' +realm.run(['./t_store_cred', cc_alice, cc_alternate]) +realm.klist(alice, ccache=cc_alternate) +realm.run(['./t_store_cred', cc_bob, cc_alternate], expected_code=1, + expected_msg='The requested credential element already exists') + +mark('FILE, gss_store_cred_into()') +os.remove(cc_alternate) +realm.run(['./t_store_cred', '-i', cc_alice, cc_alternate]) +realm.klist(alice, ccache=cc_alternate) +realm.run(['./t_store_cred', '-i', cc_bob, cc_alternate], expected_code=1, + expected_msg='The requested credential element already exists') + +mark('DIR, gss_krb5_ccache_name()') +cc_dir = 'DIR:' + os.path.join(realm.testdir, 'cc') +realm.run(['./t_store_cred', cc_alice, cc_dir]) +realm.run([klist, '-c', cc_dir], expected_code=1, + expected_msg='No credentials cache found') +realm.run([klist, '-l', '-c', cc_dir], expected_msg=alice) +realm.run(['./t_store_cred', cc_alice, cc_dir], expected_code=1, + expected_msg='The requested credential element already exists') +realm.run(['./t_store_cred', '-o', cc_alice, cc_dir]) +realm.run([klist, '-c', cc_dir], expected_code=1, + expected_msg='No credentials cache found') +realm.run([klist, '-l', cc_dir], expected_msg=alice) +realm.run(['./t_store_cred', '-d', cc_bob, cc_dir]) +# The k5test klist method does not currently work with a collection name. +realm.run([klist, cc_dir], expected_msg=bob) +realm.run([klist, '-l', cc_dir], expected_msg=alice) +realm.run(['./t_store_cred', '-o', '-d', cc_alice, cc_dir]) +realm.run([klist, cc_dir], expected_msg=alice) +realm.run([kdestroy, '-A', '-c', cc_dir]) + +mark('DIR, gss_store_cred_into()') +realm.run(['./t_store_cred', '-i', cc_alice, cc_dir]) +realm.run(['./t_store_cred', '-i', '-d', cc_bob, cc_dir]) +realm.run([klist, cc_dir], expected_msg=bob) +realm.run([klist, '-l', cc_dir], expected_msg=alice) +realm.run([kdestroy, '-A', '-c', cc_dir]) + +mark('DIR, default output ccache') +realm.ccache = cc_dir +realm.env['KRB5CCNAME'] = cc_dir +realm.run(['./t_store_cred', '-i', cc_alice, cc_dir]) +realm.run(['./t_store_cred', '-i', '-d', cc_bob, cc_dir]) +realm.run([klist], expected_msg=bob) +realm.run([klist, '-l'], expected_msg=alice) + +success('gss_store_cred() tests') -- 2.47.2