krb5 commit: Add responder support to preauth_otp

Greg Hudson ghudson at MIT.EDU
Mon Oct 15 11:10:18 EDT 2012


https://github.com/krb5/krb5/commit/815da88a734c8a721e94fe0979ee5789b4576d10
commit 815da88a734c8a721e94fe0979ee5789b4576d10
Author: Nathaniel McCallum <npmccallum at redhat.com>
Date:   Mon Oct 15 10:49:21 2012 -0400

    Add responder support to preauth_otp

 doc/rst_source/krb_appldev/refs/api/index.rst |    3 +
 src/include/krb5/krb5.hin                     |  126 ++++++
 src/lib/krb5/krb/preauth_otp.c                |  590 ++++++++++++++++++++++++-
 src/lib/krb5/libkrb5.exports                  |    3 +
 src/lib/krb5_32.def                           |    3 +
 5 files changed, 707 insertions(+), 18 deletions(-)

diff --git a/doc/rst_source/krb_appldev/refs/api/index.rst b/doc/rst_source/krb_appldev/refs/api/index.rst
index dc9c35a..26b7f86 100644
--- a/doc/rst_source/krb_appldev/refs/api/index.rst
+++ b/doc/rst_source/krb_appldev/refs/api/index.rst
@@ -82,6 +82,9 @@ Frequently used public interfaces
    krb5_responder_get_challenge.rst
    krb5_responder_list_questions.rst
    krb5_responder_set_answer.rst
