krb5 commit: Add responder feature for initial cred exchanges

Greg Hudson ghudson at MIT.EDU
Tue Sep 11 01:08:07 EDT 2012


https://github.com/krb5/krb5/commit/43f507711689a71d3aaec8696721b8c981f8428e
commit 43f507711689a71d3aaec8696721b8c981f8428e
Author: Nathaniel McCallum <npmccallum at redhat.com>
Date:   Mon Sep 10 17:38:23 2012 -0400

    Add responder feature for initial cred exchanges
    
    Add new APIs:
    * krb5_get_init_creds_opt_set_responder
    * krb5_responder_get_challenge
    * krb5_responder_list_questions
    * krb5_responder_set_answer
    
    If a caller sets a responder, it will be invoked after preauth modules
    have had a chance to review their incoming padata but before they produce
    outgoing padata.  The responder will be presented a set of questions with
    optional challenges.  The responder should then answer all questions it knows
    how to handle.  Both the answers and the challenges are printable UTF-8 and
    may contain encoded, structured data specific to the question asked.
    
    Add two new callbacks and one optional method to the clpreauth
    interface.  The new method (prep_questions) allows modules to ask questions
    by setting them in the responder context using one of the new callbacks
    (ask_responder_question).  The other new callback (get_responder_answer) is
    used by the process method to read the answers to the questions asked.
    
    ticket: 7355 (new)

 .gitignore                          |    1 +
 src/include/k5-int.h                |    8 ++
 src/include/krb5/krb5.hin           |   71 ++++++++++++
 src/include/krb5/preauth_plugin.h   |   37 ++++++-
 src/lib/krb5/krb/Makefile.in        |   12 ++-
 src/lib/krb5/krb/get_in_tkt.c       |    6 +
 src/lib/krb5/krb/gic_opt.c          |   17 +++
 src/lib/krb5/krb/init_creds_ctx.h   |    1 +
 src/lib/krb5/krb/int-proto.h        |   31 +++++
 src/lib/krb5/krb/preauth2.c         |  103 ++++++++++++++++-
 src/lib/krb5/krb/response_items.c   |  212 +++++++++++++++++++++++++++++++++++
 src/lib/krb5/krb/t_response_items.c |   94 +++++++++++++++
 src/lib/krb5/libkrb5.exports        |    4 +
 src/lib/krb5_32.def                 |    4 +
 14 files changed, 595 insertions(+), 6 deletions(-)

diff --git a/.gitignore b/.gitignore
index 840bc65..9c14c22 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 670915d..bf36a17 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 2f3974a..7c519f0 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 72fd92d..a9a2ab9 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 4d08478..31160a7 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 3f67df0..d52147a 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 a98a47e..2580abd 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 2653ee1..ae69ed0 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 f794f14..6f3de8f 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 2ed53ab..1153175 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 0000000..243df48
--- /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 0000000..0deb929
--- /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 2709598..701aa39 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 09adc92..a363801 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


More information about the cvs-krb5 mailing list