krb5 commit: Perform atomic ccache refreshes when possible
Greg Hudson
ghudson at mit.edu
Wed Aug 25 12:13:41 EDT 2021
https://github.com/krb5/krb5/commit/371f09d4bf4ca0c7ba15c5ef909bc35307ed9cc3
commit 371f09d4bf4ca0c7ba15c5ef909bc35307ed9cc3
Author: Greg Hudson <ghudson at mit.edu>
Date: Tue Aug 17 11:26:59 2021 -0400
Perform atomic ccache refreshes when possible
Allow ccache types to implement atomic replacement via a new replace
method (replacing the unused "move" vtable slot). Make krb5_cc_move()
use this method when possible, falling back to non-atomic replacement.
Implement atomic replacement for FILE, DIR, MEMORY, and KCM (using a
new opcode, falling back when it is not implemented).
Use krb5_cc_move() in get_in_tkt.c when an output ccache is specified,
in kinit for ticket validation and renewal, and in kvno --out-cache.
Add a test program to exercise concurrent krb5_get_credentials() and
cache refresh.
This commit does not implement atomic replacement for KEYRING or for
gss_store_creds().
ticket: 7707
src/clients/kinit/kinit.c | 22 ++++-
src/clients/kvno/kvno.c | 24 ++++-
src/include/kcm.h | 2 +
src/lib/krb5/ccache/cc-int.h | 8 +-
src/lib/krb5/ccache/cc_dir.c | 11 ++-
src/lib/krb5/ccache/cc_file.c | 110 ++++++++++++++++++-----
src/lib/krb5/ccache/cc_kcm.c | 39 ++++++++-
src/lib/krb5/ccache/cc_memory.c | 100 +++++++++++++-------
src/lib/krb5/ccache/ccbase.c | 119 +++++++++++++++++-------
src/lib/krb5/krb/get_in_tkt.c | 83 ++++++++++++-----
src/lib/krb5/krb/t_vfy_increds.c | 15 ++-
src/tests/Makefile.in | 23 +++--
src/tests/conccache.c | 190 ++++++++++++++++++++++++++++++++++++++
src/tests/kcmserver.py | 74 ++++++++++-----
src/tests/t_ccache.py | 5 +
15 files changed, 661 insertions(+), 164 deletions(-)
diff --git a/src/clients/kinit/kinit.c b/src/clients/kinit/kinit.c
index 79775c2..f4c7b2b 100644
--- a/src/clients/kinit/kinit.c
+++ b/src/clients/kinit/kinit.c
@@ -645,6 +645,8 @@ k5_kinit(struct k_opts *opts, struct k5_data *k5)
krb5_get_init_creds_opt *options = NULL;
krb5_boolean pwprompt = FALSE;
krb5_address **addresses = NULL;
+ krb5_principal cprinc;
+ krb5_ccache mcc = NULL;
int i;
memset(&my_creds, 0, sizeof(my_creds));
@@ -793,21 +795,29 @@ k5_kinit(struct k_opts *opts, struct k5_data *k5)
}
if (opts->action != INIT_PW && opts->action != INIT_KT) {
- ret = krb5_cc_initialize(k5->ctx, k5->out_cc, opts->canonicalize ?
- my_creds.client : k5->me);
+ cprinc = opts->canonicalize ? my_creds.client : k5->me;
+ ret = krb5_cc_new_unique(k5->ctx, "MEMORY", NULL, &mcc);
+ if (!ret)
+ ret = krb5_cc_initialize(k5->ctx, mcc, cprinc);
if (ret) {
- com_err(progname, ret, _("when initializing cache %s"),
- opts->k5_out_cache_name ? opts->k5_out_cache_name : "");
+ com_err(progname, ret, _("when creating temporary cache"));
goto cleanup;
}
if (opts->verbose)
fprintf(stderr, _("Initialized cache\n"));
- ret = k5_cc_store_primary_cred(k5->ctx, k5->out_cc, &my_creds);
+ ret = k5_cc_store_primary_cred(k5->ctx, mcc, &my_creds);
if (ret) {
com_err(progname, ret, _("while storing credentials"));
goto cleanup;
}
+ ret = krb5_cc_move(k5->ctx, mcc, k5->out_cc);
+ if (ret) {
+ com_err(progname, ret, _("while saving to cache %s"),
+ opts->k5_out_cache_name ? opts->k5_out_cache_name : "");
+ goto cleanup;
+ }
+ mcc = NULL;
if (opts->verbose)
fprintf(stderr, _("Stored credentials\n"));
}
@@ -824,6 +834,8 @@ cleanup:
#ifndef _WIN32
kinit_kdb_fini();
#endif
+ if (mcc != NULL)
+ krb5_cc_destroy(k5->ctx, mcc);
if (options)
krb5_get_init_creds_opt_free(k5->ctx, options);
if (my_creds.client == k5->me)
diff --git a/src/clients/kvno/kvno.c b/src/clients/kvno/kvno.c
index 89b5ce9..7f8667c 100644
--- a/src/clients/kvno/kvno.c
+++ b/src/clients/kvno/kvno.c
@@ -454,7 +454,7 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
krb5_error_code ret;
int i, errors, flags, initialized = 0;
krb5_enctype etype;
- krb5_ccache ccache, out_ccache = NULL;
+ krb5_ccache ccache, mcc, out_ccache = NULL;
krb5_principal me;
krb5_keytab keytab = NULL;
krb5_principal for_user_princ = NULL;
@@ -545,6 +545,14 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
exit(1);
}
+ if (out_ccache != NULL) {
+ ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc);
+ if (ret) {
+ com_err(prog, ret, _("while creating temporary output ccache"));
+ exit(1);
+ }
+ }
+
errors = 0;
for (i = 0; i < count; i++) {
if (kvno(names[i], ccache, me, etype, keytab, sname, options, unknown,
@@ -552,7 +560,7 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
errors++;
} else if (out_ccache != NULL) {
if (!initialized) {
- ret = krb5_cc_initialize(context, out_ccache, creds->client);
+ ret = krb5_cc_initialize(context, mcc, creds->client);
if (ret) {
com_err(prog, ret, _("while initializing output ccache"));
exit(1);
@@ -560,9 +568,9 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
initialized = 1;
}
if (count == 1)
- ret = k5_cc_store_primary_cred(context, out_ccache, creds);
+ ret = k5_cc_store_primary_cred(context, mcc, creds);
else
- ret = krb5_cc_store_cred(context, out_ccache, creds);
+ ret = krb5_cc_store_cred(context, mcc, creds);
if (ret) {
com_err(prog, ret, _("while storing creds in output ccache"));
exit(1);
@@ -572,6 +580,14 @@ do_v5_kvno(int count, char *names[], char * ccachestr, char *etypestr,
krb5_free_creds(context, creds);
}
+ if (!errors && out_ccache != NULL) {
+ ret = krb5_cc_move(context, mcc, out_ccache);
+ if (ret) {
+ com_err(prog, ret, _("while writing output ccache"));
+ exit(1);
+ }
+ }
+
if (keytab != NULL)
krb5_kt_close(context, keytab);
krb5_free_principal(context, me);
diff --git a/src/include/kcm.h b/src/include/kcm.h
index 85c20d3..d5d74aa 100644
--- a/src/include/kcm.h
+++ b/src/include/kcm.h
@@ -113,6 +113,8 @@ typedef enum kcm_opcode {
/* MIT extensions */
KCM_OP_MIT_EXTENSION_BASE = 13000,
KCM_OP_GET_CRED_LIST, /* (name) -> (count, count*{len, cred}) */
+ KCM_OP_REPLACE, /* (name, offset, princ,
+ * count, count*{len, cred}) -> () */
} kcm_opcode;
#endif /* KCM_H */
diff --git a/src/lib/krb5/ccache/cc-int.h b/src/lib/krb5/ccache/cc-int.h
index 9fe6e1b..70f2827 100644
--- a/src/lib/krb5/ccache/cc-int.h
+++ b/src/lib/krb5/ccache/cc-int.h
@@ -51,6 +51,10 @@ krb5int_cc_initialize(void);
void
krb5int_cc_finalize(void);
+krb5_error_code
+k5_nonatomic_replace(krb5_context context, krb5_ccache ccache,
+ krb5_principal princ, krb5_creds **creds);
+
/*
* Cursor for iterating over ccache types
*/
@@ -210,8 +214,8 @@ struct _krb5_cc_ops {
krb5_ccache *);
krb5_error_code (KRB5_CALLCONV *ptcursor_free)(krb5_context,
krb5_cc_ptcursor *);
- krb5_error_code (KRB5_CALLCONV *move)(krb5_context, krb5_ccache,
- krb5_ccache);
+ krb5_error_code (KRB5_CALLCONV *replace)(krb5_context, krb5_ccache,
+ krb5_principal, krb5_creds **);
krb5_error_code (KRB5_CALLCONV *wasdefault)(krb5_context, krb5_ccache,
krb5_timestamp *);
krb5_error_code (KRB5_CALLCONV *lock)(krb5_context, krb5_ccache);
diff --git a/src/lib/krb5/ccache/cc_dir.c b/src/lib/krb5/ccache/cc_dir.c
index 7b100a0..1da40b5 100644
--- a/src/lib/krb5/ccache/cc_dir.c
+++ b/src/lib/krb5/ccache/cc_dir.c
@@ -692,6 +692,15 @@ dcc_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
}
static krb5_error_code KRB5_CALLCONV
+dcc_replace(krb5_context context, krb5_ccache cache, krb5_principal princ,
+ krb5_creds **creds)
+{
+ dcc_data *data = cache->data;
+
+ return krb5_fcc_ops.replace(context, data->fcc, princ, creds);
+}
+
+static krb5_error_code KRB5_CALLCONV
dcc_lock(krb5_context context, krb5_ccache cache)
{
dcc_data *data = cache->data;
@@ -752,7 +761,7 @@ const krb5_cc_ops krb5_dcc_ops = {
dcc_ptcursor_new,
dcc_ptcursor_next,
dcc_ptcursor_free,
- NULL, /* move */
+ dcc_replace,
NULL, /* wasdefault */
dcc_lock,
dcc_unlock,
diff --git a/src/lib/krb5/ccache/cc_file.c b/src/lib/krb5/ccache/cc_file.c
index 9a9b45a..c70a282 100644
--- a/src/lib/krb5/ccache/cc_file.c
+++ b/src/lib/krb5/ccache/cc_file.c
@@ -439,16 +439,40 @@ read_header(krb5_context context, FILE *fp, int *version_out)
return 0;
}
+static void
+marshal_header(krb5_context context, struct k5buf *buf, krb5_principal princ)
+{
+ krb5_os_context os_ctx = &context->os_context;
+ int version = context->fcc_default_format - FVNO_BASE;
+ uint16_t fields_len;
+
+ version = context->fcc_default_format - FVNO_BASE;
+ k5_buf_add_uint16_be(buf, FVNO_BASE + version);
+ if (version >= 4) {
+ /* Add tagged header fields. */
+ fields_len = 0;
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
+ fields_len += 12;
+ k5_buf_add_uint16_be(buf, fields_len);
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+ /* Add time offset tag. */
+ k5_buf_add_uint16_be(buf, FCC_TAG_DELTATIME);
+ k5_buf_add_uint16_be(buf, 8);
+ k5_buf_add_uint32_be(buf, os_ctx->time_offset);
+ k5_buf_add_uint32_be(buf, os_ctx->usec_offset);
+ }
+ }
+ k5_marshal_princ(buf, version, princ);
+}
+
/* Create or overwrite the cache file with a header and default principal. */
static krb5_error_code KRB5_CALLCONV
fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
{
krb5_error_code ret;
- krb5_os_context os_ctx = &context->os_context;
fcc_data *data = id->data;
- uint16_t fields_len;
ssize_t nwritten;
- int st, flags, version, fd = -1;
+ int st, flags, fd = -1;
struct k5buf buf = EMPTY_K5BUF;
krb5_boolean file_locked = FALSE;
@@ -482,23 +506,7 @@ fcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
/* Prepare the header and principal in buf. */
k5_buf_init_dynamic(&buf);
- version = context->fcc_default_format - FVNO_BASE;
- k5_buf_add_uint16_be(&buf, FVNO_BASE + version);
- if (version >= 4) {
- /* Add tagged header fields. */
- fields_len = 0;
- if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID)
- fields_len += 12;
- k5_buf_add_uint16_be(&buf, fields_len);
- if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
- /* Add time offset tag. */
- k5_buf_add_uint16_be(&buf, FCC_TAG_DELTATIME);
- k5_buf_add_uint16_be(&buf, 8);
- k5_buf_add_uint32_be(&buf, os_ctx->time_offset);
- k5_buf_add_uint32_be(&buf, os_ctx->usec_offset);
- }
- }
- k5_marshal_princ(&buf, version, princ);
+ marshal_header(context, &buf, princ);
ret = k5_buf_status(&buf);
if (ret)
goto cleanup;
@@ -1260,6 +1268,64 @@ fcc_unlock(krb5_context context, krb5_ccache id)
return 0;
}
+static krb5_error_code KRB5_CALLCONV
+fcc_replace(krb5_context context, krb5_ccache id, krb5_principal princ,
+ krb5_creds **creds)
+{
+ krb5_error_code ret;
+ fcc_data *data = id->data;
+ char *tmpname = NULL;
+ int i, st, fd = -1, version = context->fcc_default_format - FVNO_BASE;
+ ssize_t nwritten;
+ struct k5buf buf = EMPTY_K5BUF;
+ krb5_boolean tmpfile_exists = FALSE;
+
+ if (asprintf(&tmpname, "%s.XXXXXX", data->filename) < 0)
+ return ENOMEM;
+ fd = mkstemp(tmpname);
+ if (fd < 0)
+ goto errno_cleanup;
+ tmpfile_exists = TRUE;
+
+ k5_buf_init_dynamic_zap(&buf);
+ marshal_header(context, &buf, princ);
+ for (i = 0; creds[i] != NULL; i++)
+ k5_marshal_cred(&buf, version, creds[i]);
+ ret = k5_buf_status(&buf);
+ if (ret)
+ goto cleanup;
+
+ nwritten = write(fd, buf.data, buf.len);
+ if (nwritten == -1)
+ goto errno_cleanup;
+ if ((size_t)nwritten != buf.len) {
+ ret = KRB5_CC_IO;
+ goto cleanup;
+ }
+ st = close(fd);
+ fd = -1;
+ if (st != 0)
+ goto errno_cleanup;
+
+ st = rename(tmpname, data->filename);
+ if (st != 0)
+ goto errno_cleanup;
+ tmpfile_exists = FALSE;
+
+cleanup:
+ k5_buf_free(&buf);
+ if (fd != -1)
+ close(fd);
+ if (tmpfile_exists)
+ unlink(tmpname);
+ free(tmpname);
+ return ret;
+
+errno_cleanup:
+ ret = interpret_errno(context, errno);
+ goto cleanup;
+}
+
/* Translate a system errno value to a Kerberos com_err code. */
static krb5_error_code
interpret_errno(krb5_context context, int errnum)
@@ -1335,7 +1401,7 @@ const krb5_cc_ops krb5_fcc_ops = {
fcc_ptcursor_new,
fcc_ptcursor_next,
fcc_ptcursor_free,
- NULL, /* move */
+ fcc_replace,
NULL, /* wasdefault */
fcc_lock,
fcc_unlock,
@@ -1405,7 +1471,7 @@ const krb5_cc_ops krb5_cc_file_ops = {
fcc_ptcursor_new,
fcc_ptcursor_next,
fcc_ptcursor_free,
- NULL, /* move */
+ fcc_replace,
NULL, /* wasdefault */
fcc_lock,
fcc_unlock,
diff --git a/src/lib/krb5/ccache/cc_kcm.c b/src/lib/krb5/ccache/cc_kcm.c
index 18505cd..204454d 100644
--- a/src/lib/krb5/ccache/cc_kcm.c
+++ b/src/lib/krb5/ccache/cc_kcm.c
@@ -1235,6 +1235,43 @@ kcm_ptcursor_free(krb5_context context, krb5_cc_ptcursor *cursor)
}
static krb5_error_code KRB5_CALLCONV
+kcm_replace(krb5_context context, krb5_ccache cache, krb5_principal princ,
+ krb5_creds **creds)
+{
+ krb5_error_code ret;
+ struct kcmreq req = EMPTY_KCMREQ;
+ size_t pos;
+ uint8_t *lenptr;
+ int ncreds, i;
+ krb5_os_context octx = &context->os_context;
+ int32_t offset;
+
+ kcmreq_init(&req, KCM_OP_REPLACE, cache);
+ offset = (octx->os_flags & KRB5_OS_TOFFSET_VALID) ? octx->time_offset : 0;
+ k5_buf_add_uint32_be(&req.reqbuf, offset);
+ k5_marshal_princ(&req.reqbuf, 4, princ);
+ for (ncreds = 0; creds[ncreds] != NULL; ncreds++);
+ k5_buf_add_uint32_be(&req.reqbuf, ncreds);
+ for (i = 0; creds[i] != NULL; i++) {
+ /* Store a dummy length, then fix it up after marshalling the cred. */
+ pos = req.reqbuf.len;
+ k5_buf_add_uint32_be(&req.reqbuf, 0);
+ k5_marshal_cred(&req.reqbuf, 4, creds[i]);
+ if (k5_buf_status(&req.reqbuf) == 0) {
+ lenptr = (uint8_t *)req.reqbuf.data + pos;
+ store_32_be(req.reqbuf.len - (pos + 4), lenptr);
+ }
+ }
+ ret = cache_call(context, cache, &req);
+ kcmreq_free(&req);
+
+ if (unsupported_op_error(ret))
+ return k5_nonatomic_replace(context, cache, princ, creds);
+
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
kcm_lock(krb5_context context, krb5_ccache cache)
{
k5_cc_mutex_lock(context, &((struct kcm_cache_data *)cache->data)->lock);
@@ -1281,7 +1318,7 @@ const krb5_cc_ops krb5_kcm_ops = {
kcm_ptcursor_new,
kcm_ptcursor_next,
kcm_ptcursor_free,
- NULL, /* move */
+ kcm_replace,
NULL, /* wasdefault */
kcm_lock,
kcm_unlock,
diff --git a/src/lib/krb5/ccache/cc_memory.c b/src/lib/krb5/ccache/cc_memory.c
index 0897d6b..2df76ed 100644
--- a/src/lib/krb5/ccache/cc_memory.c
+++ b/src/lib/krb5/ccache/cc_memory.c
@@ -166,6 +166,45 @@ empty_mcc_cache(krb5_context context, krb5_mcc_data *d)
d->prin = NULL;
}
+/* Remove all creds from d and initialize it with princ as the default client
+ * principal. The caller is responsible for locking. */
+static krb5_error_code
+init_mcc_cache(krb5_context context, krb5_mcc_data *d, krb5_principal princ)
+{
+ krb5_os_context os_ctx = &context->os_context;
+
+ empty_mcc_cache(context, d);
+ if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
+ /* Store client time offsets in the cache. */
+ d->time_offset = os_ctx->time_offset;
+ d->usec_offset = os_ctx->usec_offset;
+ }
+ return krb5_copy_principal(context, princ, &d->prin);
+}
+
+/* Add cred to d. The caller is responsible for locking. */
+static krb5_error_code
+store_cred(krb5_context context, krb5_mcc_data *d, krb5_creds *cred)
+{
+ krb5_error_code ret;
+ krb5_mcc_link *new_node;
+
+ new_node = malloc(sizeof(*new_node));
+ if (new_node == NULL)
+ return ENOMEM;
+ new_node->next = NULL;
+ ret = krb5_copy_creds(context, cred, &new_node->creds);
+ if (ret) {
+ free(new_node);
+ return ret;
+ }
+
+ /* Place the new node at the tail of the list. */
+ *d->tail = new_node;
+ d->tail = &new_node->next;
+ return 0;
+}
+
/*
* Modifies:
* id
@@ -180,21 +219,11 @@ empty_mcc_cache(krb5_context context, krb5_mcc_data *d)
krb5_error_code KRB5_CALLCONV
krb5_mcc_initialize(krb5_context context, krb5_ccache id, krb5_principal princ)
{
- krb5_os_context os_ctx = &context->os_context;
krb5_error_code ret;
krb5_mcc_data *d = id->data;
k5_cc_mutex_lock(context, &d->lock);
- empty_mcc_cache(context, d);
-
- ret = krb5_copy_principal(context, princ, &d->prin);
-
- if (os_ctx->os_flags & KRB5_OS_TOFFSET_VALID) {
- /* Store client time offsets in the cache */
- d->time_offset = os_ctx->time_offset;
- d->usec_offset = os_ctx->usec_offset;
- }
-
+ ret = init_mcc_cache(context, d, princ);
k5_cc_mutex_unlock(context, &d->lock);
if (ret == KRB5_OK)
krb5_change_cache();
@@ -660,34 +689,19 @@ krb5_mcc_get_flags(krb5_context context, krb5_ccache id, krb5_flags *flags)
* Save away creds in the ccache.
*
* Errors:
- * system errors (mutex locking)
* ENOMEM
*/
krb5_error_code KRB5_CALLCONV
-krb5_mcc_store(krb5_context ctx, krb5_ccache id, krb5_creds *creds)
+krb5_mcc_store(krb5_context context, krb5_ccache id, krb5_creds *creds)
{
- krb5_error_code err;
- krb5_mcc_link *new_node;
- krb5_mcc_data *mptr = (krb5_mcc_data *)id->data;
-
- new_node = malloc(sizeof(krb5_mcc_link));
- if (new_node == NULL)
- return ENOMEM;
- new_node->next = NULL;
- err = krb5_copy_creds(ctx, creds, &new_node->creds);
- if (err)
- goto cleanup;
+ krb5_error_code ret;
+ krb5_mcc_data *d = id->data;
/* Place the new node at the tail of the list. */
- k5_cc_mutex_lock(ctx, &mptr->lock);
- *mptr->tail = new_node;
- mptr->tail = &new_node->next;
- k5_cc_mutex_unlock(ctx, &mptr->lock);
-
- return 0;
-cleanup:
- free(new_node);
- return err;
+ k5_cc_mutex_lock(context, &d->lock);
+ ret = store_cred(context, d, creds);
+ k5_cc_mutex_unlock(context, &d->lock);
+ return ret;
}
static krb5_error_code KRB5_CALLCONV
@@ -752,6 +766,24 @@ krb5_mcc_ptcursor_free(
}
static krb5_error_code KRB5_CALLCONV
+krb5_mcc_replace(krb5_context context, krb5_ccache id, krb5_principal princ,
+ krb5_creds **creds)
+{
+ krb5_error_code ret;
+ krb5_mcc_data *d = id->data;
+ int i;
+
+ k5_cc_mutex_lock(context, &d->lock);
+ ret = init_mcc_cache(context, d, princ);
+ for (i = 0; !ret && creds[i] != NULL; i++)
+ ret = store_cred(context, d, creds[i]);
+ k5_cc_mutex_unlock(context, &d->lock);
+ if (!ret)
+ krb5_change_cache();
+ return ret;
+}
+
+static krb5_error_code KRB5_CALLCONV
krb5_mcc_lock(krb5_context context, krb5_ccache id)
{
krb5_mcc_data *data = (krb5_mcc_data *) id->data;
@@ -790,7 +822,7 @@ const krb5_cc_ops krb5_mcc_ops = {
krb5_mcc_ptcursor_new,
krb5_mcc_ptcursor_next,
krb5_mcc_ptcursor_free,
- NULL, /* move */
+ krb5_mcc_replace,
NULL, /* wasdefault */
krb5_mcc_lock,
krb5_mcc_unlock,
diff --git a/src/lib/krb5/ccache/ccbase.c b/src/lib/krb5/ccache/ccbase.c
index f53ba50..8d5bcef 100644
--- a/src/lib/krb5/ccache/ccbase.c
+++ b/src/lib/krb5/ccache/ccbase.c
@@ -348,50 +348,103 @@ krb5int_cc_typecursor_free(krb5_context context, krb5_cc_typecursor *t)
return 0;
}
+krb5_error_code
+k5_nonatomic_replace(krb5_context context, krb5_ccache ccache,
+ krb5_principal princ, krb5_creds **creds)
+{
+ krb5_error_code ret;
+ int i;
+
+ ret = krb5_cc_initialize(context, ccache, princ);
+ for (i = 0; !ret && creds[i] != NULL; creds++)
+ ret = krb5_cc_store_cred(context, ccache, creds[i]);
+ return ret;
+}
+
+static krb5_error_code
+read_creds(krb5_context context, krb5_ccache ccache, krb5_creds ***creds_out)
+{
+ krb5_error_code ret;
+ krb5_cc_cursor cur = NULL;
+ krb5_creds **list = NULL, *cred = NULL, **newptr;
+ int i;
+
+ *creds_out = NULL;
+
+ ret = krb5_cc_start_seq_get(context, ccache, &cur);
+ if (ret)
+ goto cleanup;
+
+ /* Allocate one extra entry so that list remains valid for freeing after
+ * we add the next entry and before we reallocate it. */
+ list = k5calloc(2, sizeof(*list), &ret);
+ if (list == NULL)
+ goto cleanup;
+
+ i = 0;
+ for (;;) {
+ cred = k5alloc(sizeof(*cred), &ret);
+ if (cred == NULL)
+ goto cleanup;
+ ret = krb5_cc_next_cred(context, ccache, &cur, cred);
+ if (ret == KRB5_CC_END)
+ break;
+ if (ret)
+ goto cleanup;
+ list[i++] = cred;
+ list[i] = NULL;
+ cred = NULL;
+
+ newptr = realloc(list, (i + 2) * sizeof(*list));
+ if (newptr == NULL) {
+ ret = ENOMEM;
+ goto cleanup;
+ }
+ list = newptr;
+ list[i + 1] = NULL;
+ }
+ ret = 0;
+
+ *creds_out = list;
+ list = NULL;
+
+cleanup:
+ if (cur != NULL)
+ (void)krb5_cc_end_seq_get(context, ccache, &cur);
+ krb5_free_tgt_creds(context, list);
+ free(cred);
+ return ret;
+}
+
krb5_error_code KRB5_CALLCONV
krb5_cc_move(krb5_context context, krb5_ccache src, krb5_ccache dst)
{
- krb5_error_code ret = 0;
+ krb5_error_code ret;
krb5_principal princ = NULL;
+ krb5_creds **creds = NULL;
TRACE_CC_MOVE(context, src, dst);
- ret = k5_cccol_lock(context);
- if (ret) {
- return ret;
- }
-
- ret = k5_cc_lock(context, src);
- if (ret) {
- k5_cccol_unlock(context);
- return ret;
- }
ret = krb5_cc_get_principal(context, src, &princ);
- if (!ret) {
- ret = krb5_cc_initialize(context, dst, princ);
- }
- if (ret) {
- k5_cc_unlock(context, src);
- k5_cccol_unlock(context);
- return ret;
- }
+ if (ret)
+ goto cleanup;
- ret = k5_cc_lock(context, dst);
- if (!ret) {
- ret = krb5_cc_copy_creds(context, src, dst);
- k5_cc_unlock(context, dst);
- }
+ ret = read_creds(context, src, &creds);
+ if (ret)
+ goto cleanup;
- k5_cc_unlock(context, src);
- if (!ret) {
- ret = krb5_cc_destroy(context, src);
- }
- k5_cccol_unlock(context);
- if (princ) {
- krb5_free_principal(context, princ);
- princ = NULL;
- }
+ if (dst->ops->replace == NULL)
+ ret = k5_nonatomic_replace(context, dst, princ, creds);
+ else
+ ret = dst->ops->replace(context, dst, princ, creds);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_cc_destroy(context, src);
+cleanup:
+ krb5_free_principal(context, princ);
+ krb5_free_tgt_creds(context, creds);
return ret;
}
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
index b28b38a..4195a55 100644
--- a/src/lib/krb5/krb/get_in_tkt.c
+++ b/src/lib/krb5/krb/get_in_tkt.c
@@ -1604,6 +1604,61 @@ warn_des3(krb5_context context, krb5_init_creds_context ctx,
(*ctx->prompter)(context, ctx->prompter_data, NULL, banner, 0, NULL);
}
+/*
+ * If ctx specifies an output ccache, create or refresh it (atomically, if
+ * possible) with the obtained credential and any appropriate ccache
+ * configuration.
+ */
+static krb5_error_code
+write_out_ccache(krb5_context context, krb5_init_creds_context ctx,
+ krb5_boolean fast_avail)
+{
+ krb5_error_code ret;
+ krb5_ccache out_ccache = k5_gic_opt_get_out_ccache(ctx->opt);
+ krb5_ccache mcc = NULL;
+ krb5_data yes = string2data("yes");
+
+ if (out_ccache == NULL)
+ return 0;
+
+ ret = krb5_cc_new_unique(context, "MEMORY", NULL, &mcc);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_cc_initialize(context, mcc, ctx->cred.client);
+ if (ret)
+ goto cleanup;
+
+ if (fast_avail) {
+ ret = krb5_cc_set_config(context, mcc, ctx->cred.server,
+ KRB5_CC_CONF_FAST_AVAIL, &yes);
+ if (ret)
+ goto cleanup;
+ }
+
+ ret = save_selected_preauth_type(context, mcc, ctx);
+ if (ret)
+ goto cleanup;
+
+ ret = save_cc_config_out_data(context, mcc, ctx);
+ if (ret)
+ goto cleanup;
+
+ ret = k5_cc_store_primary_cred(context, mcc, &ctx->cred);
+ if (ret)
+ goto cleanup;
+
+ ret = krb5_cc_move(context, mcc, out_ccache);
+ if (ret)
+ goto cleanup;
+ mcc = NULL;
+
+cleanup:
+ if (mcc != NULL)
+ krb5_cc_destroy(context, mcc);
+ return ret;
+}
+
static krb5_error_code
init_creds_step_reply(krb5_context context,
krb5_init_creds_context ctx,
@@ -1618,7 +1673,6 @@ init_creds_step_reply(krb5_context context,
krb5_keyblock *strengthen_key = NULL;
krb5_keyblock encrypting_key;
krb5_boolean fast_avail;
- krb5_ccache out_ccache = k5_gic_opt_get_out_ccache(ctx->opt);
encrypting_key.length = 0;
encrypting_key.contents = NULL;
@@ -1786,30 +1840,9 @@ init_creds_step_reply(krb5_context context,
code = stash_as_reply(context, ctx->reply, &ctx->cred, NULL);
if (code != 0)
goto cleanup;
- if (out_ccache != NULL) {
- krb5_data config_data;
- code = krb5_cc_initialize(context, out_ccache, ctx->cred.client);
- if (code != 0)
- goto cc_cleanup;
- code = k5_cc_store_primary_cred(context, out_ccache, &ctx->cred);
- if (code != 0)
- goto cc_cleanup;
- if (fast_avail) {
- config_data.data = "yes";
- config_data.length = strlen(config_data.data);
- code = krb5_cc_set_config(context, out_ccache, ctx->cred.server,
- KRB5_CC_CONF_FAST_AVAIL, &config_data);
- if (code != 0)
- goto cc_cleanup;
- }
- code = save_selected_preauth_type(context, out_ccache, ctx);
- if (code != 0)
- goto cc_cleanup;
- code = save_cc_config_out_data(context, out_ccache, ctx);
- cc_cleanup:
- if (code != 0)
- k5_prependmsg(context, code, _("Failed to store credentials"));
- }
+ code = write_out_ccache(context, ctx, fast_avail);
+ if (code)
+ k5_prependmsg(context, code, _("Failed to store credentials"));
k5_preauth_request_context_fini(context, ctx);
diff --git a/src/lib/krb5/krb/t_vfy_increds.c b/src/lib/krb5/krb/t_vfy_increds.c
index 693eb37..00a6b03 100644
--- a/src/lib/krb5/krb/t_vfy_increds.c
+++ b/src/lib/krb5/krb/t_vfy_increds.c
@@ -26,9 +26,9 @@
/*
* This program is intended to be run from t_vfy_increds.py. It retrieves the
- * first credential from the default ccache and verifies it against the default
- * keytab, exiting with status 0 on successful verification and 1 on
- * unsuccessful verification.
+ * first non-config credential from the default ccache and verifies it against
+ * the default keytab, exiting with status 0 on successful verification and 1
+ * on unsuccessful verification.
*/
#include "k5-int.h"
@@ -64,10 +64,15 @@ main(int argc, char **argv)
if (*argv != NULL)
check(krb5_parse_name(context, *argv, &princ));
- /* Fetch the first credential from the default ccache. */
+ /* Fetch the first non-config credential from the default ccache. */
check(krb5_cc_default(context, &ccache));
check(krb5_cc_start_seq_get(context, ccache, &cursor));
- check(krb5_cc_next_cred(context, ccache, &cursor, &creds));
+ for (;;) {
+ check(krb5_cc_next_cred(context, ccache, &cursor, &creds));
+ if (!krb5_is_config_principal(context, creds.server))
+ break;
+ krb5_free_cred_contents(context, &creds);
+ }
check(krb5_cc_end_seq_get(context, ccache, &cursor));
check(krb5_cc_close(context, ccache));
diff --git a/src/tests/Makefile.in b/src/tests/Makefile.in
index 1a19383..e7cf64e 100644
--- a/src/tests/Makefile.in
+++ b/src/tests/Makefile.in
@@ -6,13 +6,13 @@ SUBDIRS = asn.1 create hammer verify gssapi shlib gss-threads misc threads \
RUN_DB_TEST = $(RUN_SETUP) KRB5_KDC_PROFILE=kdc.conf KRB5_CONFIG=krb5.conf \
GSS_MECH_CONFIG=mech.conf LC_ALL=C $(VALGRIND)
-OBJS= adata.o etinfo.o forward.o gcred.o hist.o hooks.o hrealm.o \
+OBJS= adata.o conccache.o etinfo.o forward.o gcred.o hist.o hooks.o hrealm.o \
icinterleave.o icred.o kdbtest.o localauth.o plugorder.o rdreq.o \
replay.o responder.o s2p.o s4u2self.o s4u2proxy.o t_inetd.o \
unlockiter.o
-EXTRADEPSRCS= adata.c etinfo.c forward.c gcred.c hist.c hooks.c hrealm.c \
- icinterleave.c icred.c kdbtest.c localauth.c plugorder.c rdreq.c \
- replay.c responder.c s2p.c s4u2self.c s4u2proxy.c t_inetd.c \
+EXTRADEPSRCS= adata.c conccache.c etinfo.c forward.c gcred.c hist.c hooks.c \
+ hrealm.c icinterleave.c icred.c kdbtest.c localauth.c plugorder.c \
+ rdreq.c replay.c responder.c s2p.c s4u2self.c s4u2proxy.c t_inetd.c \
unlockiter.c
TEST_DB = ./testdb
@@ -28,6 +28,9 @@ KTEST_OPTS= $(KADMIN_OPTS) -p $(TEST_PREFIX) -n $(TEST_NUM) -D $(TEST_DEPTH)
adata: adata.o $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o $@ adata.o $(KRB5_BASE_LIBS)
+conccache: conccache.o $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ conccache.o $(KRB5_BASE_LIBS)
+
etinfo: etinfo.o $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o $@ etinfo.o $(KRB5_BASE_LIBS)
@@ -125,9 +128,9 @@ kdb_check: kdc.conf krb5.conf
$(RUN_DB_TEST) ../kadmin/dbutil/kdb5_util $(KADMIN_OPTS) destroy -f
$(RM) $(TEST_DB)* stash_file
-check-pytests: adata etinfo forward gcred hist hooks hrealm icinterleave icred
-check-pytests: kdbtest localauth plugorder rdreq replay responder s2p s4u2proxy
-check-pytests: unlockiter s4u2self
+check-pytests: adata conccache etinfo forward gcred hist hooks hrealm
+check-pytests: icinterleave icred kdbtest localauth plugorder rdreq replay
+check-pytests: responder s2p s4u2proxy unlockiter s4u2self
$(RUNPYTEST) $(srcdir)/t_general.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_hooks.py $(PYTESTFLAGS)
$(RUNPYTEST) $(srcdir)/t_dump.py $(PYTESTFLAGS)
@@ -190,9 +193,9 @@ check-pytests: unlockiter s4u2self
$(RUNPYTEST) $(srcdir)/t_replay.py $(PYTESTFLAGS)
clean:
- $(RM) adata etinfo forward gcred hist hooks hrealm icinterleave icred
- $(RM) kdbtest localauth plugorder rdreq replay responder s2p s4u2proxy
- $(RM) s4u2self t_inetd unlockiter
+ $(RM) adata conccache etinfo forward gcred hist hooks hrealm
+ $(RM) icinterleave icred kdbtest localauth plugorder rdreq replay
+ $(RM) responder s2p s4u2proxy s4u2self t_inetd unlockiter
$(RM) krb5.conf kdc.conf
$(RM) -rf kdc_realm/sandbox ldap
$(RM) au.log
diff --git a/src/tests/conccache.c b/src/tests/conccache.c
new file mode 100644
index 0000000..7b0ca63
--- /dev/null
+++ b/src/tests/conccache.c
@@ -0,0 +1,190 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* tests/conccache.c - ccache concurrent get_creds/refresh test program */
+/*
+ * 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: conccache ccname clientprinc serverprinc
+ *
+ * This program spawns two subprocesses. One repeatedly runs
+ * krb5_get_credentials() on ccname, and the other repeatedly refreshes ccname
+ * from the default keytab. If either subprocess fails, the program exits with
+ * status 1. The goal is to expose time windows where cache refreshes cause
+ * get_cred operations to fail.
+ */
+
+#include "k5-platform.h"
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <krb5.h>
+
+/* Run this many iterations of each operation. */
+static const int iterations = 200;
+
+/* Saved command-line arguments. */
+static const char *ccname, *server_name, *client_name;
+
+static void
+check(krb5_error_code code)
+{
+ if (code)
+ abort();
+}
+
+static krb5_boolean
+get_cred(krb5_context context)
+{
+ krb5_error_code ret;
+ krb5_ccache cc;
+ krb5_principal client, server;
+ krb5_creds mcred, *cred;
+
+ check(krb5_cc_resolve(context, ccname, &cc));
+ check(krb5_parse_name(context, client_name, &client));
+ check(krb5_parse_name(context, server_name, &server));
+
+ memset(&mcred, 0, sizeof(mcred));
+ mcred.client = client;
+ mcred.server = server;
+ ret = krb5_get_credentials(context, 0, cc, &mcred, &cred);
+
+ krb5_free_creds(context, cred);
+ krb5_free_principal(context, client);
+ krb5_free_principal(context, server);
+ krb5_cc_close(context, cc);
+
+ return ret == 0;
+}
+
+static krb5_boolean
+refresh_cache(krb5_context context)
+{
+ krb5_error_code ret;
+ krb5_ccache cc;
+ krb5_principal client;
+ krb5_get_init_creds_opt *opt;
+ krb5_creds cred;
+
+ check(krb5_cc_resolve(context, ccname, &cc));
+ check(krb5_parse_name(context, client_name, &client));
+
+ check(krb5_get_init_creds_opt_alloc(context, &opt));
+ check(krb5_get_init_creds_opt_set_out_ccache(context, opt, cc));
+ ret = krb5_get_init_creds_keytab(context, &cred, client, NULL, 0, NULL,
+ opt);
+
+ krb5_get_init_creds_opt_free(context, opt);
+ krb5_free_cred_contents(context, &cred);
+ krb5_free_principal(context, client);
+ krb5_cc_close(context, cc);
+
+ return ret == 0;
+}
+
+static pid_t
+spawn_cred_subprocess()
+{
+ krb5_context context;
+ pid_t pid;
+ int i;
+
+ pid = fork();
+ assert(pid >= 0);
+ if (pid > 0)
+ return pid;
+
+ check(krb5_init_context(&context));
+ for (i = 0; i < iterations; i++) {
+ if (!get_cred(context)) {
+ fprintf(stderr, "cred worker failed after %d successes\n", i);
+ exit(1);
+ }
+ }
+ krb5_free_context(context);
+ exit(0);
+}
+
+static pid_t
+spawn_refresh_subprocess()
+{
+ krb5_context context;
+ pid_t pid;
+ int i;
+
+ pid = fork();
+ assert(pid >= 0);
+ if (pid > 0)
+ return pid;
+
+ check(krb5_init_context(&context));
+ for (i = 0; i < iterations; i++) {
+ if (!refresh_cache(context)) {
+ fprintf(stderr, "refresh worker failed after %d successes\n", i);
+ exit(1);
+ }
+ }
+ krb5_free_context(context);
+ exit(0);
+}
+
+int
+main(int argc, char *argv[])
+{
+ krb5_context context;
+ pid_t cred_pid, refresh_pid, pid;
+ int cstatus, rstatus;
+
+ assert(argc == 4);
+ ccname = argv[1];
+ client_name = argv[2];
+ server_name = argv[3];
+
+ /* Begin with an initialized cache. */
+ check(krb5_init_context(&context));
+ refresh_cache(context);
+ krb5_free_context(context);
+
+ cred_pid = spawn_cred_subprocess();
+ refresh_pid = spawn_refresh_subprocess();
+
+ pid = waitpid(cred_pid, &cstatus, 0);
+ if (pid == -1)
+ abort();
+ pid = waitpid(refresh_pid, &rstatus, 0);
+ if (pid == -1)
+ abort();
+
+ if (!WIFEXITED(cstatus) || WEXITSTATUS(cstatus) != 0)
+ return 1;
+ if (!WIFEXITED(rstatus) || WEXITSTATUS(rstatus) != 0)
+ return 1;
+ return 0;
+}
diff --git a/src/tests/kcmserver.py b/src/tests/kcmserver.py
index 25e6f2b..f6dfcb7 100644
--- a/src/tests/kcmserver.py
+++ b/src/tests/kcmserver.py
@@ -52,6 +52,7 @@ class KCMOpcodes(object):
GET_KDC_OFFSET = 22
SET_KDC_OFFSET = 23
GET_CRED_LIST = 13001
+ REPLACE = 13002
class KRB5Errors(object):
@@ -88,27 +89,28 @@ def get_cache(name):
return cache
-def unpack_data(argbytes):
- dlen, = struct.unpack('>L', argbytes[:4])
- return argbytes[4:dlen+4], argbytes[dlen+4:]
-
-
def unmarshal_name(argbytes):
offset = argbytes.find(b'\0')
return argbytes[0:offset], argbytes[offset+1:]
-def unmarshal_princ(argbytes):
- # Ignore the type at argbytes[0:4].
- ncomps, = struct.unpack('>L', argbytes[4:8])
- realm, rest = unpack_data(argbytes[8:])
- comps = []
+# Find the bounds of a marshalled principal, returning it and the
+# remainder of argbytes.
+def extract_princ(argbytes):
+ ncomps, rlen = struct.unpack('>LL', argbytes[4:12])
+ pos = 12 + rlen
for i in range(ncomps):
- comp, rest = unpack_data(rest)
- comps.append(comp)
- # Asssume no quoting is needed.
- princ = b'/'.join(comps) + b'@' + realm
- return princ, rest
+ clen, = struct.unpack('>L', argbytes[pos:pos+4])
+ pos += 4 + clen
+ return argbytes[0:pos], argbytes[pos:]
+
+
+# Return true if the marshalled principals p1 and p2 name the same
+# principal.
+def princ_eq(p1, p2):
+ # Ignore the name-types at bytes 0..3. The remaining bytes should
+ # be identical if the principals are the same.
+ return p1[4:] == p2[4:]
def op_gen_new(argbytes):
@@ -151,13 +153,13 @@ def op_retrieve(argbytes):
# Ignore the flags at rest[0:4] and the header at rest[4:8].
# Assume there are client and server creds in the tag and match
# only against them.
- cprinc, rest = unmarshal_princ(rest[8:])
- sprinc, rest = unmarshal_princ(rest)
+ cprinc, rest = extract_princ(rest[8:])
+ sprinc, rest = extract_princ(rest)
cache = get_cache(name)
for cred in (cache.creds[u] for u in cache.cred_uuids):
- cred_cprinc, rest = unmarshal_princ(cred)
- cred_sprinc, rest = unmarshal_princ(rest)
- if cred_cprinc == cprinc and cred_sprinc == sprinc:
+ cred_cprinc, rest = extract_princ(cred)
+ cred_sprinc, rest = extract_princ(rest)
+ if princ_eq(cred_cprinc, cprinc) and princ_eq(cred_sprinc, sprinc):
return 0, cred
return KRB5Errors.KRB5_CC_NOTFOUND, b''
@@ -230,6 +232,31 @@ def op_get_cred_list(argbytes):
b''.join(struct.pack('>L', len(c)) + c for c in creds))
+def op_replace(argbytes):
+ name, rest = unmarshal_name(argbytes)
+ offset, = struct.unpack('>L', rest[0:4])
+ princ, rest = extract_princ(rest[4:])
+ ncreds, = struct.unpack('>L', rest[0:4])
+ rest = rest[4:]
+ creds = []
+ for i in range(ncreds):
+ len, = struct.unpack('>L', rest[0:4])
+ creds.append(rest[4:4+len])
+ rest = rest[4+len:]
+
+ cache = get_cache(name)
+ cache.princ = princ
+ cache.cred_uuids = []
+ cache.creds = {}
+ cache.time_offset = offset
+ for i in range(ncreds):
+ uuid = make_uuid()
+ cache.creds[uuid] = creds[i]
+ cache.cred_uuids.append(uuid)
+
+ return 0, b''
+
+
ophandlers = {
KCMOpcodes.GEN_NEW : op_gen_new,
KCMOpcodes.INITIALIZE : op_initialize,
@@ -246,7 +273,8 @@ ophandlers = {
KCMOpcodes.SET_DEFAULT_CACHE : op_set_default_cache,
KCMOpcodes.GET_KDC_OFFSET : op_get_kdc_offset,
KCMOpcodes.SET_KDC_OFFSET : op_set_kdc_offset,
- KCMOpcodes.GET_CRED_LIST : op_get_cred_list
+ KCMOpcodes.GET_CRED_LIST : op_get_cred_list,
+ KCMOpcodes.REPLACE : op_replace
}
# Read and respond to a request from the socket s.
@@ -281,11 +309,13 @@ def service_request(s):
parser = optparse.OptionParser()
parser.add_option('-f', '--fallback', action='store_true', dest='fallback',
- default=False, help='Do not support RETRIEVE/GET_CRED_LIST')
+ default=False,
+ help='Do not support RETRIEVE/GET_CRED_LIST/REPLACE')
(options, args) = parser.parse_args()
if options.fallback:
del ophandlers[KCMOpcodes.RETRIEVE]
del ophandlers[KCMOpcodes.GET_CRED_LIST]
+ del ophandlers[KCMOpcodes.REPLACE]
server = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
server.bind(args[0])
diff --git a/src/tests/t_ccache.py b/src/tests/t_ccache.py
index 9e005ec..9371a0c 100755
--- a/src/tests/t_ccache.py
+++ b/src/tests/t_ccache.py
@@ -29,6 +29,11 @@ conf = {'libdefaults': {'kcm_socket': kcm_socket_path,
'kcm_mach_service': '-'}}
realm = K5Realm(krb5_conf=conf)
+realm.addprinc('contest')
+realm.extract_keytab('contest', realm.keytab)
+realm.run(['./conccache', realm.ccache + '.contest', 'contest',
+ realm.host_princ])
+
keyctl = which('keyctl')
out = realm.run([klist, '-c', 'KEYRING:process:abcd'], expected_code=1)
test_keyring = (keyctl is not None and
More information about the cvs-krb5
mailing list