+   krb5_responder_otp_get_challenge.rst
+   krb5_responder_otp_set_answer.rst
+   krb5_responder_otp_challenge_free.rst
    krb5_set_default_realm.rst
    krb5_set_password.rst
    krb5_set_password_using_ccache.rst
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index db71f96..f338689 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -6367,6 +6367,66 @@ krb5_prompter_posix(krb5_context context, void *data, const char *name,
  */
 #define KRB5_RESPONDER_QUESTION_PASSWORD "password"
 
+/**
+ * OTP responder question
+ *
+ * The OTP responder question is asked when the KDC indicates that an OTP
+ * value is required in order to complete the authentication.  The JSON format
+ * of the challenge is:
+ *   {
+ *     "service": <string (optional)>,
+ *     "tokenInfo": [
+ *       {
+ *         "flags":     <number>,
+ *         "vendor":    <string (optional)>,
+ *         "challenge": <string (optional)>,
+ *         "length":    <number (optional)>,
+ *         "format":    <number (optional)>,
+ *         "tokenID":   <string (optional)>,
+ *         "algID":     <string (optional)>,
+ *       },
+ *       ...
+ *     ]
+ *   }
+ *
+ * The answer to the question MUST be JSON formatted:
+ *   {
+ *     "tokeninfo": <number>,
+ *     "value":     <string (optional)>,
+ *     "pin":       <string (optional)>,
+ *   }
+ *
+ * For more detail, please see RFC 6560.
+ *
+ * @version First introduced in 1.11
+ */
+#define KRB5_RESPONDER_QUESTION_OTP "otp"
+
+/**
+ * These format constants identify the format of the token value.
+ */
+#define KRB5_RESPONDER_OTP_FORMAT_DECIMAL 0
+#define KRB5_RESPONDER_OTP_FORMAT_HEXADECIMAL 1
+#define KRB5_RESPONDER_OTP_FORMAT_ALPHANUMERIC 2
+#define KRB5_RESPONDER_OTP_FORMAT_BINARY 3
+
+/**
+ * This flag indicates that the token value MUST be collected.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN (1 << 0)
+
+/**
+ * This flag indicates that the PIN value MUST be collected.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN   (1 << 1)
+
+/**
+ * This flag indicates that the token is now in re-synchronization mode with
+ * the server.  The user is expected to reply with the next code displayed on
+ * the token.
+ */
+#define KRB5_RESPONDER_OTP_FLAGS_NEXTOTP       (1 << 2)
+
 typedef struct krb5_responder_context_st *krb5_responder_context;
 
 /**
@@ -6431,6 +6491,72 @@ typedef krb5_error_code
 (*krb5_responder_fn)(krb5_context ctx, krb5_responder_context rctx,
                      void *data);
 
+typedef struct _krb5_responder_otp_tokeninfo {
+    krb5_flags flags;
+    krb5_int32 format; /* -1 when not specified. */
+    krb5_int32 length; /* -1 when not specified. */
+    char *vendor;
+    char *challenge;
+    char *token_id;
+    char *alg_id;
+} krb5_responder_otp_tokeninfo;
+
+typedef struct _krb5_responder_otp_challenge {
+    char *service;
+    krb5_responder_otp_tokeninfo **tokeninfo;
+} krb5_responder_otp_challenge;
+
+/**
+ * Decode the KRB5_RESPONDER_QUESTION_OTP to a C struct.
+ *
+ * A convenience function which parses the KRB5_RESPONDER_QUESTION_OTP
+ * question challenge data, making it available in native C.  The main feature
+ * of this function is the ability to interact with OTP tokens without parsing
+ * the JSON.
+ *
+ * The returned value must be passed to krb5_responder_otp_challenge_free() to
+ * be freed.
+ *
+ * @param [in]  ctx             Library context
+ * @param [in]  rctx            Responder context
+ * @param [out] chl             Challenge structure
+ *
+ * @version First introduced in 1.11
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_responder_otp_get_challenge(krb5_context ctx,
+                                 krb5_responder_context rctx,
+                                 krb5_responder_otp_challenge **chl);
+
+/**
+ * Answer the KRB5_RESPONDER_QUESTION_OTP question.
+ *
+ * @param [in] ctx              Library context
+ * @param [in] rctx             Responder context
+ * @param [in] ti               The index of the tokeninfo selected
+ * @param [in] value            The value to set, or NULL for none
+ * @param [in] pin              The pin to set, or NULL for none
+ *
+ * @version First introduced in 1.11
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx,
+                              size_t ti, const char *value, const char *pin);
+
+/**
+ * Free the value returned by krb5_responder_otp_get_challenge().
+ *
+ * @param [in] ctx              Library context
+ * @param [in] rctx             Responder context
+ * @param [in] chl              The challenge to free
+ *
+ * @version First introduced in 1.11
+ */
+void KRB5_CALLCONV
+krb5_responder_otp_challenge_free(krb5_context ctx,
+                                  krb5_responder_context rctx,
+                                  krb5_responder_otp_challenge *chl);
+
 /** Store options for @c _krb5_get_init_creds */
 typedef struct _krb5_get_init_creds_opt {
     krb5_flags flags;
diff --git a/src/lib/krb5/krb/preauth_otp.c b/src/lib/krb5/krb/preauth_otp.c
index c72f8b6..9a550e8 100644
--- a/src/lib/krb5/krb/preauth_otp.c
+++ b/src/lib/krb5/krb/preauth_otp.c
@@ -29,6 +29,7 @@
  */
 
 #include "k5-int.h"
+#include "k5-json.h"
 #include "int-proto.h"
 
 #include <krb5/preauth_plugin.h>
@@ -37,6 +38,392 @@
 static krb5_preauthtype otp_client_supported_pa_types[] =
     { KRB5_PADATA_OTP_CHALLENGE, 0 };
 
+/* Frees a tokeninfo. */
+static void
+free_tokeninfo(krb5_responder_otp_tokeninfo *ti)
+{
+    if (ti == NULL)
+        return;
+
+    free(ti->alg_id);
+    free(ti->challenge);
+    free(ti->token_id);
+    free(ti->vendor);
+    free(ti);
+}
+
+/* Converts a property of a json object into a char*. */
+static krb5_error_code
+codec_value_to_string(k5_json_object obj, const char *key, char **string)
+{
+    k5_json_value val;
+    char *str;
+
+    val = k5_json_object_get(obj, key);
+    if (val == NULL)
+        return ENOENT;
+
+    if (k5_json_get_tid(val) != K5_JSON_TID_STRING)
+        return EINVAL;
+
+    str = strdup(k5_json_string_utf8(val));
+    if (str == NULL)
+        return ENOMEM;
+
+    *string = str;
+    return 0;
+}
+
+/* Converts a property of a json object into a krb5_data struct. */
+static krb5_error_code
+codec_value_to_data(k5_json_object obj, const char *key, krb5_data *data)
+{
+    krb5_error_code retval;
+    char *tmp;
+
+    retval = codec_value_to_string(obj, key, &tmp);
+    if (retval != 0)
+        return retval;
+
+    *data = string2data(tmp);
+    return 0;
+}
+
+/* Converts a krb5_data struct into a property of a JSON object. */
+static krb5_error_code
+codec_data_to_value(krb5_data *data, k5_json_object obj, const char *key)
+{
+    krb5_error_code retval;
+    k5_json_string str;
+
+    if (data->data == NULL)
+        return 0;
+
+    str = k5_json_string_create_len(data->data, data->length);
+    if (str == NULL)
+        return ENOMEM;
+
+    retval = k5_json_object_set(obj, key, str);
+    k5_json_release(str);
+    return retval == 0 ? 0 : ENOMEM;
+}
+
+/* Converts a property of a json object into a krb5_int32. */
+static krb5_error_code
+codec_value_to_int32(k5_json_object obj, const char *key, krb5_int32 *int32)
+{
+    k5_json_value val;
+
+    val = k5_json_object_get(obj, key);
+    if (val == NULL)
+        return ENOENT;
+
+    if (k5_json_get_tid(val) != K5_JSON_TID_NUMBER)
+        return EINVAL;
+
+    *int32 = k5_json_number_value(val);
+    return 0;
+}
+
+/* Converts a krb5_int32 into a property of a JSON object. */
+static krb5_error_code
+codec_int32_to_value(krb5_int32 int32, k5_json_object obj, const char *key)
+{
+    krb5_error_code retval;
+    k5_json_number num;
+
+    if (int32 == -1)
+        return 0;
+
+    num = k5_json_number_create(int32);
+    if (num == NULL)
+        return ENOMEM;
+
+    retval = k5_json_object_set(obj, key, num);
+    k5_json_release(num);
+    return retval == 0 ? 0 : ENOMEM;
+}
+
+/* Converts a krb5_otp_tokeninfo into a JSON object. */
+static krb5_error_code
+codec_encode_tokeninfo(krb5_otp_tokeninfo *ti, k5_json_object *out)
+{
+    krb5_error_code retval = 0;
+    k5_json_object obj;
+    krb5_flags flags;
+
+    obj = k5_json_object_create();
+    if (obj == NULL)
+        goto error;
+
+    flags = KRB5_RESPONDER_OTP_FLAGS_COLLECT_TOKEN;
+    if (ti->flags & KRB5_OTP_FLAG_COLLECT_PIN)
+        flags |= KRB5_RESPONDER_OTP_FLAGS_COLLECT_PIN;
+    if (ti->flags & KRB5_OTP_FLAG_NEXTOTP)
+        flags |= KRB5_RESPONDER_OTP_FLAGS_NEXTOTP;
+
+    retval = codec_int32_to_value(flags, obj, "flags");
+    if (retval != 0)
+        goto error;
+
+    retval = codec_data_to_value(&ti->vendor, obj, "vendor");
+    if (retval != 0)
+        goto error;
+
+    retval = codec_data_to_value(&ti->challenge, obj, "challenge");
+    if (retval != 0)
+        goto error;
+
+    retval = codec_int32_to_value(ti->length, obj, "length");
+    if (retval != 0)
+        goto error;
+
+    if (ti->format != KRB5_OTP_FORMAT_BASE64) {
+        retval = codec_int32_to_value(ti->format, obj, "format");
+        if (retval != 0)
+            goto error;
+    }
+
+    retval = codec_data_to_value(&ti->token_id, obj, "tokenID");
+    if (retval != 0)
+        goto error;
+
+    retval = codec_data_to_value(&ti->alg_id, obj, "algID");
+    if (retval != 0)
+        goto error;
+
+    *out = obj;
+    return 0;
+
+error:
+    k5_json_release(obj);
+    return retval;
+}
+
+/* Converts a krb5_pa_otp_challenge into a JSON object. */
+static krb5_error_code
+codec_encode_challenge(krb5_context ctx, krb5_pa_otp_challenge *chl,
+                       char **json)
+{
+    k5_json_object obj = NULL, tmp = NULL;
+    k5_json_string str = NULL;
+    k5_json_array arr = NULL;
+    krb5_error_code retval = 0;
+    int i;
+
+    obj = k5_json_object_create();
+    if (obj == NULL)
+        goto error;
+
+    if (chl->service.data) {
+        str = k5_json_string_create_len(chl->service.data,
+                                        chl->service.length);
+        if (str == NULL)
+            goto error;
+        retval = k5_json_object_set(obj, "service", str);
+        k5_json_release(str);
+        if (retval != 0) {
+            retval = ENOMEM;
+            goto error;
+        }
+    }
+
+    arr = k5_json_array_create();
+    if (arr == NULL)
+        goto error;
+
+    for (i = 0; chl->tokeninfo[i] != NULL ; i++) {
+        retval = codec_encode_tokeninfo(chl->tokeninfo[i], &tmp);
+        if (retval != 0)
+            goto error;
+
+        retval = k5_json_array_add(arr, tmp);
+        k5_json_release(tmp);
+        if (retval != 0) {
+            retval = ENOMEM;
+            goto error;
+        }
+    }
+
+    if (k5_json_object_set(obj, "tokenInfo", arr) != 0) {
+        retval = ENOMEM;
+        goto error;
+    }
+
+    *json = k5_json_encode(obj);
+    if (*json == NULL)
+        goto error;
+
+    k5_json_release(arr);
+    k5_json_release(obj);
+    return 0;
+
+error:
+    k5_json_release(arr);
+    k5_json_release(obj);
+    return retval == 0 ? ENOMEM : retval;
+}
+
+/* Converts a JSON object into a krb5_responder_otp_tokeninfo. */
+static krb5_responder_otp_tokeninfo *
+codec_decode_tokeninfo(k5_json_object obj)
+{
+    krb5_responder_otp_tokeninfo *ti = NULL;
+    krb5_error_code retval;
+
+    ti = calloc(1, sizeof(krb5_responder_otp_tokeninfo));
+    if (ti == NULL)
+        goto error;
+
+    retval = codec_value_to_int32(obj, "flags", &ti->flags);
+    if (retval != 0)
+        goto error;
+
+    retval = codec_value_to_string(obj, "vendor", &ti->vendor);
+    if (retval != 0 && retval != ENOENT)
+        goto error;
+
+    retval = codec_value_to_string(obj, "challenge", &ti->challenge);
+    if (retval != 0 && retval != ENOENT)
+        goto error;
+
+    retval = codec_value_to_int32(obj, "length", &ti->length);
+    if (retval == ENOENT)
+        ti->length = -1;
+    else if (retval != 0)
+        goto error;
+
+    retval = codec_value_to_int32(obj, "format", &ti->format);
+    if (retval == ENOENT)
+        ti->format = -1;
+    else if (retval != 0)
+        goto error;
+
+    retval = codec_value_to_string(obj, "tokenID", &ti->token_id);
+    if (retval != 0 && retval != ENOENT)
+        goto error;
+
+    retval = codec_value_to_string(obj, "algID", &ti->alg_id);
+    if (retval != 0 && retval != ENOENT)
+        goto error;
+
+    return ti;
+
+error:
+    free_tokeninfo(ti);
+    return NULL;
+}
+
+/* Converts a JSON object into a krb5_responder_otp_challenge. */
+static krb5_responder_otp_challenge *
+codec_decode_challenge(krb5_context ctx, const char *json)
+{
+    krb5_responder_otp_challenge *chl = NULL;
+    k5_json_value obj = NULL, arr = NULL, tmp = NULL;
+    krb5_error_code retval;
+    size_t i;
+
+    obj = k5_json_decode(json);
+    if (obj == NULL)
+        goto error;
+
+    if (k5_json_get_tid(obj) != K5_JSON_TID_OBJECT)
+        goto error;
+
+    arr = k5_json_object_get(obj, "tokenInfo");
+    if (arr == NULL)
+        goto error;
+
+    if (k5_json_get_tid(arr) != K5_JSON_TID_ARRAY)
+        goto error;
+
+    chl = calloc(1, sizeof(krb5_responder_otp_challenge));
+    if (chl == NULL)
+        goto error;
+
+    chl->tokeninfo = calloc(k5_json_array_length(arr) + 1,
+                            sizeof(krb5_responder_otp_tokeninfo*));
+    if (chl->tokeninfo == NULL)
+        goto error;
+
+    retval = codec_value_to_string(obj, "service", &chl->service);
+    if (retval != 0 && retval != ENOENT)
+        goto error;
+
+    for (i = 0; i < k5_json_array_length(arr); i++) {
+        tmp = k5_json_array_get(arr, i);
+        if (k5_json_get_tid(tmp) != K5_JSON_TID_OBJECT)
+            goto error;
+
+        chl->tokeninfo[i] = codec_decode_tokeninfo(tmp);
+        if (chl->tokeninfo[i] == NULL)
+            goto error;
+    }
+
+    k5_json_release(obj);
+    return chl;
+
+error:
+    if (chl != NULL) {
+        for (i = 0; chl->tokeninfo != NULL && chl->tokeninfo[i] != NULL; i++)
+            free_tokeninfo(chl->tokeninfo[i]);
+        free(chl->tokeninfo);
+        free(chl);
+    }
+    k5_json_release(obj);
+    return NULL;
+}
+
+/* Decode the responder answer into a tokeninfo, a value and a pin. */
+static krb5_error_code
+codec_decode_answer(krb5_context context, const char *answer,
+                    krb5_otp_tokeninfo **tis, krb5_otp_tokeninfo **ti,
+                    krb5_data *value, krb5_data *pin)
+{
+    krb5_error_code retval = EBADMSG;
+    k5_json_value val = NULL;
+    krb5_int32 indx, i;
+    krb5_data tmp;
+
+    if (answer == NULL)
+        return EBADMSG;
+
+    val = k5_json_decode(answer);
+    if (val == NULL)
+        goto cleanup;
+
+    if (k5_json_get_tid(val) != K5_JSON_TID_OBJECT)
+        goto cleanup;
+
+    retval = codec_value_to_int32(val, "tokeninfo", &indx);
+    if (retval != 0)
+        goto cleanup;
+
+    for (i = 0; tis[i] != NULL; i++) {
+        if (i == indx) {
+            retval = codec_value_to_data(val, "value", &tmp);
+            if (retval != 0 && retval != ENOENT)
+                goto cleanup;
+
+            retval = codec_value_to_data(val, "pin", pin);
+            if (retval != 0 && retval != ENOENT) {
+                krb5_free_data_contents(context, &tmp);
+                goto cleanup;
+            }
+
+            *value = tmp;
+            *ti = tis[i];
+            retval = 0;
+            goto cleanup;
+        }
+    }
+    retval = EINVAL;
+
+cleanup:
+    k5_json_release(val);
+    return retval;
+}
+
 /* Takes the nonce from the challenge and encrypts it into the request. */
 static krb5_error_code
 encrypt_nonce(krb5_context ctx, krb5_keyblock *key,
@@ -541,6 +928,57 @@ otp_client_get_flags(krb5_context context, krb5_preauthtype pa_type)
     return PA_REAL;
 }
 
+static void
+otp_client_request_init(krb5_context context, krb5_clpreauth_moddata moddata,
+                        krb5_clpreauth_modreq *modreq_out)
+{
+    *modreq_out = calloc(1, sizeof(krb5_pa_otp_challenge *));
+}
+
+static krb5_error_code
+otp_client_prep_questions(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)
+{
+    krb5_pa_otp_challenge *chl;
+    krb5_error_code retval;
+    krb5_data tmp;
+    char *json;
+
+    if (modreq == NULL)
+        return ENOMEM;
+
+    /* Decode the challenge. */
+    tmp = make_data(pa_data->contents, pa_data->length);
+    retval = decode_krb5_pa_otp_challenge(&tmp,
+                                          (krb5_pa_otp_challenge **)modreq);
+    if (retval != 0)
+        return retval;
+    chl = *(krb5_pa_otp_challenge **)modreq;
+
+    /* Remove unsupported tokeninfos. */
+    retval = filter_supported_tokeninfos(context, chl->tokeninfo);
+    if (retval != 0)
+        return retval;
+
+    /* Make the JSON representation. */
+    retval = codec_encode_challenge(context, chl, &json);
+    if (retval != 0)
+        return retval;
+
+    /* Ask the question. */
+    retval = cb->ask_responder_question(context, rock,
+                                        KRB5_RESPONDER_QUESTION_OTP,
+                                        json);
+    free(json);
+    return retval;
+}
+
 static krb5_error_code
 otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
                    krb5_clpreauth_modreq modreq, krb5_get_init_creds_opt *opt,
@@ -555,7 +993,12 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
     krb5_keyblock *as_key = NULL;
     krb5_pa_otp_req *req = NULL;
     krb5_error_code retval = 0;
-    krb5_data tmp, value, pin;
+    krb5_data value, pin;
+    const char *answer;
+
+    if (modreq == NULL)
+        return ENOMEM;
+    chl = *(krb5_pa_otp_challenge **)modreq;
 
     *pa_data_out = NULL;
 
@@ -569,22 +1012,21 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
     if (retval != 0)
         return retval;
 
-    /* Decode the challenge. */
-    tmp = make_data(pa_data->contents, pa_data->length);
-    retval = decode_krb5_pa_otp_challenge(&tmp, &chl);
-    if (retval != 0)
-        return retval;
-
-    /* Remove unsupported tokeninfos. */
-    retval = filter_supported_tokeninfos(context, chl->tokeninfo);
-    if (retval != 0)
-        goto error;
-
-    /* Have the user select a tokeninfo and enter a password/pin. */
-    retval = prompt_for_token(context, prompter, prompter_data,
-                              chl->tokeninfo, &ti, &value, &pin);
-    if (retval != 0)
-        goto error;
+    /* Attempt to get token selection from the responder. */
+    pin = empty_data();
+    value = empty_data();
+    answer = cb->get_responder_answer(context, rock,
+                                      KRB5_RESPONDER_QUESTION_OTP);
+    retval = codec_decode_answer(context, answer, chl->tokeninfo, &ti, &value,
+                                 &pin);
+    if (retval != 0) {
+        /* If the responder doesn't have a token selection,
+         * we need to select the token via prompting. */
+        retval = prompt_for_token(context, prompter, prompter_data,
+                                  chl->tokeninfo, &ti, &value, &pin);
+        if (retval != 0)
+            goto error;
+    }
 
     /* Make the request. */
     retval = make_request(context, ti, &value, &pin, &req);
@@ -601,11 +1043,21 @@ otp_client_process(krb5_context context, krb5_clpreauth_moddata moddata,
 error:
     krb5_free_data_contents(context, &value);
     krb5_free_data_contents(context, &pin);
-    k5_free_pa_otp_challenge(context, chl);
     k5_free_pa_otp_req(context, req);
     return retval;
 }
 
+static void
+otp_client_request_fini(krb5_context context, krb5_clpreauth_moddata moddata,
+                        krb5_clpreauth_modreq modreq)
+{
+    if (modreq == NULL)
+        return;
+
+    k5_free_pa_otp_challenge(context, *(krb5_pa_otp_challenge **)modreq);
+    free(modreq);
+}
+
 krb5_error_code
 clpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
                      krb5_plugin_vtable vtable)
