summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--src/include/k5-int.h8
-rw-r--r--src/include/krb5/krb5.hin71
-rw-r--r--src/include/krb5/preauth_plugin.h37
-rw-r--r--src/lib/krb5/krb/Makefile.in12
-rw-r--r--src/lib/krb5/krb/get_in_tkt.c6
-rw-r--r--src/lib/krb5/krb/gic_opt.c17
-rw-r--r--src/lib/krb5/krb/init_creds_ctx.h1
-rw-r--r--src/lib/krb5/krb/int-proto.h31
-rw-r--r--src/lib/krb5/krb/preauth2.c103
-rw-r--r--src/lib/krb5/krb/response_items.c212
-rw-r--r--src/lib/krb5/krb/t_response_items.c94
-rw-r--r--src/lib/krb5/libkrb5.exports4
-rw-r--r--src/lib/krb5_32.def4
14 files changed, 595 insertions, 6 deletions
diff --git a/.gitignore b/.gitignore
index 840bc65482..9c14c221e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -194,6 +194,7 @@ testlog
/src/lib/krb5/krb/t_ser
/src/lib/krb5/krb/t_vfy_increds
/src/lib/krb5/krb/t_walk_rtree
+/src/lib/krb5/krb/t_response_items
/src/lib/krb5/os/t_an_to_ln
/src/lib/krb5/os/t_kuserok
diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 670915d741..bf36a177d9 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -792,6 +792,11 @@ error(MIT_DES_KEYSIZE does not equal KRB5_MIT_DES_KEYSIZE)
#include <krb5/preauth_plugin.h>
+typedef struct k5_response_items_st k5_response_items;
+struct krb5_responder_context_st {
+ k5_response_items *items;
+};
+
typedef krb5_error_code
(*krb5_gic_get_as_key_fct)(krb5_context, krb5_principal, krb5_enctype,
krb5_prompter_fct, void *prompter_data,
@@ -831,6 +836,7 @@ struct krb5_clpreauth_rock_st {
krb5_timestamp pa_offset;
krb5_int32 pa_offset_usec;
enum { NO_OFFSET = 0, UNAUTH_OFFSET, AUTH_OFFSET } pa_offset_state;
+ struct krb5_responder_context_st rctx;
};
typedef struct _krb5_pa_enc_ts {
@@ -1025,6 +1031,8 @@ typedef struct _krb5_gic_opt_private {
krb5_flags fast_flags;
krb5_expire_callback_func expire_cb;
void *expire_data;
+ krb5_responder_fn responder;
+ void *responder_data;
} krb5_gic_opt_private;
/*
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 2f3974a129..7c519f05d0 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -6353,6 +6353,64 @@ krb5_prompter_posix(krb5_context context, void *data, const char *name,
const char *banner, int num_prompts,
krb5_prompt prompts[]);
+typedef struct krb5_responder_context_st *krb5_responder_context;
+
+/**
+ * List the question names contained in the responder context.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ */
+const char * const * KRB5_CALLCONV
+krb5_responder_list_questions(krb5_context ctx, krb5_responder_context rctx);
+
+/**
+ * Retrieve the challenge data for a given question in the responder context.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [in] question Question name
+ */
+const char * KRB5_CALLCONV
+krb5_responder_get_challenge(krb5_context ctx, krb5_responder_context rctx,
+ const char *question);
+
+/**
+ * Answer a named question in the responder context.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [in] question Question name
+ * @param [in] answer The string to set (MUST be printable UTF-8)
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_responder_set_answer(krb5_context ctx, krb5_responder_context rctx,
+ const char *question, const char *answer);
+
+/**
+ * Responder function for an initial credential exchange.
+ *
+ * @param [in] ctx Library context
+ * @param [in] rctx Responder context
+ * @param [in] data Callback data
+ *
+ * A responder function is like a prompter function, but is used for handling
+ * questions and answers as potentially complex data types. Client
+ * preauthentication modules will insert a set of named "questions" into
+ * the responder context. Each question may optionally contain a challenge.
+ * This challenge is printable UTF-8, but may be an encoded value. The
+ * precise encoding and contents of the challenge are specific to the question
+ * asked. When the responder is called, it should answer all the questions it
+ * understands. Like the challenge, the answer MUST be printable UTF-8, but
+ * may contain structured/encoded data formatted to the expected answer format
+ * of the question.
+ *
+ * If a required question is unanswered, the prompter may be called.
+ */
+typedef krb5_error_code
+(*krb5_responder_fn)(krb5_context ctx, krb5_responder_context rctx,
+ void *data);
+
/** Store options for @c _krb5_get_init_creds */
typedef struct _krb5_get_init_creds_opt {
krb5_flags flags;
@@ -6712,6 +6770,19 @@ krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
void *data);
/**
+ * Set the responder function in initial credential options.
+ *
+ * @param [in] context Library context
+ * @param [in] opt Options structure
+ * @param [in] responder Responder function
+ * @param [in] data Responder data argument
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_responder(krb5_context context,
+ krb5_get_init_creds_opt *opt,
+ krb5_responder_fn responder, void *data);
+
+/**
* Get initial credentials using a password.
*
* @param [in] context Library context
diff --git a/src/include/krb5/preauth_plugin.h b/src/include/krb5/preauth_plugin.h
index 72fd92d26f..a9a2ab9d2c 100644
--- a/src/include/krb5/preauth_plugin.h
+++ b/src/include/krb5/preauth_plugin.h
@@ -38,7 +38,7 @@
*
*
* The clpreauth interface has a single supported major version, which is
- * 1. Major version 1 has a current minor version of 1. clpreauth modules
+ * 1. Major version 1 has a current minor version of 2. clpreauth modules
* should define a function named clpreauth_<modulename>_initvt, matching
* the signature:
*
@@ -193,6 +193,19 @@ typedef struct krb5_clpreauth_callbacks_st {
krb5_timestamp *time_out,
krb5_int32 *usec_out);
+ /* Set a question to be answered by the responder and optionally provide
+ * a challenge. */
+ krb5_error_code (*ask_responder_question)(krb5_context context,
+ krb5_clpreauth_rock rock,
+ const char *question,
+ const char *challenge);
+
+ /* Get an answer from the responder, or NULL if the question was
+ * unanswered. */
+ const char *(*get_responder_answer)(krb5_context context,
+ krb5_clpreauth_rock rock,
+ const char *question);
+
/* End of version 2 clpreauth callbacks (added in 1.11). */
} *krb5_clpreauth_callbacks;
@@ -235,6 +248,25 @@ typedef void
krb5_clpreauth_modreq modreq);
/*
+ * Optional: process server-supplied data in pa_data and set responder
+ * questions.
+ *
+ * encoded_previous_request may be NULL if there has been no previous request
+ * in the AS exchange.
+ */
+typedef krb5_error_code
+(*krb5_clpreauth_prep_questions_fn)(krb5_context context,
+ krb5_clpreauth_moddata moddata,
+ krb5_clpreauth_modreq modreq,
+ krb5_get_init_creds_opt *opt,
+ krb5_clpreauth_callbacks cb,
+ krb5_clpreauth_rock rock,
+ krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data *pa_data);
+
+/*
* Mandatory: process server-supplied data in pa_data and return created data
* in pa_data_out. Also called after the AS-REP is received if the AS-REP
* includes preauthentication data of the associated type.
@@ -317,6 +349,9 @@ typedef struct krb5_clpreauth_vtable_st {
krb5_clpreauth_tryagain_fn tryagain;
krb5_clpreauth_supply_gic_opts_fn gic_opts;
/* Minor version 1 ends here. */
+
+ krb5_clpreauth_prep_questions_fn prep_questions;
+ /* Minor version 2 ends here. */
} *krb5_clpreauth_vtable;
/*
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index 4d084786a9..31160a7673 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -92,6 +92,7 @@ STLIBOBJS= \
rd_req_dec.o \
rd_safe.o \
recvauth.o \
+ response_items.o \
s4u_authdata.o \
s4u_creds.o \
sendauth.o \
@@ -199,6 +200,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \
$(OUTPRE)rd_req_dec.$(OBJEXT) \
$(OUTPRE)rd_safe.$(OBJEXT) \
$(OUTPRE)recvauth.$(OBJEXT) \
+ $(OUTPRE)response_items.$(OBJEXT) \
$(OUTPRE)s4u_authdata.$(OBJEXT) \
$(OUTPRE)s4u_creds.$(OBJEXT) \
$(OUTPRE)sendauth.$(OBJEXT) \
@@ -306,6 +308,7 @@ SRCS= $(srcdir)/addr_comp.c \
$(srcdir)/rd_req_dec.c \
$(srcdir)/rd_safe.c \
$(srcdir)/recvauth.c \
+ $(srcdir)/response_items.c \
$(srcdir)/s4u_authdata.c\
$(srcdir)/s4u_creds.c \
$(srcdir)/sendauth.c \
@@ -412,8 +415,11 @@ t_expire_warn: t_expire_warn.o $(KRB5_BASE_DEPLIBS)
t_vfy_increds: t_vfy_increds.o $(KRB5_BASE_DEPLIBS)
$(CC_LINK) -o $@ t_vfy_increds.o $(KRB5_BASE_LIBS)
+t_response_items: t_response_items.o response_items.o $(KRB5_BASE_DEPLIBS)
+ $(CC_LINK) -o $@ t_response_items.o response_items.o $(KRB5_BASE_LIBS)
+
TEST_PROGS= t_walk_rtree t_kerb t_ser t_deltat t_expand t_authdata t_pac \
- t_princ t_etypes t_vfy_increds
+ t_princ t_etypes t_vfy_increds t_response_items
check-unix:: $(TEST_PROGS)
KRB5_CONFIG=$(srcdir)/t_krb5.conf ; export KRB5_CONFIG ;\
@@ -451,6 +457,7 @@ check-unix:: $(TEST_PROGS)
$(RUN_SETUP) $(VALGRIND) ./t_pac
$(RUN_SETUP) $(VALGRIND) ./t_princ
$(RUN_SETUP) $(VALGRIND) ./t_etypes
+ $(RUN_SETUP) $(VALGRIND) ./t_response_items
check-pytests:: t_expire_warn t_vfy_increds
$(RUNPYTEST) $(srcdir)/t_expire_warn.py $(PYTESTFLAGS)
@@ -467,7 +474,8 @@ clean::
$(OUTPRE)t_pac$(EXEEXT) $(OUTPRE)t_pac.$(OBJEXT) \
$(OUTPRE)t_princ$(EXEEXT) $(OUTPRE)t_princ.$(OBJEXT) \
$(OUTPRE)t_authdata$(EXEEXT) $(OUTPRE)t_authdata.$(OBJEXT) \
- $(OUTPRE)t_vfy_increds$(EXEEXT) $(OUTPRE)t_vfy_increds.$(OBJEXT)
+ $(OUTPRE)t_vfy_increds$(EXEEXT) $(OUTPRE)t_vfy_increds.$(OBJEXT) \
+ $(OUTPRE)t_response_items$(EXEEXT) $(OUTPRE)t_response_items.$(OBJEXT)
@libobj_frag@
diff --git a/src/lib/krb5/krb/get_in_tkt.c b/src/lib/krb5/krb/get_in_tkt.c
index 3f67df0a5e..d52147ac26 100644
--- a/src/lib/krb5/krb/get_in_tkt.c
+++ b/src/lib/krb5/krb/get_in_tkt.c
@@ -495,6 +495,7 @@ krb5_init_creds_free(krb5_context context,
krb5_get_init_creds_opt_free(context,
(krb5_get_init_creds_opt *)ctx->opte);
}
+ k5_response_items_free(ctx->rctx.items);
free(ctx->in_tkt_service);
zap(ctx->password.data, ctx->password.length);
krb5_free_data_contents(context, &ctx->password);
@@ -811,6 +812,10 @@ krb5_init_creds_init(krb5_context context,
if (code != 0)
goto cleanup;
+ code = k5_response_items_new(&ctx->rctx.items);
+ if (code != 0)
+ goto cleanup;
+
opte = ctx->opte;
ctx->preauth_rock.magic = CLIENT_ROCK_MAGIC;
@@ -821,6 +826,7 @@ krb5_init_creds_init(krb5_context context,
ctx->preauth_rock.default_salt = &ctx->default_salt;
ctx->preauth_rock.salt = &ctx->salt;
ctx->preauth_rock.s2kparams = &ctx->s2kparams;
+ ctx->preauth_rock.rctx = ctx->rctx;
ctx->preauth_rock.client = client;
ctx->preauth_rock.prompter = prompter;
ctx->preauth_rock.prompter_data = data;
diff --git a/src/lib/krb5/krb/gic_opt.c b/src/lib/krb5/krb/gic_opt.c
index a98a47ed2a..2580abdbe2 100644
--- a/src/lib/krb5/krb/gic_opt.c
+++ b/src/lib/krb5/krb/gic_opt.c
@@ -523,3 +523,20 @@ krb5_get_init_creds_opt_set_expire_callback(krb5_context context,
opte->opt_private->expire_data = data;
return retval;
}
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_init_creds_opt_set_responder(krb5_context context,
+ krb5_get_init_creds_opt *opt,
+ krb5_responder_fn responder, void *data)
+{
+ krb5_error_code ret;
+ krb5_gic_opt_ext *opte;
+
+ ret = krb5int_gic_opt_to_opte(context, opt, &opte, 0,
+ "krb5_get_init_creds_opt_set_responder");
+ if (ret)
+ return ret;
+ opte->opt_private->responder = responder;
+ opte->opt_private->responder_data = data;
+ return 0;
+}
diff --git a/src/lib/krb5/krb/init_creds_ctx.h b/src/lib/krb5/krb/init_creds_ctx.h
index 2653ee1613..ae69ed0828 100644
--- a/src/lib/krb5/krb/init_creds_ctx.h
+++ b/src/lib/krb5/krb/init_creds_ctx.h
@@ -46,6 +46,7 @@ struct _krb5_init_creds_context {
krb5_boolean have_restarted;
krb5_boolean sent_nontrivial_preauth;
krb5_boolean preauth_required;
+ struct krb5_responder_context_st rctx;
};
krb5_error_code
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index f794f143e6..6f3de8f345 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -203,4 +203,35 @@ krb5_error_code
k5_init_creds_get(krb5_context context, krb5_init_creds_context ctx,
int *use_master);
+krb5_error_code
+k5_response_items_new(k5_response_items **ri_out);
+
+void
+k5_response_items_free(k5_response_items *ri);
+
+void
+k5_response_items_reset(k5_response_items *ri);
+
+krb5_boolean
+k5_response_items_empty(const k5_response_items *ri);
+
+const char * const *
+k5_response_items_list_questions(const k5_response_items *ri);
+
+krb5_error_code
+k5_response_items_ask_question(k5_response_items *ri, const char *question,
+ const char *challenge);
+
+const char *
+k5_response_items_get_challenge(const k5_response_items *ri,
+ const char *question);
+
+krb5_error_code
+k5_response_items_set_answer(k5_response_items *ri, const char *question,
+ const char *answer);
+
+const char *
+k5_response_items_get_answer(const k5_response_items *ri,
+ const char *question);
+
#endif /* KRB5_INT_FUNC_PROTO__ */
diff --git a/src/lib/krb5/krb/preauth2.c b/src/lib/krb5/krb/preauth2.c
index 2ed53ab08c..1153175a5d 100644
--- a/src/lib/krb5/krb/preauth2.c
+++ b/src/lib/krb5/krb/preauth2.c
@@ -60,6 +60,7 @@ struct krb5_preauth_context_st {
* convenience when we populated the list. */
const char *name;
int flags, use_count;
+ krb5_clpreauth_prep_questions_fn client_prep_questions;
krb5_clpreauth_process_fn client_process;
krb5_clpreauth_tryagain_fn client_tryagain;
krb5_clpreauth_supply_gic_opts_fn client_supply_gic_opts;
@@ -138,7 +139,7 @@ krb5_init_preauth_context(krb5_context kcontext)
if (vtables == NULL)
goto cleanup;
for (pl = plugins, n_tables = 0; *pl != NULL; pl++) {
- if ((*pl)(kcontext, 1, 1, (krb5_plugin_vtable)&vtables[n_tables]) == 0)
+ if ((*pl)(kcontext, 1, 2, (krb5_plugin_vtable)&vtables[n_tables]) == 0)
n_tables++;
}
@@ -188,6 +189,7 @@ krb5_init_preauth_context(krb5_context kcontext)
mod->name = vt->name;
mod->flags = (*vt->flags)(kcontext, pa_type);
mod->use_count = 0;
+ mod->client_prep_questions = vt->prep_questions;
mod->client_process = vt->process;
mod->client_tryagain = vt->tryagain;
mod->client_supply_gic_opts = first ? vt->gic_opts : NULL;
@@ -404,13 +406,30 @@ get_preauth_time(krb5_context context, krb5_clpreauth_rock rock,
}
}
+static krb5_error_code
+responder_ask_question(krb5_context context, krb5_clpreauth_rock rock,
+ const char *question, const char *challenge)
+{
+ return k5_response_items_ask_question(rock->rctx.items, question,
+ challenge);
+}
+
+static const char *
+responder_get_answer(krb5_context context, krb5_clpreauth_rock rock,
+ const char *question)
+{
+ return k5_response_items_get_answer(rock->rctx.items, question);
+}
+
static struct krb5_clpreauth_callbacks_st callbacks = {
2,
get_etype,
fast_armor,
get_as_key,
set_as_key,
- get_preauth_time
+ get_preauth_time,
+ responder_ask_question,
+ responder_get_answer
};
/* Tweak the request body, for now adding any enctypes which the module claims
@@ -440,6 +459,32 @@ krb5_preauth_prepare_request(krb5_context kcontext,
}
}
+const char * const * KRB5_CALLCONV
+krb5_responder_list_questions(krb5_context ctx, krb5_responder_context rctx)
+{
+ return k5_response_items_list_questions(rctx->items);
+}
+
+const char * KRB5_CALLCONV
+krb5_responder_get_challenge(krb5_context ctx, krb5_responder_context rctx,
+ const char *question)
+{
+ if (rctx == NULL)
+ return NULL;
+
+ return k5_response_items_get_challenge(rctx->items, question);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_responder_set_answer(krb5_context ctx, krb5_responder_context rctx,
+ const char *question, const char *answer)
+{
+ if (rctx == NULL)
+ return EINVAL;
+
+ return k5_response_items_set_answer(rctx->items, question, answer);
+}
+
/* Find the first module which provides for the named preauth type which also
* hasn't had a chance to run yet (INFO modules don't count, because as a rule
* they don't generate preauth data), and run it. */
@@ -789,6 +834,42 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
return ret;
}
+/* Compile the set of response items for in_padata by invoke each module's
+ * prep_questions method. */
+static krb5_error_code
+fill_response_items(krb5_context context, krb5_kdc_req *request,
+ krb5_data *encoded_request_body,
+ krb5_data *encoded_previous_request,
+ krb5_pa_data **in_padata, krb5_clpreauth_rock rock,
+ krb5_gic_opt_ext *opte)
+{
+ krb5_error_code ret;
+ krb5_pa_data *pa;
+ struct krb5_preauth_context_module_st *module;
+ krb5_clpreauth_prep_questions_fn prep_questions;
+ int i, j;
+
+ k5_response_items_reset(rock->rctx.items);
+ for (i = 0; in_padata[i] != NULL; i++) {
+ pa = in_padata[i];
+ for (j = 0; j < context->preauth_context->n_modules; j++) {
+ module = &context->preauth_context->modules[j];
+ prep_questions = module->client_prep_questions;
+ if (module->pa_type != pa->pa_type || prep_questions == NULL)
+ continue;
+ ret = (*prep_questions)(context, module->moddata,
+ *module->modreq_p,
+ (krb5_get_init_creds_opt *)opte,
+ &callbacks, rock, request,
+ encoded_request_body,
+ encoded_previous_request, pa);
+ if (ret)
+ return ret;
+ }
+ }
+ return 0;
+}
+
krb5_error_code KRB5_CALLCONV
krb5_do_preauth(krb5_context context, krb5_kdc_req *request,
krb5_data *encoded_request_body,
@@ -802,6 +883,7 @@ krb5_do_preauth(krb5_context context, krb5_kdc_req *request,
int out_pa_list_size = 0;
krb5_pa_data **out_pa_list = NULL;
krb5_error_code ret, module_ret;
+ krb5_responder_fn responder = opte->opt_private->responder;
static const int paorder[] = { PA_INFO, PA_REAL };
*out_padata = NULL;
@@ -839,7 +921,22 @@ krb5_do_preauth(krb5_context context, krb5_kdc_req *request,
goto error;
}
- /* First do all the informational preauths, then the first real one. */
+ /* Get a list of response items for in_padata from the preauth modules. */
+ ret = fill_response_items(context, request, encoded_request_body,
+ encoded_previous_request, in_padata, rock, opte);
+ if (ret)
+ goto error;
+
+ /* Call the responder to answer response items. */
+ if (responder != NULL && !k5_response_items_empty(rock->rctx.items)) {
+ ret = (*responder)(context, opte->opt_private->responder_data,
+ &rock->rctx);
+ if (ret)
+ goto error;
+ }
+
+ /* Produce output padata, first from all the informational preauths, then
+ * the from first real one. */
for (h = 0; h < sizeof(paorder) / sizeof(paorder[0]); h++) {
for (i = 0; in_padata[i] != NULL; i++) {
#ifdef DEBUG
diff --git a/src/lib/krb5/krb/response_items.c b/src/lib/krb5/krb/response_items.c
new file mode 100644
index 0000000000..243df48d0d
--- /dev/null
+++ b/src/lib/krb5/krb/response_items.c
@@ -0,0 +1,212 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/response_items.c - Response items */
+/*
+ * Copyright 2012 Red Hat, Inc.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Red Hat not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original Red Hat software.
+ * Red Hat makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include "k5-int.h"
+#include "int-proto.h"
+
+struct k5_response_items_st {
+ size_t count;
+ char **questions;
+ char **challenges;
+ char **answers;
+};
+
+krb5_error_code
+k5_response_items_new(k5_response_items **ri_out)
+{
+ *ri_out = calloc(1, sizeof(**ri_out));
+ return (*ri_out == NULL) ? ENOMEM : 0;
+}
+
+void
+k5_response_items_free(k5_response_items *ri)
+{
+ k5_response_items_reset(ri);
+ free(ri);
+}
+
+void
+k5_response_items_reset(k5_response_items *ri)
+{
+ size_t i;
+
+ for (i = 0; i < ri->count; i++)
+ free(ri->questions[i]);
+ free(ri->questions);
+ ri->questions = NULL;
+
+ for (i = 0; i < ri->count; i++)
+ zapfreestr(ri->challenges[i]);
+ free(ri->challenges);
+ ri->challenges = NULL;
+
+ for (i = 0; i < ri->count; i++)
+ zapfreestr(ri->answers[i]);
+ free(ri->answers);
+ ri->answers = NULL;
+
+ ri->count = 0;
+}
+
+krb5_boolean
+k5_response_items_empty(const k5_response_items *ri)
+{
+ return ri->count == 0;
+}
+
+const char * const *
+k5_response_items_list_questions(const k5_response_items *ri)
+{
+ return (const char * const *)ri->questions;
+}
+
+static ssize_t
+find_question(const k5_response_items *ri, const char *question)
+{
+ size_t i;
+
+ for (i = 0; i < ri->count; i++) {
+ if (strcmp(ri->questions[i], question) == 0)
+ return i;
+ }
+
+ return -1;
+}
+
+static krb5_error_code
+push_question(k5_response_items *ri, const char *question,
+ const char *challenge)
+{
+ char **tmp;
+ const size_t size = sizeof(char*) * (ri->count + 2);
+
+ tmp = realloc(ri->questions, size);
+ if (tmp == NULL)
+ return ENOMEM;
+ ri->questions = tmp;
+ ri->questions[ri->count] = NULL;
+ ri->questions[ri->count + 1] = NULL;
+
+ tmp = realloc(ri->challenges, size);
+ if (tmp == NULL)
+ return ENOMEM;
+ ri->challenges = tmp;
+ ri->challenges[ri->count] = NULL;
+ ri->challenges[ri->count + 1] = NULL;
+
+ tmp = realloc(ri->answers, size);
+ if (tmp == NULL)
+ return ENOMEM;
+ ri->answers = tmp;
+ ri->answers[ri->count] = NULL;
+ ri->answers[ri->count + 1] = NULL;
+
+ ri->questions[ri->count] = strdup(question);
+ if (ri->questions[ri->count] == NULL)
+ return ENOMEM;
+
+ if (challenge != NULL) {
+ ri->challenges[ri->count] = strdup(challenge);
+ if (ri->challenges[ri->count] == NULL) {
+ free(ri->questions[ri->count]);
+ ri->questions[ri->count] = NULL;
+ return ENOMEM;
+ }
+ }
+
+ ri->count++;
+ return 0;
+}
+
+krb5_error_code
+k5_response_items_ask_question(k5_response_items *ri, const char *question,
+ const char *challenge)
+{
+ ssize_t i;
+ char *tmp = NULL;
+
+ i = find_question(ri, question);
+ if (i < 0)
+ return push_question(ri, question, challenge);
+
+ if (challenge != NULL) {
+ tmp = strdup(challenge);
+ if (tmp == NULL)
+ return ENOMEM;
+ }
+
+ zapfreestr(ri->challenges[i]);
+ ri->challenges[i] = tmp;
+ return 0;
+}
+
+const char *
+k5_response_items_get_challenge(const k5_response_items *ri,
+ const char *question)
+{
+ ssize_t i;
+
+ i = find_question(ri, question);
+ if (i < 0)
+ return NULL;
+
+ return ri->challenges[i];
+}
+
+krb5_error_code
+k5_response_items_set_answer(k5_response_items *ri, const char *question,
+ const char *answer)
+{
+ char *tmp = NULL;
+ ssize_t i;
+
+ i = find_question(ri, question);
+ if (i < 0)
+ return EINVAL;
+
+ if (answer != NULL) {
+ tmp = strdup(answer);
+ if (tmp == NULL)
+ return ENOMEM;
+ }
+
+ zapfreestr(ri->answers[i]);
+ ri->answers[i] = tmp;
+ return 0;
+}
+
+const char *
+k5_response_items_get_answer(const k5_response_items *ri,
+ const char *question)
+{
+ ssize_t i;
+
+ i = find_question(ri, question);
+ if (i < 0)
+ return NULL;
+
+ return ri->answers[i];
+}
diff --git a/src/lib/krb5/krb/t_response_items.c b/src/lib/krb5/krb/t_response_items.c
new file mode 100644
index 0000000000..0deb9292a1
--- /dev/null
+++ b/src/lib/krb5/krb/t_response_items.c
@@ -0,0 +1,94 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/t_response_set.c - Test krb5_response_set */
+/*
+ * Copyright 2012 Red Hat, Inc.
+ * All Rights Reserved.
+ *
+ * Export of this software from the United States of America may
+ * require a specific license from the United States Government.
+ * It is the responsibility of any person or organization contemplating
+ * export to obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of Red Hat not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original Red Hat software.
+ * Red Hat makes no representations about the suitability of
+ * this software for any purpose. It is provided "as is" without express
+ * or implied warranty.
+ */
+
+#include <k5-int.h>
+
+#include "int-proto.h"
+
+#define TEST_STR1 "test1"
+#define TEST_STR2 "test2"
+
+static void
+check_pred(int predicate)
+{
+ if (!predicate)
+ abort();
+}
+
+static void
+check(krb5_error_code code)
+{
+ if (code != 0) {
+ com_err("t_response_items", code, NULL);
+ abort();
+ }
+}
+
+static int
+nstrcmp(const char *a, const char *b)
+{
+ if (a == NULL && b == NULL)
+ return 0;
+ else if (a == NULL)
+ return -1;
+ else if (b == NULL)
+ return 1;
+
+ return strcmp(a, b);
+}
+
+int
+main()
+{
+ k5_response_items *ri;
+
+ check(k5_response_items_new(&ri));
+ check_pred(k5_response_items_empty(ri));
+
+ check(k5_response_items_ask_question(ri, TEST_STR1, TEST_STR1));
+ check(k5_response_items_ask_question(ri, TEST_STR2, NULL));
+ check_pred(nstrcmp(k5_response_items_get_challenge(ri, TEST_STR1),
+ TEST_STR1) == 0);
+ check_pred(nstrcmp(k5_response_items_get_challenge(ri, TEST_STR2),
+ NULL) == 0);
+ check_pred(!k5_response_items_empty(ri));
+
+ k5_response_items_reset(ri);
+ check_pred(k5_response_items_empty(ri));
+ check_pred(k5_response_items_get_challenge(ri, TEST_STR1) == NULL);
+ check_pred(k5_response_items_get_challenge(ri, TEST_STR2) == NULL);
+
+ check(k5_response_items_ask_question(ri, TEST_STR1, TEST_STR1));
+ check_pred(nstrcmp(k5_response_items_get_challenge(ri, TEST_STR1),
+ TEST_STR1) == 0);
+ check(k5_response_items_set_answer(ri, TEST_STR1, TEST_STR1));
+ check_pred(nstrcmp(k5_response_items_get_answer(ri, TEST_STR1),
+ TEST_STR1) == 0);
+
+ k5_response_items_free(ri);
+
+ return 0;
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 270959876f..701aa398c4 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -373,6 +373,7 @@ krb5_get_init_creds_opt_set_pa
krb5_get_init_creds_opt_set_preauth_list
krb5_get_init_creds_opt_set_proxiable
krb5_get_init_creds_opt_set_renew_life
+krb5_get_init_creds_opt_set_responder
krb5_get_init_creds_opt_set_salt
krb5_get_init_creds_opt_set_tkt_life
krb5_get_init_creds_password
@@ -524,6 +525,9 @@ krb5_realm_compare
krb5_recvauth
krb5_recvauth_version
krb5_register_serializer
+krb5_responder_get_challenge
+krb5_responder_list_questions
+krb5_responder_set_answer
krb5_salttype_to_string
krb5_sendauth
krb5_sendto_kdc
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index 09adc92e00..a363801b25 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -431,3 +431,7 @@ EXPORTS
krb5_cccol_have_content @402
krb5_kt_client_default @403
krb5int_cc_user_set_default_name @404 ; PRIVATE LEASH
+ krb5_get_init_creds_opt_set_responder @405
+ krb5_responder_get_challenge @406
+ krb5_responder_list_questions @407
+ krb5_responder_set_answer @408