@@ -619,8 +1071,110 @@ clpreauth_otp_initvt(krb5_context context, int maj_ver, int min_ver,
     vt->name = "otp";
     vt->pa_type_list = otp_client_supported_pa_types;
     vt->flags = otp_client_get_flags;
+    vt->request_init = otp_client_request_init;
+    vt->prep_questions = otp_client_prep_questions;
     vt->process = otp_client_process;
+    vt->request_fini = otp_client_request_fini;
     vt->gic_opts = NULL;
 
     return 0;
 }
+
+krb5_error_code
+krb5_responder_otp_get_challenge(krb5_context ctx,
+                                 krb5_responder_context rctx,
+                                 krb5_responder_otp_challenge **chl)
+{
+    const char *answer;
+    krb5_responder_otp_challenge *challenge;
+
+    answer = krb5_responder_get_challenge(ctx, rctx,
+                                          KRB5_RESPONDER_QUESTION_OTP);
+    if (answer == NULL) {
+        *chl = NULL;
+        return 0;
+    }
+
+    challenge = codec_decode_challenge(ctx, answer);
+    if (challenge == NULL)
+        return ENOMEM;
+
+    *chl = challenge;
+    return 0;
+}
+
+krb5_error_code
+krb5_responder_otp_set_answer(krb5_context ctx, krb5_responder_context rctx,
+                              size_t ti, const char *value, const char *pin)
+{
+    krb5_error_code retval;
+    k5_json_object obj = NULL;
+    k5_json_value val = NULL;
+    char *tmp;
+
+    obj = k5_json_object_create();
+    if (obj == NULL)
+        goto error;
+
+    val = k5_json_number_create(ti);
+    if (val == NULL)
+        goto error;
+
+    retval = k5_json_object_set(obj, "tokeninfo", val);
+    k5_json_release(val);
+    if (retval != 0)
+        goto error;
+
+    if (value != NULL) {
+        val = k5_json_string_create(value);
+        if (val == NULL)
+            goto error;
+
+        retval = k5_json_object_set(obj, "value", val);
+        k5_json_release(val);
+        if (retval != 0)
+            goto error;
+    }
+
+    if (pin != NULL) {
+        val = k5_json_string_create(pin);
+        if (val == NULL)
+            goto error;
+
+        retval = k5_json_object_set(obj, "pin", val);
+        k5_json_release(val);
+        if (retval != 0)
+            goto error;
+    }
+
+    tmp = k5_json_encode(obj);
+    k5_json_release(obj);
+    if (tmp == NULL)
+        goto error;
+
+    retval = krb5_responder_set_answer(ctx, rctx, KRB5_RESPONDER_QUESTION_OTP,
+                                       tmp);
+    free(tmp);
+    return retval;
+
+error:
+    k5_json_release(obj);
+    return ENOMEM;
+}
+
+void
+krb5_responder_otp_challenge_free(krb5_context ctx,
+                                  krb5_responder_context rctx,
+                                  krb5_responder_otp_challenge *chl)
+{
+    size_t i;
+
+    if (chl == NULL)
+        return;
+
+    for (i = 0; chl->tokeninfo[i]; i++)
+        free_tokeninfo(chl->tokeninfo[i]);
+    free(chl->service);
+    free(chl->tokeninfo);
+    free(chl);
+}
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index 701aa39..d8c181d 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -528,6 +528,9 @@ krb5_register_serializer
 krb5_responder_get_challenge
 krb5_responder_list_questions
 krb5_responder_set_answer
+krb5_responder_otp_get_challenge
+krb5_responder_otp_set_answer
+krb5_responder_otp_challenge_free
 krb5_salttype_to_string
 krb5_sendauth
 krb5_sendto_kdc
diff --git a/src/lib/krb5_32.def b/src/lib/krb5_32.def
index 21419b7..6fe0784 100644
--- a/src/lib/krb5_32.def
+++ b/src/lib/krb5_32.def
@@ -439,3 +439,6 @@ EXPORTS
 	krb5_rc_resolve_full				@410 ; PRIVATE GSSAPI
 	krb5_rc_get_name				@411 ; PRIVATE GSSAPI
 	krb5_rc_get_type				@412 ; PRIVATE GSSAPI
+	krb5_responder_otp_get_challenge		@413
+	krb5_responder_otp_set_answer			@414
+	krb5_responder_otp_challenge_free		@415


More information about the cvs-krb5 mailing list