Stop using SALT_TYPE_AFS_LENGTH
[krb5.git] / src / lib / krb5 / krb / preauth2.c
index 73f4e79b613b95c226536bbd39aa17d604b3c477..7c5452790218ae0128732bd4d4db72231a1fc05b 100644 (file)
@@ -1,5 +1,6 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
 /*
- * Copyright 1995, 2003, 2008 by the Massachusetts Institute of Technology.  All
+ * Copyright 1995, 2003, 2008, 2012 by the Massachusetts Institute of Technology.  All
  * Rights Reserved.
  *
  * Export of this software from the United States of America may
 
 /*
  * This file contains routines for establishing, verifying, and any other
- * necessary functions, for utilizing the pre-authentication field of the 
+ * necessary functions, for utilizing the pre-authentication field of the
  * kerberos kdc request, with various hardware/software verification devices.
  */
 
 #include "k5-int.h"
-#if APPLE_PKINIT
-#include "pkinit_client.h"
-#include "pkinit_cert_store.h"
-#endif /* APPLE_PKINIT */
 #include "osconf.h"
 #include <krb5/preauth_plugin.h>
 #include "int-proto.h"
 #include <unistd.h>
 #endif
 
-#if TARGET_OS_MAC
-static const char *objdirs[] = { KRB5_PLUGIN_BUNDLE_DIR, LIBDIR "/krb5/plugins/preauth", NULL }; /* should be a list */
-#else
-static const char *objdirs[] = { LIBDIR "/krb5/plugins/preauth", NULL };
-#endif
+/* This structure lets us keep track of all of the modules which are loaded,
+ * turning the list of modules and their lists of implemented preauth types
+ * into a single list which we can walk easily. */
+struct krb5_preauth_context_st {
+    int n_modules;
+    struct krb5_preauth_context_module_st {
+        /* Which of the possibly more than one preauth types which the
+         * module supports we're using at this point in the list. */
+        krb5_preauthtype pa_type;
+        /* Encryption types which the client claims to support -- we
+         * copy them directly into the krb5_kdc_req structure during
+         * krb5_preauth_prepare_request(). */
+        krb5_enctype *enctypes;
+        /* The plugin's module data and a function to clear it. */
+        krb5_clpreauth_moddata moddata;
+        krb5_clpreauth_fini_fn client_fini;
+        /* The module's table, and some of its members, copied here for
+         * convenience when we populated the list. */
+        const char *name;
+        int flags, use_count;
+        krb5_clpreauth_process_fn client_process;
+        krb5_clpreauth_tryagain_fn client_tryagain;
+        krb5_clpreauth_supply_gic_opts_fn client_supply_gic_opts;
+        krb5_clpreauth_request_init_fn client_req_init;
+        krb5_clpreauth_request_fini_fn client_req_fini;
+        /* The per-request context which the client_req_init() function
+         * might allocate, which we'll need to clean up later by
+         * calling the client_req_fini() function. */
+        krb5_clpreauth_modreq modreq;
+        /* A pointer to the request_context pointer.  All modules within
+         * a plugin will point at the request_context of the first
+         * module within the plugin. */
+        krb5_clpreauth_modreq *modreq_p;
+    } *modules;
+};
 
 typedef krb5_error_code (*pa_function)(krb5_context,
-                                      krb5_kdc_req *request,
-                                      krb5_pa_data *in_padata,
-                                      krb5_pa_data **out_padata,
-                                      krb5_data *salt, krb5_data *s2kparams,
-                                      krb5_enctype *etype,
-                                      krb5_keyblock *as_key,
-                                      krb5_prompter_fct prompter_fct,
-                                      void *prompter_data,
-                                      krb5_gic_get_as_key_fct gak_fct,
-                                      void *gak_data);
-                                
+                                       krb5_kdc_req *request,
+                                       krb5_pa_data *in_padata,
+                                       krb5_pa_data **out_padata,
+                                       krb5_data *salt, krb5_data *s2kparams,
+                                       krb5_enctype *etype,
+                                       krb5_keyblock *as_key,
+                                       krb5_prompter_fct prompter_fct,
+                                       void *prompter_data,
+                                       krb5_gic_get_as_key_fct gak_fct,
+                                       void *gak_data);
+
 typedef struct _pa_types_t {
     krb5_preauthtype type;
     pa_function fct;
@@ -75,135 +102,131 @@ typedef struct _pa_types_t {
 void KRB5_CALLCONV
 krb5_init_preauth_context(krb5_context kcontext)
 {
-    int n_modules, n_tables, i, j, k;
-    void **tables;
-    struct krb5plugin_preauth_client_ftable_v1 *table;
+    int n_tables, n_modules, i, count;
+    krb5_plugin_initvt_fn *plugins = NULL, *pl;
+    struct krb5_clpreauth_vtable_st *vtables = NULL, *vt;
+    struct krb5_preauth_context_module_st *mod;
     krb5_preauth_context *context = NULL;
-    void *plugin_context;
-    krb5_preauthtype pa_type;
-    void **rcpp;
+    krb5_clpreauth_moddata moddata;
+    krb5_preauthtype pa_type, *pat;
+    krb5_boolean first;
+    krb5_clpreauth_modreq *rcpp;
 
     /* Only do this once for each krb5_context */
     if (kcontext->preauth_context != NULL)
-       return;
-
-    /* load the plugins for the current context */
-    if (PLUGIN_DIR_OPEN(&kcontext->preauth_plugins) == 0) {
-       if (krb5int_open_plugin_dirs(objdirs, NULL,
-                                    &kcontext->preauth_plugins,
-                                    &kcontext->err) != 0) {
-               return;
-       }
-    }
+        return;
 
-    /* pull out the module function tables for all of the modules */
-    tables = NULL;
-    if (krb5int_get_plugin_dir_data(&kcontext->preauth_plugins,
-                                   "preauthentication_client_1",
-                                   &tables,
-                                   &kcontext->err) != 0) {
-       return;
-    }
-    if (tables == NULL) {
-       return;
+    /* Auto-register built-in modules. */
+    k5_plugin_register_dyn(kcontext, PLUGIN_INTERFACE_CLPREAUTH, "pkinit",
+                           "preauth");
+    k5_plugin_register(kcontext, PLUGIN_INTERFACE_CLPREAUTH,
+                       "encrypted_challenge",
+                       clpreauth_encrypted_challenge_initvt);
+    k5_plugin_register(kcontext, PLUGIN_INTERFACE_CLPREAUTH,
+                       "encrypted_timestamp",
+                       clpreauth_encrypted_timestamp_initvt);
+    k5_plugin_register(kcontext, PLUGIN_INTERFACE_CLPREAUTH, "sam2",
+                       clpreauth_sam2_initvt);
+
+    /* Get all available clpreauth vtables. */
+    if (k5_plugin_load_all(kcontext, PLUGIN_INTERFACE_CLPREAUTH, &plugins))
+        return;
+    for (count = 0; plugins[count] != NULL; count++);
+    vtables = calloc(count, sizeof(*vtables));
+    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)
+            n_tables++;
     }
 
-    /* count how many modules we ended up loading, and how many preauth
-     * types we may claim to support as a result */
+    /* Count how many modules we ended up loading, and how many preauth
+     * types we may claim to support as a result. */
     n_modules = 0;
-    for (n_tables = 0;
-         (tables != NULL) && (tables[n_tables] != NULL);
-         n_tables++) {
-       table = tables[n_tables];
-       if ((table->pa_type_list != NULL) && (table->process != NULL)) {
-           for (j = 0; table->pa_type_list[j] > 0; j++) {
-               n_modules++;
-           }
-       }
+    for (i = 0; i < n_tables; i++) {
+        for (count = 0; vtables[i].pa_type_list[count] > 0; count++);
+        n_modules += count;
     }
 
-    /* allocate the space we need */
+    /* Allocate the space we need. */
     context = malloc(sizeof(*context));
-    if (context == NULL) {
-       krb5int_free_plugin_dir_data(tables);
-        return;
-    }
-    context->modules = calloc(n_modules, sizeof(context->modules[0]));
-    if (context->modules == NULL) {
-       krb5int_free_plugin_dir_data(tables);
-        free(context);
-        return;
-    }
-    context->n_modules = n_modules;
+    if (context == NULL)
+        goto cleanup;
+    context->modules = calloc(n_modules, sizeof(*context->modules));
+    if (context->modules == NULL)
+        goto cleanup;
 
     /* fill in the structure */
-    k = 0;
+    n_modules = 0;
     for (i = 0; i < n_tables; i++) {
-        table = tables[i];
-        if ((table->pa_type_list != NULL) && (table->process != NULL)) {
-           plugin_context = NULL;
-           if ((table->init != NULL) &&
-               ((*table->init)(kcontext, &plugin_context) != 0)) {
+        vt = &vtables[i];
+        if ((vt->pa_type_list != NULL) && (vt->process != NULL)) {
+            moddata = NULL;
+            if (vt->init != NULL && vt->init(kcontext, &moddata) != 0) {
 #ifdef DEBUG
-                   fprintf (stderr, "init err, skipping module \"%s\"\n",
-                            table->name);
+                fprintf(stderr, "init err, skipping module \"%s\"\n",
+                        vt->name);
 #endif
-                   continue;
-           }
-
-           rcpp = NULL;
-           for (j = 0; table->pa_type_list[j] > 0; j++) {
-               pa_type = table->pa_type_list[j];
-               context->modules[k].pa_type = pa_type;
-               context->modules[k].enctypes = table->enctype_list;
-               context->modules[k].plugin_context = plugin_context;
-               /* Only call client_fini once per plugin */
-               if (j == 0)
-                   context->modules[k].client_fini = table->fini;
-               else
-                   context->modules[k].client_fini = NULL;
-               context->modules[k].ftable = table;
-               context->modules[k].name = table->name;
-               context->modules[k].flags = (*table->flags)(kcontext, pa_type);
-               context->modules[k].use_count = 0;
-               context->modules[k].client_process = table->process;
-               context->modules[k].client_tryagain = table->tryagain;
-               if (j == 0)
-                   context->modules[k].client_supply_gic_opts = table->gic_opts;
-               else
-                   context->modules[k].client_supply_gic_opts = NULL;
-               context->modules[k].request_context = NULL;
-               /*
-                * Only call request_init and request_fini once per plugin.
-                * Only the first module within each plugin will ever
-                * have request_context filled in.  Every module within
-                * the plugin will have its request_context_pp pointing
-                * to that entry's request_context.  That way all the
-                * modules within the plugin share the same request_context
-                */
-               if (j == 0) {
-                   context->modules[k].client_req_init = table->request_init;
-                   context->modules[k].client_req_fini = table->request_fini;
-                   rcpp = &context->modules[k].request_context;
-               } else {
-                   context->modules[k].client_req_init = NULL;
-                   context->modules[k].client_req_fini = NULL;
-               }
-               context->modules[k].request_context_pp = rcpp;
+                continue;
+            }
+
+            rcpp = NULL;
+            for (pat = vt->pa_type_list, first = TRUE; *pat > 0;
+                 pat++, first = FALSE) {
+                pa_type = *pat;
+                mod = &context->modules[n_modules];
+                mod->pa_type = pa_type;
+                mod->enctypes = vt->enctype_list;
+                mod->moddata = moddata;
+                /* Only call client_fini once per plugin */
+                if (first)
+                    mod->client_fini = vt->fini;
+                else
+                    mod->client_fini = NULL;
+                mod->name = vt->name;
+                mod->flags = (*vt->flags)(kcontext, pa_type);
+                mod->use_count = 0;
+                mod->client_process = vt->process;
+                mod->client_tryagain = vt->tryagain;
+                mod->client_supply_gic_opts = first ? vt->gic_opts : NULL;
+                mod->modreq = NULL;
+                /*
+                 * Only call request_init and request_fini once per plugin.
+                 * Only the first module within each plugin will ever
+                 * have request_context filled in.  Every module within
+                 * the plugin will have its request_context_pp pointing
+                 * to that entry's request_context.  That way all the
+                 * modules within the plugin share the same request_context
+                 */
+                if (first) {
+                    mod->client_req_init = vt->request_init;
+                    mod->client_req_fini = vt->request_fini;
+                    rcpp = &mod->modreq;
+                } else {
+                    mod->client_req_init = NULL;
+                    mod->client_req_fini = NULL;
+                }
+                mod->modreq_p = rcpp;
 #ifdef DEBUG
-               fprintf (stderr, "init module \"%s\", pa_type %d, flag %d\n",
-                        context->modules[k].name,
-                        context->modules[k].pa_type,
-                        context->modules[k].flags);
+                fprintf(stderr, "init module \"%s\", pa_type %d, flag %d\n",
+                        mod->name, mod->pa_type, mod->flags);
 #endif
-               k++;
-           }
-       }
+                n_modules++;
+            }
+        }
     }
-    krb5int_free_plugin_dir_data(tables);
+    context->n_modules = n_modules;
 
-    /* return the result */
+    /* Place the constructed preauth context into the krb5 context. */
     kcontext->preauth_context = context;
+    context = NULL;
+
+cleanup:
+    if (context)
+        free(context->modules);
+    free(context);
+    k5_plugin_free_modules(kcontext, plugins);
+    free(vtables);
 }
 
 /* Zero the use counts for the modules herein.  Usually used before we
@@ -214,57 +237,12 @@ krb5_clear_preauth_context_use_counts(krb5_context context)
 {
     int i;
     if (context->preauth_context != NULL) {
-       for (i = 0; i < context->preauth_context->n_modules; i++) {
-           context->preauth_context->modules[i].use_count = 0;
-       }
+        for (i = 0; i < context->preauth_context->n_modules; i++) {
+            context->preauth_context->modules[i].use_count = 0;
+        }
     }
 }
 
-/*
- * Give all the preauth plugins a look at the preauth option which
- * has just been set
- */
-krb5_error_code
-krb5_preauth_supply_preauth_data(krb5_context context,
-                                krb5_gic_opt_ext *opte,
-                                const char *attr,
-                                const char *value)
-{
-    krb5_error_code retval = 0;
-    int i;
-    void *pctx;
-    const char *emsg = NULL;
-
-    if (context->preauth_context == NULL)
-       krb5_init_preauth_context(context);
-    if (context->preauth_context == NULL) {
-       retval = EINVAL;
-       krb5int_set_error(&context->err, retval,
-               "krb5_preauth_supply_preauth_data: "
-               "Unable to initialize preauth context");
-       return retval;
-    }
-
-    /*
-     * Go down the list of preauth modules, and supply them with the
-     * attribute/value pair.
-     */
-    for (i = 0; i < context->preauth_context->n_modules; i++) {
-       if (context->preauth_context->modules[i].client_supply_gic_opts == NULL)
-           continue;
-       pctx = context->preauth_context->modules[i].plugin_context;
-       retval = (*context->preauth_context->modules[i].client_supply_gic_opts)
-                               (context, pctx,
-                                (krb5_get_init_creds_opt *)opte, attr, value); 
-       if (retval) {
-           emsg = krb5_get_error_message(context, retval);
-           krb5int_set_error(&context->err, retval, "Preauth plugin %s: %s",
-                             context->preauth_context->modules[i].name, emsg);
-           break;
-       }
-    }
-    return retval;
-}
 
 /* Free the per-krb5_context preauth_context. This means clearing any
  * plugin-specific context which may have been created, and then
@@ -273,23 +251,19 @@ void KRB5_CALLCONV
 krb5_free_preauth_context(krb5_context context)
 {
     int i;
-    void *pctx;
-    if (context && context->preauth_context != NULL) {
-       for (i = 0; i < context->preauth_context->n_modules; i++) {
-           pctx = context->preauth_context->modules[i].plugin_context;
-           if (context->preauth_context->modules[i].client_fini != NULL) {
-               (*context->preauth_context->modules[i].client_fini)(context, pctx);
-           }
-           memset(&context->preauth_context->modules[i], 0,
-                  sizeof(context->preauth_context->modules[i]));
-       }
-       if (context->preauth_context->modules != NULL) {
-           free(context->preauth_context->modules);
-           context->preauth_context->modules = NULL;
-       }
-       free(context->preauth_context);
-       context->preauth_context = NULL;
-    }
+    struct krb5_preauth_context_module_st *mod;
+
+    if (context == NULL || context->preauth_context == NULL)
+        return;
+    for (i = 0; i < context->preauth_context->n_modules; i++) {
+        mod = &context->preauth_context->modules[i];
+        if (mod->client_fini != NULL)
+            mod->client_fini(context, mod->moddata);
+        zap(mod, sizeof(*mod));
+    }
+    free(context->preauth_context->modules);
+    free(context->preauth_context);
+    context->preauth_context = NULL;
 }
 
 /* Initialize the per-AS-REQ context. This means calling the client_req_init
@@ -298,19 +272,17 @@ void KRB5_CALLCONV
 krb5_preauth_request_context_init(krb5_context context)
 {
     int i;
-    void *rctx, *pctx;
+    struct krb5_preauth_context_module_st *mod;
 
-    /* Limit this to only one attempt per context? */
     if (context->preauth_context == NULL)
-       krb5_init_preauth_context(context);
-    if (context->preauth_context != NULL) {
-       for (i = 0; i < context->preauth_context->n_modules; i++) {
-           pctx = context->preauth_context->modules[i].plugin_context;
-           if (context->preauth_context->modules[i].client_req_init != NULL) {
-               rctx = context->preauth_context->modules[i].request_context_pp;
-               (*context->preauth_context->modules[i].client_req_init) (context, pctx, rctx);
-           }
-       }
+        krb5_init_preauth_context(context);
+    if (context->preauth_context == NULL)
+        return;
+    for (i = 0; i < context->preauth_context->n_modules; i++) {
+        context->preauth_context->modules[i].use_count = 0;
+        mod = &context->preauth_context->modules[i];
+        if (mod->client_req_init != NULL)
+            mod->client_req_init(context, mod->moddata, mod->modreq_p);
     }
 }
 
@@ -320,18 +292,17 @@ void KRB5_CALLCONV
 krb5_preauth_request_context_fini(krb5_context context)
 {
     int i;
-    void *rctx, *pctx;
-    if (context->preauth_context != NULL) {
-       for (i = 0; i < context->preauth_context->n_modules; i++) {
-           pctx = context->preauth_context->modules[i].plugin_context;
-           rctx = context->preauth_context->modules[i].request_context;
-           if (rctx != NULL) {
-               if (context->preauth_context->modules[i].client_req_fini != NULL) {
-                   (*context->preauth_context->modules[i].client_req_fini)(context, pctx, rctx);
-               }
-               context->preauth_context->modules[i].request_context = NULL;
-           }
-       }
+    struct krb5_preauth_context_module_st *mod;
+
+    if (context->preauth_context == NULL)
+        return;
+    for (i = 0; i < context->preauth_context->n_modules; i++) {
+        mod = &context->preauth_context->modules[i];
+        if (mod->modreq != NULL) {
+            if (mod->client_req_fini != NULL)
+                mod->client_req_fini(context, mod->moddata, mod->modreq);
+            mod->modreq = NULL;
+        }
     }
 }
 
@@ -342,18 +313,18 @@ grow_ktypes(krb5_enctype **out_ktypes, int *out_nktypes, krb5_enctype ktype)
     int i;
     krb5_enctype *ktypes;
     for (i = 0; i < *out_nktypes; i++) {
-       if ((*out_ktypes)[i] == ktype)
-           return;
+        if ((*out_ktypes)[i] == ktype)
+            return;
     }
     ktypes = malloc((*out_nktypes + 2) * sizeof(ktype));
     if (ktypes) {
-       for (i = 0; i < *out_nktypes; i++)
-           ktypes[i] = (*out_ktypes)[i];
-       ktypes[i++] = ktype;
-       ktypes[i] = 0;
-       free(*out_ktypes);
-       *out_ktypes = ktypes;
-       *out_nktypes = i;
+        for (i = 0; i < *out_nktypes; i++)
+            ktypes[i] = (*out_ktypes)[i];
+        ktypes[i++] = ktype;
+        ktypes[i] = 0;
+        free(*out_ktypes);
+        *out_ktypes = ktypes;
+        *out_nktypes = i;
     }
 }
 
@@ -363,160 +334,136 @@ grow_ktypes(krb5_enctype **out_ktypes, int *out_nktypes, krb5_enctype ktype)
  */
 static int
 grow_pa_list(krb5_pa_data ***out_pa_list, int *out_pa_list_size,
-            krb5_pa_data **addition, int num_addition)
+             krb5_pa_data **addition, int num_addition)
 {
     krb5_pa_data **pa_list;
     int i, j;
 
     if (out_pa_list == NULL || addition == NULL) {
-       return EINVAL;
+        return EINVAL;
     }
 
     if (*out_pa_list == NULL) {
-       /* Allocate room for the new additions and a NULL terminator. */
-       pa_list = malloc((num_addition + 1) * sizeof(krb5_pa_data *));
-       if (pa_list == NULL)
-           return ENOMEM;
-       for (i = 0; i < num_addition; i++)
-           pa_list[i] = addition[i];
-       pa_list[i] = NULL;
-       *out_pa_list = pa_list;
-       *out_pa_list_size = num_addition;
+        /* Allocate room for the new additions and a NULL terminator. */
+        pa_list = malloc((num_addition + 1) * sizeof(krb5_pa_data *));
+        if (pa_list == NULL)
+            return ENOMEM;
+        for (i = 0; i < num_addition; i++)
+            pa_list[i] = addition[i];
+        pa_list[i] = NULL;
+        *out_pa_list = pa_list;
+        *out_pa_list_size = num_addition;
     } else {
-       /*
-        * Allocate room for the existing entries plus
-        * the new additions and a NULL terminator.
-        */
-       pa_list = malloc((*out_pa_list_size + num_addition + 1)
-                                               * sizeof(krb5_pa_data *));
-       if (pa_list == NULL)
-           return ENOMEM;
-       for (i = 0; i < *out_pa_list_size; i++)
-           pa_list[i] = (*out_pa_list)[i];
-       for (j = 0; j < num_addition;)
-           pa_list[i++] = addition[j++];
-       pa_list[i] = NULL;
-       free(*out_pa_list);
-       *out_pa_list = pa_list;
-       *out_pa_list_size = i;
+        /*
+         * Allocate room for the existing entries plus
+         * the new additions and a NULL terminator.
+         */
+        pa_list = malloc((*out_pa_list_size + num_addition + 1)
+                         * sizeof(krb5_pa_data *));
+        if (pa_list == NULL)
+            return ENOMEM;
+        for (i = 0; i < *out_pa_list_size; i++)
+            pa_list[i] = (*out_pa_list)[i];
+        for (j = 0; j < num_addition;)
+            pa_list[i++] = addition[j++];
+        pa_list[i] = NULL;
+        free(*out_pa_list);
+        *out_pa_list = pa_list;
+        *out_pa_list_size = i;
     }
     return 0;
 }
 
-/*
- * Retrieve a specific piece of information required by the plugin and
- * return it in a new krb5_data item.  There are separate request_types
- * to obtain the data and free it.
- *
- * This may require massaging data into a contrived format, but it will
- * hopefully keep us from having to reveal library-internal functions
- * or data to the plugin modules.
- */
+static krb5_enctype
+get_etype(krb5_context context, krb5_clpreauth_rock rock)
+{
+    return *rock->etype;
+}
+
+static krb5_keyblock *
+fast_armor(krb5_context context, krb5_clpreauth_rock rock)
+{
+    return rock->fast_state->armor_key;
+}
 
 static krb5_error_code
-client_data_proc(krb5_context kcontext,
-                krb5_preauth_client_rock *rock,
-                krb5_int32 request_type,
-                krb5_data **retdata)
+get_as_key(krb5_context context, krb5_clpreauth_rock rock,
+           krb5_keyblock **keyblock)
 {
-    krb5_data *ret;
-    krb5_error_code retval;
-    char *data;
-
-    if (rock->magic != CLIENT_ROCK_MAGIC)
-       return EINVAL;
-    if (retdata == NULL)
-       return EINVAL;
-
-    switch (request_type) {
-    case krb5plugin_preauth_client_get_etype:
-       {
-           krb5_enctype *eptr;
-           ret = malloc(sizeof(krb5_data));
-           if (ret == NULL)
-               return ENOMEM;
-           data = malloc(sizeof(krb5_enctype));
-           if (data == NULL) {
-               free(ret);
-               return ENOMEM;
-           }
-           ret->data = data;
-           ret->length = sizeof(krb5_enctype);
-           eptr = (krb5_enctype *)data;
-           *eptr = *rock->etype;
-           *retdata = ret;
-           return 0;
-       }
-       break;
-    case krb5plugin_preauth_client_free_etype:
-       ret = *retdata;
-       if (ret == NULL)
-           return 0;
-       if (ret->data)
-           free(ret->data);
-       free(ret);
-       return 0;
-       break;
-    case krb5plugin_preauth_client_fast_armor: {
-       krb5_keyblock *key = NULL;
-       ret = calloc(1, sizeof(krb5_data));
-       if (ret == NULL)
-           return ENOMEM;
-       retval = 0;
-       if (rock->fast_state->armor_key)
-           retval = krb5_copy_keyblock(kcontext, rock->fast_state->armor_key,
-                                       &key);
-       if (retval == 0) {
-           ret->data = (char *) key;
-           ret->length = key?sizeof(krb5_keyblock):0;
-           key = NULL;
-       }
-       if (retval == 0) {
-           *retdata = ret;
-           ret = NULL;
-       }
-       if (ret)
-           free(ret);
-       return retval;
+    krb5_error_code ret;
+    krb5_data *salt;
+
+    if (rock->as_key->length == 0) {
+        salt = (*rock->default_salt) ? NULL : rock->salt;
+        ret = (*rock->gak_fct)(context, rock->client, *rock->etype,
+                               rock->prompter, rock->prompter_data, salt,
+                               rock->s2kparams, rock->as_key, *rock->gak_data);
+        if (ret)
+            return ret;
     }
-    case krb5plugin_preauth_client_free_fast_armor:
-       ret = *retdata;
-       if (ret) {
-           if (ret->data)
-               krb5_free_keyblock(kcontext, (krb5_keyblock *) ret->data);
-           free(ret);
-           *retdata = NULL;
-       }
-       return 0;
-               default:
-       return EINVAL;
+    *keyblock = rock->as_key;
+    return 0;
+}
+
+static krb5_error_code
+set_as_key(krb5_context context, krb5_clpreauth_rock rock,
+           const krb5_keyblock *keyblock)
+{
+    krb5_free_keyblock_contents(context, rock->as_key);
+    return krb5_copy_keyblock_contents(context, keyblock, rock->as_key);
+}
+
+static krb5_error_code
+get_preauth_time(krb5_context context, krb5_clpreauth_rock rock,
+                 krb5_boolean allow_unauth_time, krb5_timestamp *time_out,
+                 krb5_int32 *usec_out)
+{
+    if (rock->pa_offset_state != NO_OFFSET &&
+        (allow_unauth_time || rock->pa_offset_state == AUTH_OFFSET) &&
+        (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME)) {
+        /* Use the offset we got from the preauth-required error. */
+        return k5_time_with_offset(rock->pa_offset, rock->pa_offset_usec,
+                                   time_out, usec_out);
+
+    } else {
+        /* Use the time offset from the context, or no offset. */
+        return krb5_us_timeofday(context, time_out, usec_out);
     }
 }
 
+static struct krb5_clpreauth_callbacks_st callbacks = {
+    2,
+    get_etype,
+    fast_armor,
+    get_as_key,
+    set_as_key,
+    get_preauth_time
+};
+
 /* Tweak the request body, for now adding any enctypes which the module claims
  * to add support for to the list, but in the future perhaps doing more
  * involved things. */
 void KRB5_CALLCONV
 krb5_preauth_prepare_request(krb5_context kcontext,
-                            krb5_gic_opt_ext *opte,
-                            krb5_kdc_req *request)
+                             krb5_gic_opt_ext *opte,
+                             krb5_kdc_req *request)
 {
     int i, j;
 
     if (kcontext->preauth_context == NULL) {
-       return;
+        return;
     }
     /* Add the module-specific enctype list to the request, but only if
      * it's something we can safely modify. */
     if (!(opte && (opte->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))) {
-       for (i = 0; i < kcontext->preauth_context->n_modules; i++) {
-           if (kcontext->preauth_context->modules[i].enctypes == NULL)
-               continue;
-           for (j = 0; kcontext->preauth_context->modules[i].enctypes[j] != 0; j++) {
-               grow_ktypes(&request->ktype, &request->nktypes,
-                           kcontext->preauth_context->modules[i].enctypes[j]);
-           }
-       }
+        for (i = 0; i < kcontext->preauth_context->n_modules; i++) {
+            if (kcontext->preauth_context->modules[i].enctypes == NULL)
+                continue;
+            for (j = 0; kcontext->preauth_context->modules[i].enctypes[j] != 0; j++) {
+                grow_ktypes(&request->ktype, &request->nktypes,
+                            kcontext->preauth_context->modules[i].enctypes[j]);
+            }
+        }
     }
 }
 
@@ -524,90 +471,77 @@ krb5_preauth_prepare_request(krb5_context kcontext,
  * 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. */
 static krb5_error_code
-krb5_run_preauth_plugins(krb5_context kcontext,
-                        int module_required_flags,
-                        krb5_kdc_req *request,
-                        krb5_data *encoded_request_body,
-                        krb5_data *encoded_previous_request,
-                        krb5_pa_data *in_padata,
-                        krb5_prompter_fct prompter,
-                        void *prompter_data,
-                        preauth_get_as_key_proc gak_fct,
-                        krb5_data *salt,
-                        krb5_data *s2kparams,
-                        void *gak_data,
-                        krb5_preauth_client_rock *get_data_rock,
-                        krb5_keyblock *as_key,
-                        krb5_pa_data ***out_pa_list,
-                        int *out_pa_list_size,
-                        int *module_ret,
-                        int *module_flags,
-                        krb5_gic_opt_ext *opte)
+run_preauth_plugins(krb5_context kcontext,
+                    int module_required_flags,
+                    krb5_kdc_req *request,
+                    krb5_data *encoded_request_body,
+                    krb5_data *encoded_previous_request,
+                    krb5_pa_data *in_padata,
+                    krb5_prompter_fct prompter,
+                    void *prompter_data,
+                    krb5_clpreauth_rock preauth_rock,
+                    krb5_pa_data ***out_pa_list,
+                    int *out_pa_list_size,
+                    int *module_ret,
+                    int *module_flags,
+                    krb5_gic_opt_ext *opte)
 {
     int i;
     krb5_pa_data **out_pa_data;
     krb5_error_code ret;
-    struct _krb5_preauth_context_module *module;
+    struct krb5_preauth_context_module_st *module;
 
     if (kcontext->preauth_context == NULL) {
-       return ENOENT;
+        return ENOENT;
     }
     /* iterate over all loaded modules */
     for (i = 0; i < kcontext->preauth_context->n_modules; i++) {
-       module = &kcontext->preauth_context->modules[i];
-       /* skip over those which don't match the preauth type */
-       if (module->pa_type != in_padata->pa_type)
-           continue;
-       /* skip over those which don't match the flags (INFO vs REAL, mainly) */
-       if ((module->flags & module_required_flags) == 0)
-           continue;
-       /* if it's a REAL module, try to call it only once per library call */
-       if (module_required_flags & PA_REAL) {
-           if (module->use_count > 0) {
-#ifdef DEBUG
-               fprintf(stderr, "skipping already-used module \"%s\"(%d)\n",
-                       module->name, module->pa_type);
-#endif
-               continue;
-           }
-           module->use_count++;
-       }
-       /* run the module's callback function */
-       out_pa_data = NULL;
+        module = &kcontext->preauth_context->modules[i];
+        /* skip over those which don't match the preauth type */
+        if (module->pa_type != in_padata->pa_type)
+            continue;
+        /* skip over those which don't match the flags (INFO vs REAL, mainly) */
+        if ((module->flags & module_required_flags) == 0)
+            continue;
+        /* if it's a REAL module, try to call it only once per library call */
+        if (module_required_flags & PA_REAL) {
+            if (module->use_count > 0) {
+                TRACE_PREAUTH_SKIP(kcontext, module->name, module->pa_type);
+                continue;
+            }
+            module->use_count++;
+        }
+        /* run the module's callback function */
+        out_pa_data = NULL;
 #ifdef DEBUG
-       fprintf(stderr, "using module \"%s\" (%d), flags = %d\n",
-               module->name, module->pa_type, module->flags);
+        fprintf(stderr, "using module \"%s\" (%d), flags = %d\n",
+                module->name, module->pa_type, module->flags);
 #endif
-       ret = module->client_process(kcontext,
-                                    module->plugin_context,
-                                    *module->request_context_pp,
-                                    (krb5_get_init_creds_opt *)opte,
-                                    client_data_proc,
-                                    get_data_rock,
-                                    request,
-                                    encoded_request_body,
-                                    encoded_previous_request,
-                                    in_padata,
-                                    prompter, prompter_data,
-                                    gak_fct, gak_data, salt, s2kparams,
-                                    as_key,
-                                    &out_pa_data);
-       /* Make note of the module's flags and status. */
-       *module_flags = module->flags;
-       *module_ret = ret;
-       /* Save the new preauth data item. */
-       if (out_pa_data != NULL) {
-           int j;
-           for (j = 0; out_pa_data[j] != NULL; j++);
-           ret = grow_pa_list(out_pa_list, out_pa_list_size, out_pa_data, j);
-           free(out_pa_data);
-           if (ret != 0)
-               return ret;
-       }
-       break;
+        ret = module->client_process(kcontext, module->moddata,
+                                     *module->modreq_p,
+                                     (krb5_get_init_creds_opt *)opte,
+                                     &callbacks, preauth_rock,
+                                     request, encoded_request_body,
+                                     encoded_previous_request, in_padata,
+                                     prompter, prompter_data, &out_pa_data);
+        TRACE_PREAUTH_PROCESS(kcontext, module->name, module->pa_type,
+                              module->flags, ret);
+        /* Make note of the module's flags and status. */
+        *module_flags = module->flags;
+        *module_ret = ret;
+        /* Save the new preauth data item. */
+        if (out_pa_data != NULL) {
+            int j;
+            for (j = 0; out_pa_data[j] != NULL; j++);
+            ret = grow_pa_list(out_pa_list, out_pa_list_size, out_pa_data, j);
+            free(out_pa_data);
+            if (ret != 0)
+                return ret;
+        }
+        break;
     }
     if (i >= kcontext->preauth_context->n_modules) {
-       return ENOENT;
+        return ENOENT;
     }
     return 0;
 }
@@ -622,51 +556,123 @@ padata2data(krb5_pa_data p)
     return d;
 }
 
-static
-krb5_error_code pa_salt(krb5_context context,
-                       krb5_kdc_req *request,
-                       krb5_pa_data *in_padata,
-                       krb5_pa_data **out_padata,
-                       krb5_data *salt, krb5_data *s2kparams,
-                       krb5_enctype *etype,
-                       krb5_keyblock *as_key,
-                       krb5_prompter_fct prompter, void *prompter_data,
-                       krb5_gic_get_as_key_fct gak_fct, void *gak_data)
+/* Set etype info parameters in rock based on padata. */
+static krb5_error_code
+get_etype_info(krb5_context context, krb5_pa_data **padata,
+               krb5_kdc_req *request, krb5_clpreauth_rock rock)
 {
-    krb5_data tmp;
-
-    tmp = padata2data(*in_padata);
-    krb5_free_data_contents(context, salt);
-    krb5int_copy_data_contents(context, &tmp, salt);
+    krb5_error_code ret = 0;
+    krb5_pa_data *pa;
+    krb5_data d;
+    krb5_etype_info etype_info = NULL, e;
+    krb5_etype_info_entry *entry;
+    krb5_boolean valid_found;
+    const char *p;
+    int i;
 
-    if (in_padata->pa_type == KRB5_PADATA_AFS3_SALT)
-       salt->length = SALT_TYPE_AFS_LENGTH;
+    /* Find an etype-info2 or etype-info element in padata. */
+    pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_ETYPE_INFO2);
+    if (pa != NULL) {
+        d = padata2data(*pa);
+        ret = decode_krb5_etype_info2(&d, &etype_info);
+    } else {
+        pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_ETYPE_INFO2);
+        if (pa != NULL) {
+            d = padata2data(*pa);
+            ret = decode_krb5_etype_info2(&d, &etype_info);
+        }
+    }
+
+    if (etype_info != NULL) {
+        /* Search entries in order of the requests's enctype preference. */
+        entry = NULL;
+        valid_found = FALSE;
+        for (i = 0; i < request->nktypes && entry == NULL; i++) {
+            for (e = etype_info; *e != NULL && entry == NULL; e++) {
+                if ((*e)->etype == request->ktype[i])
+                    entry = *e;
+                if (krb5_c_valid_enctype((*e)->etype))
+                    valid_found = TRUE;
+            }
+        }
+        if (entry == NULL) {
+            ret = (valid_found) ? KRB5_CONFIG_ETYPE_NOSUPP :
+                KRB5_PROG_ETYPE_NOSUPP;
+            goto cleanup;
+        }
+
+        /* Set rock fields based on the entry we selected. */
+        *rock->etype = entry->etype;
+        krb5_free_data_contents(context, rock->salt);
+        if (entry->length != KRB5_ETYPE_NO_SALT) {
+            *rock->salt = make_data(entry->salt, entry->length);
+            entry->salt = NULL;
+            *rock->default_salt = FALSE;
+        } else {
+            *rock->salt = empty_data();
+            *rock->default_salt = TRUE;
+        }
+        krb5_free_data_contents(context, rock->s2kparams);
+        *rock->s2kparams = entry->s2kparams;
+        entry->s2kparams = empty_data();
+        TRACE_PREAUTH_ETYPE_INFO(context, *rock->etype, rock->salt,
+                                 rock->s2kparams);
+    } else {
+        /* Look for a pw-salt or afs3-salt element. */
+        pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_PW_SALT);
+        if (pa == NULL)
+            pa = krb5int_find_pa_data(context, padata, KRB5_PADATA_AFS3_SALT);
+        if (pa != NULL) {
+            /* Set rock->salt based on the element we found. */
+            krb5_free_data_contents(context, rock->salt);
+            d = padata2data(*pa);
+            ret = krb5int_copy_data_contents(context, &d, rock->salt);
+            if (ret)
+                goto cleanup;
+            if (pa->pa_type == KRB5_PADATA_AFS3_SALT) {
+                /* Work around a (possible) old Heimdal KDC foible. */
+                p = memchr(rock->salt->data, '@', rock->salt->length);
+                if (p != NULL)
+                    rock->salt->length = p - rock->salt->data;
+                /* Tolerate extra null in MIT KDC afs3-salt value. */
+                if (rock->salt->length > 0 &&
+                    rock->salt->data[rock->salt->length - 1] == '\0')
+                    rock->salt->length--;
+                /* Set an s2kparams value to indicate AFS string-to-key. */
+                krb5_free_data_contents(context, rock->s2kparams);
+                ret = alloc_data(rock->s2kparams, 1);
+                if (ret)
+                    goto cleanup;
+                rock->s2kparams->data[0] = '\1';
+            }
+            *rock->default_salt = FALSE;
+            TRACE_PREAUTH_SALT(context, rock->salt, pa->pa_type);
+        }
+    }
 
-    return(0);
+cleanup:
+    krb5_free_etype_info(context, etype_info);
+    return ret;
 }
 
-static
-krb5_error_code pa_fx_cookie(krb5_context context,
-                                krb5_kdc_req *request,
-                                krb5_pa_data *in_padata,
-                                krb5_pa_data **out_padata,
-                                krb5_data *salt,
-                                krb5_data *s2kparams,
-                                krb5_enctype *etype,
-                                krb5_keyblock *as_key,
-                                krb5_prompter_fct prompter,
-                                void *prompter_data,
-                                krb5_gic_get_as_key_fct gak_fct,
-                                void *gak_data)
+static krb5_error_code
+pa_fx_cookie(krb5_context context, krb5_kdc_req *request,
+             krb5_pa_data *in_padata, krb5_pa_data **out_padata,
+             krb5_data *salt, krb5_data *s2kparams, krb5_enctype *etype,
+             krb5_keyblock *as_key, krb5_prompter_fct prompter,
+             void *prompter_data, krb5_gic_get_as_key_fct gak_fct,
+             void *gak_data)
 {
     krb5_pa_data *pa = calloc(1, sizeof(krb5_pa_data));
     krb5_octet *contents;
+
+    TRACE_PREAUTH_COOKIE(context, in_padata->length, in_padata->contents);
     if (pa == NULL)
-       return ENOMEM;
+        return ENOMEM;
     contents = malloc(in_padata->length);
     if (contents == NULL) {
-       free(pa);
-       return ENOMEM;
+        free(pa);
+        return ENOMEM;
     }
     *pa = *in_padata;
     pa->contents = contents;
@@ -675,1079 +681,69 @@ krb5_error_code pa_fx_cookie(krb5_context context,
     return 0;
 }
 
-static
-krb5_error_code pa_enc_timestamp(krb5_context context,
-                                krb5_kdc_req *request,
-                                krb5_pa_data *in_padata,
-                                krb5_pa_data **out_padata,
-                                krb5_data *salt,
-                                krb5_data *s2kparams,
-                                krb5_enctype *etype,
-                                krb5_keyblock *as_key,
-                                krb5_prompter_fct prompter,
-                                void *prompter_data,
-                                krb5_gic_get_as_key_fct gak_fct,
-                                void *gak_data)
-{
-    krb5_error_code ret;
-    krb5_pa_enc_ts pa_enc;
-    krb5_data *tmp;
-    krb5_enc_data enc_data;
-    krb5_pa_data *pa;
-   
-    if (as_key->length == 0) {
-#ifdef DEBUG
-       fprintf (stderr, "%s:%d: salt len=%d", __FILE__, __LINE__,
-                salt->length);
-       if ((int) salt->length > 0)
-           fprintf (stderr, " '%.*s'", salt->length, salt->data);
-       fprintf (stderr, "; *etype=%d request->ktype[0]=%d\n",
-                *etype, request->ktype[0]);
-#endif
-       if ((ret = ((*gak_fct)(context, request->client,
-                             *etype ? *etype : request->ktype[0],
-                             prompter, prompter_data,
-                             salt, s2kparams, as_key, gak_data))))
-           return(ret);
-    }
-
-    /* now get the time of day, and encrypt it accordingly */
-
-    if ((ret = krb5_us_timeofday(context, &pa_enc.patimestamp, &pa_enc.pausec)))
-       return(ret);
-
-    if ((ret = encode_krb5_pa_enc_ts(&pa_enc, &tmp)))
-       return(ret);
-
-#ifdef DEBUG
-    fprintf (stderr, "key type %d bytes %02x %02x ...\n",
-            as_key->enctype,
-            as_key->contents[0], as_key->contents[1]);
-#endif
-    ret = krb5_encrypt_helper(context, as_key,
-                             KRB5_KEYUSAGE_AS_REQ_PA_ENC_TS,
-                             tmp, &enc_data);
-#ifdef DEBUG
-    fprintf (stderr, "enc data { type=%d kvno=%d data=%02x %02x ... }\n",
-            enc_data.enctype, enc_data.kvno,
-            0xff & enc_data.ciphertext.data[0],
-            0xff & enc_data.ciphertext.data[1]);
-#endif
-
-    krb5_free_data(context, tmp);
-
-    if (ret) {
-       free(enc_data.ciphertext.data);
-       return(ret);
-    }
-
-    ret = encode_krb5_enc_data(&enc_data, &tmp);
-
-    free(enc_data.ciphertext.data);
-
-    if (ret)
-       return(ret);
-
-    if ((pa = (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
-       krb5_free_data(context, tmp);
-       return(ENOMEM);
-    }
-
-    pa->magic = KV5M_PA_DATA;
-    pa->pa_type = KRB5_PADATA_ENC_TIMESTAMP;
-    pa->length = tmp->length;
-    pa->contents = (krb5_octet *) tmp->data;
-
-    *out_padata = pa;
-
-    free(tmp);
-
-    return(0);
-}
-
-static 
-char *sam_challenge_banner(krb5_int32 sam_type)
-{
-    char *label;
-
-    switch (sam_type) {
-    case PA_SAM_TYPE_ENIGMA:   /* Enigma Logic */
-       label = "Challenge for Enigma Logic mechanism";
-       break;
-    case PA_SAM_TYPE_DIGI_PATH: /*  Digital Pathways */
-    case PA_SAM_TYPE_DIGI_PATH_HEX: /*  Digital Pathways */
-       label = "Challenge for Digital Pathways mechanism";
-       break;
-    case PA_SAM_TYPE_ACTIVCARD_DEC: /*  Digital Pathways */
-    case PA_SAM_TYPE_ACTIVCARD_HEX: /*  Digital Pathways */
-       label = "Challenge for Activcard mechanism";
-       break;
-    case PA_SAM_TYPE_SKEY_K0:  /*  S/key where  KDC has key 0 */
-       label = "Challenge for Enhanced S/Key mechanism";
-       break;
-    case PA_SAM_TYPE_SKEY:     /*  Traditional S/Key */
-       label = "Challenge for Traditional S/Key mechanism";
-       break;
-    case PA_SAM_TYPE_SECURID:  /*  Security Dynamics */
-       label = "Challenge for Security Dynamics mechanism";
-       break;
-    case PA_SAM_TYPE_SECURID_PREDICT:  /* predictive Security Dynamics */
-       label = "Challenge for Security Dynamics mechanism";
-       break;
-    default:
-       label = "Challenge from authentication server";
-       break;
-    }
-
-    return(label);
-}
-
-/* this macro expands to the int,ptr necessary for "%.*s" in an sprintf */
-
-#define SAMDATA(kdata, str, maxsize) \
-       (int)((kdata.length)? \
-             ((((kdata.length)<=(maxsize))?(kdata.length):strlen(str))): \
-             strlen(str)), \
-       (kdata.length)? \
-       ((((kdata.length)<=(maxsize))?(kdata.data):(str))):(str)
-
-/* XXX Danger! This code is not in sync with the kerberos-password-02
-   draft.  This draft cannot be implemented as written.  This code is
-   compatible with earlier versions of mit krb5 and cygnus kerbnet. */
-
-static
-krb5_error_code pa_sam(krb5_context context,
-                      krb5_kdc_req *request,
-                      krb5_pa_data *in_padata,
-                      krb5_pa_data **out_padata,
-                      krb5_data *salt,
-                      krb5_data *s2kparams,
-                      krb5_enctype *etype,
-                      krb5_keyblock *as_key,
-                      krb5_prompter_fct prompter,
-                      void *prompter_data,
-                      krb5_gic_get_as_key_fct gak_fct,
-                      void *gak_data)
-{
-    krb5_error_code            ret;
-    krb5_data                  tmpsam;
-    char                       name[100], banner[100];
-    char                       prompt[100], response[100];
-    krb5_data                  response_data;
-    krb5_prompt                        kprompt;
-    krb5_prompt_type           prompt_type;
-    krb5_data                  defsalt;
-    krb5_sam_challenge         *sam_challenge = 0;
-    krb5_sam_response          sam_response;
-    /* these two get encrypted and stuffed in to sam_response */
-    krb5_enc_sam_response_enc  enc_sam_response_enc;
-    krb5_data *                        scratch;
-    krb5_pa_data *             pa;
-
-    if (prompter == NULL)
-       return EIO;
-
-    tmpsam.length = in_padata->length;
-    tmpsam.data = (char *) in_padata->contents;
-    if ((ret = decode_krb5_sam_challenge(&tmpsam, &sam_challenge)))
-       return(ret);
-
-    if (sam_challenge->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) {
-       krb5_free_sam_challenge(context, sam_challenge);
-       return(KRB5_SAM_UNSUPPORTED);
-    }
-
-    /* If we need the password from the user (USE_SAD_AS_KEY not set), */
-    /* then get it here.  Exception for "old" KDCs with CryptoCard     */
-    /* support which uses the USE_SAD_AS_KEY flag, but still needs pwd */ 
-
-    if (!(sam_challenge->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) ||
-       (sam_challenge->sam_type == PA_SAM_TYPE_CRYPTOCARD)) {
-
-       /* etype has either been set by caller or by KRB5_PADATA_ETYPE_INFO */
-       /* message from the KDC.  If it is not set, pick an enctype that we */
-       /* think the KDC will have for us.                                  */
-
-       if (etype && *etype == 0)
-          *etype = ENCTYPE_DES_CBC_CRC;
-
-       if ((ret = (gak_fct)(context, request->client, *etype, prompter,
-                       prompter_data, salt, s2kparams, as_key, gak_data)))
-          return(ret);
-    }
-    snprintf(name, sizeof(name), "%.*s",
-            SAMDATA(sam_challenge->sam_type_name, "SAM Authentication",
-                    sizeof(name) - 1));
-
-    snprintf(banner, sizeof(banner), "%.*s",
-            SAMDATA(sam_challenge->sam_challenge_label,
-                    sam_challenge_banner(sam_challenge->sam_type),
-                    sizeof(banner)-1));
-
-    /* sprintf(prompt, "Challenge is [%s], %s: ", challenge, prompt); */
-    snprintf(prompt, sizeof(prompt), "%s%.*s%s%.*s",
-            sam_challenge->sam_challenge.length?"Challenge is [":"",
-            SAMDATA(sam_challenge->sam_challenge, "", 20),
-            sam_challenge->sam_challenge.length?"], ":"",
-            SAMDATA(sam_challenge->sam_response_prompt, "passcode", 55));
-
-    response_data.data = response;
-    response_data.length = sizeof(response);
-
-    kprompt.prompt = prompt;
-    kprompt.hidden = 1;
-    kprompt.reply = &response_data;
-    prompt_type = KRB5_PROMPT_TYPE_PREAUTH;
-
-    /* PROMPTER_INVOCATION */
-    krb5int_set_prompt_types(context, &prompt_type);
-    if ((ret = ((*prompter)(context, prompter_data, name,
-                          banner, 1, &kprompt)))) {
-       krb5_free_sam_challenge(context, sam_challenge);
-       krb5int_set_prompt_types(context, 0);
-       return(ret);
-    }
-    krb5int_set_prompt_types(context, 0);
-
-    enc_sam_response_enc.sam_nonce = sam_challenge->sam_nonce;
-    if (sam_challenge->sam_nonce == 0) {
-       if ((ret = krb5_us_timeofday(context, 
-                               &enc_sam_response_enc.sam_timestamp,
-                               &enc_sam_response_enc.sam_usec))) {
-           krb5_free_sam_challenge(context,sam_challenge);
-           return(ret);
-       }
-
-       sam_response.sam_patimestamp = enc_sam_response_enc.sam_timestamp;
-    }
-
-    /* XXX What if more than one flag is set?  */
-    if (sam_challenge->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) {
-
-       /* Most of this should be taken care of before we get here.  We */
-       /* will need the user's password and as_key to encrypt the SAD  */
-       /* and we want to preserve ordering of user prompts (first      */
-       /* password, then SAM data) so that user's won't be confused.   */
-
-       if (as_key->length) {
-           krb5_free_keyblock_contents(context, as_key);
-           as_key->length = 0;
-       }
-
-       /* generate a salt using the requested principal */
-
-       if ((salt->length == -1 || salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
-           if ((ret = krb5_principal2salt(context, request->client,
-                                         &defsalt))) {
-               krb5_free_sam_challenge(context, sam_challenge);
-               return(ret);
-           }
-
-           salt = &defsalt;
-       } else {
-           defsalt.length = 0;
-       }
-
-       /* generate a key using the supplied password */
-
-       ret = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5,
-                                  (krb5_data *)gak_data, salt, as_key);
-
-       if (defsalt.length)
-           free(defsalt.data);
-
-       if (ret) {
-           krb5_free_sam_challenge(context, sam_challenge);
-           return(ret);
-       }
-
-       /* encrypt the passcode with the key from above */
-
-       enc_sam_response_enc.sam_sad = response_data;
-    } else if (sam_challenge->sam_flags & KRB5_SAM_USE_SAD_AS_KEY) {
-
-       /* process the key as password */
-
-       if (as_key->length) {
-           krb5_free_keyblock_contents(context, as_key);
-           as_key->length = 0;
-       }
-
-#if 0
-       if ((salt->length == SALT_TYPE_AFS_LENGTH) && (salt->data == NULL)) {
-           if (ret = krb5_principal2salt(context, request->client,
-                                         &defsalt)) {
-               krb5_free_sam_challenge(context, sam_challenge);
-               return(ret);
-           }
-
-           salt = &defsalt;
-       } else {
-           defsalt.length = 0;
-       }
-#else
-       defsalt.length = 0;
-       salt = NULL;
-#endif
-           
-       /* XXX As of the passwords-04 draft, no enctype is specified,
-          the server uses ENCTYPE_DES_CBC_MD5. In the future the
-          server should send a PA-SAM-ETYPE-INFO containing the enctype. */
-
-       ret = krb5_c_string_to_key(context, ENCTYPE_DES_CBC_MD5,
-                                  &response_data, salt, as_key);
-
-       if (defsalt.length)
-           free(defsalt.data);
-
-       if (ret) {
-           krb5_free_sam_challenge(context, sam_challenge);
-           return(ret);
-       }
-
-       enc_sam_response_enc.sam_sad.length = 0;
-    } else {
-       /* Eventually, combine SAD with long-term key to get
-          encryption key.  */
-       return KRB5_PREAUTH_BAD_TYPE;
-    }
-
-    /* copy things from the challenge */
-    sam_response.sam_nonce = sam_challenge->sam_nonce;
-    sam_response.sam_flags = sam_challenge->sam_flags;
-    sam_response.sam_track_id = sam_challenge->sam_track_id;
-    sam_response.sam_type = sam_challenge->sam_type;
-    sam_response.magic = KV5M_SAM_RESPONSE;
-
-    free(sam_challenge);
-
-    /* encode the encoded part of the response */
-    if ((ret = encode_krb5_enc_sam_response_enc(&enc_sam_response_enc,
-                                               &scratch)))
-       return(ret);
-
-    ret = krb5_encrypt_data(context, as_key, 0, scratch,
-                           &sam_response.sam_enc_nonce_or_ts);
-
-    krb5_free_data(context, scratch);
-
-    if (ret)
-       return(ret);
-
-    /* sam_enc_key is reserved for future use */
-    sam_response.sam_enc_key.ciphertext.length = 0;
-
-    if ((pa = malloc(sizeof(krb5_pa_data))) == NULL)
-       return(ENOMEM);
-
-    if ((ret = encode_krb5_sam_response(&sam_response, &scratch))) {
-       free(pa);
-       return(ret);
-    }
-
-    pa->magic = KV5M_PA_DATA;
-    pa->pa_type = KRB5_PADATA_SAM_RESPONSE;
-    pa->length = scratch->length;
-    pa->contents = (krb5_octet *) scratch->data;
-
-    *out_padata = pa;
-
-    free(scratch);
-
-    return(0);
-}
-
-#if APPLE_PKINIT
-/* 
- * PKINIT. One function to generate AS-REQ, one to parse AS-REP
- */
-#define  PKINIT_DEBUG    0
-#if     PKINIT_DEBUG
-#define kdcPkinitDebug(args...)       printf(args)
-#else
-#define kdcPkinitDebug(args...)
-#endif
-
-static krb5_error_code pa_pkinit_gen_req(
-    krb5_context context,
-    krb5_kdc_req *request,
-    krb5_pa_data *in_padata,
-    krb5_pa_data **out_padata,
-    krb5_data *salt, 
-    krb5_data *s2kparams,
-    krb5_enctype *etype,
-    krb5_keyblock *as_key,
-    krb5_prompter_fct prompter, 
-    void *prompter_data,
-    krb5_gic_get_as_key_fct gak_fct, 
-    void *gak_data)
+static krb5_error_code
+pa_s4u_x509_user(krb5_context context, krb5_kdc_req *request,
+                 krb5_pa_data *in_padata, krb5_pa_data **out_padata,
+                 krb5_data *salt, krb5_data *s2kparams, krb5_enctype *etype,
+                 krb5_keyblock *as_key, krb5_prompter_fct prompter,
+                 void *prompter_data, krb5_gic_get_as_key_fct gak_fct,
+                 void *gak_data)
 {
-    krb5_error_code            krtn;
-    krb5_data                  out_data = {0, 0, NULL};
-    krb5_timestamp             kctime = 0;
-    krb5_int32                 cusec = 0;
-    krb5_ui_4                  nonce = 0;
-    krb5_checksum              cksum;
-    krb5_pkinit_signing_cert_t client_cert;
-    krb5_data                  *der_req = NULL;
-    char                       *client_principal = NULL;
-    char                       *server_principal = NULL;
-    unsigned char              nonce_bytes[4];
-    krb5_data                  nonce_data = {0, 4, (char *)nonce_bytes};
-    int                                dex;
-
-    /* 
-     * Trusted CA list and specific KC cert optionally obtained via
-     * krb5_pkinit_get_server_certs(). All are DER-encoded certs. 
-     */
-    krb5_data *trusted_CAs = NULL;
-    krb5_ui_4 num_trusted_CAs;
-    krb5_data kdc_cert = {0};
+    krb5_s4u_userid *userid = (krb5_s4u_userid *)gak_data; /* XXX private contract */
+    krb5_pa_data *s4u_padata;
+    krb5_error_code code;
+    krb5_principal client;
 
-    kdcPkinitDebug("pa_pkinit_gen_req\n");
+    *out_padata = NULL;
 
-    /* If we don't have a client cert, we're done */
-    if(request->client == NULL) {
-       kdcPkinitDebug("No request->client; aborting PKINIT\n");
-       return KRB5KDC_ERR_PREAUTH_FAILED;
-    }
-    krtn = krb5_unparse_name(context, request->client, &client_principal);
-    if(krtn) {
-       return krtn;
-    }
-    krtn = krb5_pkinit_get_client_cert(client_principal, &client_cert);
-    free(client_principal);
-    if(krtn) {
-       kdcPkinitDebug("No client cert; aborting PKINIT\n");
-       return krtn;
-    }
-       
-    /* optional platform-dependent CA list and KDC cert */
-    krtn = krb5_unparse_name(context, request->server, &server_principal);
-    if(krtn) {
-       goto cleanup;
-    }
-    krtn = krb5_pkinit_get_server_certs(client_principal, server_principal,
-       &trusted_CAs, &num_trusted_CAs, &kdc_cert);
-    if(krtn) {
-       goto cleanup;
-    }
-    
-    /* checksum of the encoded KDC-REQ-BODY */
-    krtn = encode_krb5_kdc_req_body(request, &der_req);
-    if(krtn) {
-       kdcPkinitDebug("encode_krb5_kdc_req_body returned %d\n", (int)krtn);
-       goto cleanup;
-    }
-    krtn = krb5_c_make_checksum(context, CKSUMTYPE_NIST_SHA, NULL, 0, der_req, &cksum);
-    if(krtn) {
-       goto cleanup;
-    }
+    if (userid == NULL)
+        return EINVAL;
 
-    krtn = krb5_us_timeofday(context, &kctime, &cusec);
-    if(krtn) {
-       goto cleanup;
-    }
-    
-    /* cook up a random 4-byte nonce */
-    krtn = krb5_c_random_make_octets(context, &nonce_data);
-    if(krtn) {
-       goto cleanup;
-    }
-    for(dex=0; dex<4; dex++) {
-       nonce <<= 8;
-       nonce |= nonce_bytes[dex];
-    }
+    code = krb5_copy_principal(context, request->client, &client);
+    if (code != 0)
+        return code;
 
-    krtn = krb5int_pkinit_as_req_create(context, 
-       kctime, cusec, nonce, &cksum,
-       client_cert, 
-       trusted_CAs, num_trusted_CAs, 
-       (kdc_cert.data ? &kdc_cert : NULL),
-       &out_data);
-    if(krtn) {
-       kdcPkinitDebug("error %d on pkinit_as_req_create; aborting PKINIT\n", (int)krtn);
-       goto cleanup;
-    }
-    *out_padata = (krb5_pa_data *)malloc(sizeof(krb5_pa_data));
-    if(*out_padata == NULL) {
-       krtn = ENOMEM;
-       free(out_data.data);
-       goto cleanup;
-    }
-    (*out_padata)->magic = KV5M_PA_DATA;
-    (*out_padata)->pa_type = KRB5_PADATA_PK_AS_REQ;
-    (*out_padata)->length = out_data.length;
-    (*out_padata)->contents = (krb5_octet *)out_data.data;
-    krtn = 0;
-cleanup:
-    if(client_cert) {
-       krb5_pkinit_release_cert(client_cert);
-    }
-    if(cksum.contents) {
-       free(cksum.contents);
-    }
-    if (der_req) {
-       krb5_free_data(context, der_req);
-    }
-    if(server_principal) {
-       free(server_principal);
-    }
-    /* free data mallocd by krb5_pkinit_get_server_certs() */
-    if(trusted_CAs) {
-       unsigned udex;
-       for(udex=0; udex<num_trusted_CAs; udex++) {
-           free(trusted_CAs[udex].data);
-       }
-       free(trusted_CAs);
-    }
-    if(kdc_cert.data) {
-       free(kdc_cert.data);
-    }
-    return krtn;
-
-}
+    if (userid->user != NULL)
+        krb5_free_principal(context, userid->user);
+    userid->user = client;
 
-/* If and only if the realm is that of a Local KDC, accept
- * the KDC certificate as valid if its hash matches the
- * realm.
- */
-static krb5_boolean local_kdc_cert_match(
-    krb5_context context,
-    krb5_data *signer_cert,
-    krb5_principal client)
-{
-    static const char lkdcprefix[] = "LKDC:SHA1.";
-    krb5_boolean match = FALSE;
-    size_t cert_hash_len;
-    char *cert_hash;
-    const char *realm_hash;
-    size_t realm_hash_len;
-
-    if (client->realm.length <= sizeof(lkdcprefix) ||
-        0 != memcmp(lkdcprefix, client->realm.data, sizeof(lkdcprefix)-1))
-       return match;
-    realm_hash = &client->realm.data[sizeof(lkdcprefix)-1];
-    realm_hash_len = client->realm.length - sizeof(lkdcprefix) + 1;
-    kdcPkinitDebug("checking realm versus certificate hash\n");
-    if (NULL != (cert_hash = krb5_pkinit_cert_hash_str(signer_cert))) {
-       kdcPkinitDebug("hash = %s\n", cert_hash);
-       cert_hash_len = strlen(cert_hash);
-       if (cert_hash_len == realm_hash_len &&
-           0 == memcmp(cert_hash, realm_hash, cert_hash_len))
-           match = TRUE;
-       free(cert_hash);
-    }
-    kdcPkinitDebug("result: %s\n", match ? "matches" : "does not match");
-    return match;
-}
+    if (userid->subject_cert.length != 0) {
+        s4u_padata = malloc(sizeof(*s4u_padata));
+        if (s4u_padata == NULL)
+            return ENOMEM;
 
-static krb5_error_code pa_pkinit_parse_rep(
-    krb5_context context,
-    krb5_kdc_req *request,
-    krb5_pa_data *in_padata,
-    krb5_pa_data **out_padata,
-    krb5_data *salt, 
-    krb5_data *s2kparams,
-    krb5_enctype *etype,
-    krb5_keyblock *as_key,
-    krb5_prompter_fct prompter, 
-    void *prompter_data,
-    krb5_gic_get_as_key_fct gak_fct, 
-    void *gak_data)
-{
-    krb5int_cert_sig_status    sig_status = (krb5int_cert_sig_status)-999;
-    krb5_error_code            krtn;
-    krb5_data                  asRep;
-    krb5_keyblock              local_key = {0};
-    krb5_pkinit_signing_cert_t client_cert;
-    char                       *princ_name = NULL;
-    krb5_checksum              as_req_checksum_rcd = {0};  /* received checksum */
-    krb5_checksum              as_req_checksum_gen = {0};  /* calculated checksum */
-    krb5_data                  *encoded_as_req = NULL;
-    krb5_data                  signer_cert = {0};
+        s4u_padata->magic = KV5M_PA_DATA;
+        s4u_padata->pa_type = KRB5_PADATA_S4U_X509_USER;
+        s4u_padata->contents = malloc(userid->subject_cert.length);
+        if (s4u_padata->contents == NULL) {
+            free(s4u_padata);
+            return ENOMEM;
+        }
+        memcpy(s4u_padata->contents, userid->subject_cert.data, userid->subject_cert.length);
+        s4u_padata->length = userid->subject_cert.length;
 
-    *out_padata = NULL;
-    kdcPkinitDebug("pa_pkinit_parse_rep\n");
-    if((in_padata == NULL) || (in_padata->length== 0)) {
-       kdcPkinitDebug("pa_pkinit_parse_rep: no in_padata\n");
-       return KRB5KDC_ERR_PREAUTH_FAILED;
+        *out_padata = s4u_padata;
     }
 
-    /* If we don't have a client cert, we're done */
-    if(request->client == NULL) {
-       kdcPkinitDebug("No request->client; aborting PKINIT\n");
-       return KRB5KDC_ERR_PREAUTH_FAILED;
-    }
-    krtn = krb5_unparse_name(context, request->client, &princ_name);
-    if(krtn) {
-       return krtn;
-    }
-    krtn = krb5_pkinit_get_client_cert(princ_name, &client_cert);
-    free(princ_name);
-    if(krtn) {
-       kdcPkinitDebug("No client cert; aborting PKINIT\n");
-       return krtn;
-    }
-    
-    memset(&local_key, 0, sizeof(local_key));
-    asRep.data = (char *)in_padata->contents;
-    asRep.length = in_padata->length;
-    krtn = krb5int_pkinit_as_rep_parse(context, &asRep, client_cert, 
-       &local_key, &as_req_checksum_rcd, &sig_status,
-       &signer_cert, NULL, NULL);
-    if(krtn) {
-       kdcPkinitDebug("pkinit_as_rep_parse returned %d\n", (int)krtn);
-       return krtn;
-    }
-    switch(sig_status) {
-       case pki_cs_good:
-           break;
-       case pki_cs_unknown_root:
-           if (local_kdc_cert_match(context, &signer_cert, request->client))
-               break;
-           /* FALLTHROUGH */
-       default:
-           kdcPkinitDebug("pa_pkinit_parse_rep: bad cert/sig status %d\n", 
-               (int)sig_status);
-           krtn = KRB5KDC_ERR_PREAUTH_FAILED;
-           goto error_out;
-    }
-    
-    /* calculate checksum of incoming AS-REQ using the decryption key 
-     * we just got from the ReplyKeyPack */
-    krtn = encode_krb5_as_req(request, &encoded_as_req);
-    if(krtn) {
-       goto error_out;
-    }
-    krtn = krb5_c_make_checksum(context, context->kdc_req_sumtype, 
-       &local_key, KRB5_KEYUSAGE_TGS_REQ_AUTH_CKSUM, 
-       encoded_as_req, &as_req_checksum_gen);
-    if(krtn) {
-       goto error_out;
-    }
-    if((as_req_checksum_gen.length != as_req_checksum_rcd.length) ||
-       memcmp(as_req_checksum_gen.contents,
-             as_req_checksum_rcd.contents,
-             as_req_checksum_gen.length)) {
-       kdcPkinitDebug("pa_pkinit_parse_rep: checksum miscompare\n");
-       krtn = KRB5KDC_ERR_PREAUTH_FAILED;
-       goto error_out;
-    }
-    
-    /* We have the key; transfer to caller */
-    if (as_key->length) {
-       krb5_free_keyblock_contents(context, as_key);
-    }
-    *as_key = local_key;
-    
-    #if PKINIT_DEBUG
-    fprintf(stderr, "pa_pkinit_parse_rep: SUCCESS\n");
-    fprintf(stderr, "enctype %d keylen %d keydata %02x %02x %02x %02x...\n",
-       (int)as_key->enctype, (int)as_key->length,
-       as_key->contents[0], as_key->contents[1], 
-       as_key->contents[2], as_key->contents[3]);
-    #endif
-    
-    krtn = 0;
-    
-error_out:
-    if (signer_cert.data) {
-           free(signer_cert.data);
-    }
-    if(as_req_checksum_rcd.contents) {
-       free(as_req_checksum_rcd.contents);
-    }
-    if(as_req_checksum_gen.contents) {
-       free(as_req_checksum_gen.contents);
-    }
-    if(encoded_as_req) {
-       krb5_free_data(context, encoded_as_req);
-    }
-    if(krtn && (local_key.contents != NULL)) {
-       krb5_free_keyblock_contents(context, &local_key);
-    }
-    return krtn;
-}
-#endif /* APPLE_PKINIT */
-
-static
-krb5_error_code pa_sam_2(krb5_context context,
-                               krb5_kdc_req *request,
-                               krb5_pa_data *in_padata,
-                               krb5_pa_data **out_padata,
-                               krb5_data *salt,
-                        krb5_data *s2kparams,
-                               krb5_enctype *etype,
-                               krb5_keyblock *as_key,
-                               krb5_prompter_fct prompter,
-                               void *prompter_data,
-                               krb5_gic_get_as_key_fct gak_fct,
-                               void *gak_data) {
-
-   krb5_error_code retval;
-   krb5_sam_challenge_2 *sc2 = NULL;
-   krb5_sam_challenge_2_body *sc2b = NULL;
-   krb5_data tmp_data;
-   krb5_data response_data;
-   char name[100], banner[100], prompt[100], response[100];
-   krb5_prompt kprompt;
-   krb5_prompt_type prompt_type;
-   krb5_data defsalt;
-   krb5_checksum **cksum;
-   krb5_data *scratch = NULL;
-   krb5_boolean valid_cksum = 0;
-   krb5_enc_sam_response_enc_2 enc_sam_response_enc_2;
-   krb5_sam_response_2 sr2;
-   size_t ciph_len;
-   krb5_pa_data *sam_padata;
-
-   if (prompter == NULL)
-       return KRB5_LIBOS_CANTREADPWD;
-
-   tmp_data.length = in_padata->length;
-   tmp_data.data = (char *)in_padata->contents;
-
-   if ((retval = decode_krb5_sam_challenge_2(&tmp_data, &sc2)))
-       return(retval);
-
-   retval = decode_krb5_sam_challenge_2_body(&sc2->sam_challenge_2_body, &sc2b);
-
-   if (retval)
-       return(retval);
-
-   if (!sc2->sam_cksum || ! *sc2->sam_cksum) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(KRB5_SAM_NO_CHECKSUM);
-   }
-
-   if (sc2b->sam_flags & KRB5_SAM_MUST_PK_ENCRYPT_SAD) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(KRB5_SAM_UNSUPPORTED);
-   }
-
-   if (!krb5_c_valid_enctype(sc2b->sam_etype)) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(KRB5_SAM_INVALID_ETYPE);
-   }
-
-   /* All of the above error checks are KDC-specific, that is, they    */
-   /* assume a failure in the KDC reply.  By returning anything other  */
-   /* than KRB5_KDC_UNREACH, KRB5_PREAUTH_FAILED,              */
-   /* KRB5_LIBOS_PWDINTR, or KRB5_REALM_CANT_RESOLVE, the client will  */
-   /* most likely go on to try the AS_REQ against master KDC           */
-
-   if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
-       /* We will need the password to obtain the key used for */
-       /* the checksum, and encryption of the sam_response.    */
-       /* Go ahead and get it now, preserving the ordering of  */
-       /* prompts for the user.                                */
-
-       retval = (gak_fct)(context, request->client,
-                       sc2b->sam_etype, prompter,
-                       prompter_data, salt, s2kparams, as_key, gak_data);
-       if (retval) {
-          krb5_free_sam_challenge_2(context, sc2);
-          krb5_free_sam_challenge_2_body(context, sc2b);
-          return(retval);
-       }
-   }
-
-   snprintf(name, sizeof(name), "%.*s",
-       SAMDATA(sc2b->sam_type_name, "SAM Authentication",
-       sizeof(name) - 1));
-
-   snprintf(banner, sizeof(banner), "%.*s",
-           SAMDATA(sc2b->sam_challenge_label,
-                   sam_challenge_banner(sc2b->sam_type),
-                   sizeof(banner)-1));
-
-   snprintf(prompt, sizeof(prompt), "%s%.*s%s%.*s",
-           sc2b->sam_challenge.length?"Challenge is [":"",
-           SAMDATA(sc2b->sam_challenge, "", 20),
-           sc2b->sam_challenge.length?"], ":"",
-           SAMDATA(sc2b->sam_response_prompt, "passcode", 55));
-
-   response_data.data = response;
-   response_data.length = sizeof(response);
-   kprompt.prompt = prompt;
-   kprompt.hidden = 1;
-   kprompt.reply = &response_data;
-
-   prompt_type = KRB5_PROMPT_TYPE_PREAUTH;
-   krb5int_set_prompt_types(context, &prompt_type);
-
-   if ((retval = ((*prompter)(context, prompter_data, name,
-                               banner, 1, &kprompt)))) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       krb5int_set_prompt_types(context, 0);
-       return(retval);
-   }
-
-   krb5int_set_prompt_types(context, (krb5_prompt_type *)NULL);
-
-   /* Generate salt used by string_to_key() */
-   if ((salt->length == -1) && (salt->data == NULL)) {
-       if ((retval = 
-            krb5_principal2salt(context, request->client, &defsalt))) {
-          krb5_free_sam_challenge_2(context, sc2);
-          krb5_free_sam_challenge_2_body(context, sc2b);
-          return(retval);
-       }
-       salt = &defsalt;
-   } else {
-       defsalt.length = 0;
-   }
-
-   /* Get encryption key to be used for checksum and sam_response */
-   if (!(sc2b->sam_flags & KRB5_SAM_USE_SAD_AS_KEY)) {
-       /* as_key = string_to_key(password) */
-
-       if (as_key->length) {
-          krb5_free_keyblock_contents(context, as_key);
-          as_key->length = 0;
-       }
-
-       /* generate a key using the supplied password */
-       retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                                   (krb5_data *)gak_data, salt, as_key);
-
-       if (retval) {
-          krb5_free_sam_challenge_2(context, sc2);
-          krb5_free_sam_challenge_2_body(context, sc2b);
-          if (defsalt.length) free(defsalt.data);
-          return(retval);
-       }
-
-       if (!(sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD)) {
-          /* as_key = combine_key (as_key, string_to_key(SAD)) */
-          krb5_keyblock tmp_kb;
-
-          retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                               &response_data, salt, &tmp_kb);
-
-          if (retval) {
-               krb5_free_sam_challenge_2(context, sc2);
-               krb5_free_sam_challenge_2_body(context, sc2b);
-               if (defsalt.length) free(defsalt.data);
-               return(retval);
-          }
-
-          /* This should be a call to the crypto library some day */
-          /* key types should already match the sam_etype */
-          retval = krb5int_c_combine_keys(context, as_key, &tmp_kb, as_key);
-
-          if (retval) {
-               krb5_free_sam_challenge_2(context, sc2);
-               krb5_free_sam_challenge_2_body(context, sc2b);
-               if (defsalt.length) free(defsalt.data);
-               return(retval);
-          }
-          krb5_free_keyblock_contents(context, &tmp_kb);
-       }
-
-       if (defsalt.length)
-          free(defsalt.data);
-
-   } else {
-       /* as_key = string_to_key(SAD) */
-
-       if (as_key->length) {
-          krb5_free_keyblock_contents(context, as_key);
-          as_key->length = 0;
-       }
-
-       /* generate a key using the supplied password */
-       retval = krb5_c_string_to_key(context, sc2b->sam_etype,
-                               &response_data, salt, as_key);
-
-       if (defsalt.length)
-          free(defsalt.data);
-
-       if (retval) {
-          krb5_free_sam_challenge_2(context, sc2);
-          krb5_free_sam_challenge_2_body(context, sc2b);
-          return(retval);
-       }
-   }
-
-   /* Now we have a key, verify the checksum on the sam_challenge */
-
-   cksum = sc2->sam_cksum;
-   
-   while (*cksum) {
-       /* Check this cksum */
-       retval = krb5_c_verify_checksum(context, as_key,
-                       KRB5_KEYUSAGE_PA_SAM_CHALLENGE_CKSUM,
-                       &sc2->sam_challenge_2_body,
-                       *cksum, &valid_cksum);
-       if (retval) {
-          krb5_free_data(context, scratch);
-          krb5_free_sam_challenge_2(context, sc2);
-          krb5_free_sam_challenge_2_body(context, sc2b);
-          return(retval);
-       }
-       if (valid_cksum)
-          break;
-       cksum++;
-   }
-
-   if (!valid_cksum) {
-
-       /* If KRB5_SAM_SEND_ENCRYPTED_SAD is set, then password is only */
-       /* source for checksum key.  Therefore, a bad checksum means a  */
-       /* bad password.  Don't give that direct feedback to someone    */
-       /* trying to brute-force passwords.                             */
-
-       if (!(sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD))
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       /*
-        * Note: We return AP_ERR_BAD_INTEGRITY so upper-level applications
-        * can interpret that as "password incorrect", which is probably
-        * the best error we can return in this situation.
-        */
-       return(KRB5KRB_AP_ERR_BAD_INTEGRITY);
-   }
-   /* fill in enc_sam_response_enc_2 */
-   enc_sam_response_enc_2.magic = KV5M_ENC_SAM_RESPONSE_ENC_2;
-   enc_sam_response_enc_2.sam_nonce = sc2b->sam_nonce;
-   if (sc2b->sam_flags & KRB5_SAM_SEND_ENCRYPTED_SAD) {
-       enc_sam_response_enc_2.sam_sad = response_data;
-   } else {
-       enc_sam_response_enc_2.sam_sad.data = NULL;
-       enc_sam_response_enc_2.sam_sad.length = 0;
-   }
-
-   /* encode and encrypt enc_sam_response_enc_2 with as_key */
-   retval = encode_krb5_enc_sam_response_enc_2(&enc_sam_response_enc_2,
-               &scratch);
-   if (retval) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(retval);
-   }
-
-   /* Fill in sam_response_2 */
-   memset(&sr2, 0, sizeof(sr2));
-   sr2.sam_type = sc2b->sam_type;
-   sr2.sam_flags = sc2b->sam_flags;
-   sr2.sam_track_id = sc2b->sam_track_id;
-   sr2.sam_nonce = sc2b->sam_nonce;
-
-   /* Now take care of sr2.sam_enc_nonce_or_sad by encrypting encoded  */
-   /* enc_sam_response_enc_2 from above */
-
-   retval = krb5_c_encrypt_length(context, as_key->enctype, scratch->length,
-                                 &ciph_len);
-   if (retval) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(retval);
-   }
-   sr2.sam_enc_nonce_or_sad.ciphertext.length = ciph_len;
-
-   sr2.sam_enc_nonce_or_sad.ciphertext.data =
-       (char *)malloc(sr2.sam_enc_nonce_or_sad.ciphertext.length);
-
-   if (!sr2.sam_enc_nonce_or_sad.ciphertext.data) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       return(ENOMEM);
-   }
-
-   retval = krb5_c_encrypt(context, as_key, KRB5_KEYUSAGE_PA_SAM_RESPONSE,
-               NULL, scratch, &sr2.sam_enc_nonce_or_sad);
-   if (retval) {
-       krb5_free_sam_challenge_2(context, sc2);
-       krb5_free_sam_challenge_2_body(context, sc2b);
-       krb5_free_data(context, scratch);
-       krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
-       return(retval);
-   }
-   krb5_free_data(context, scratch);
-   scratch = NULL;
-
-   /* Encode the sam_response_2 */
-   retval = encode_krb5_sam_response_2(&sr2, &scratch);
-   krb5_free_sam_challenge_2(context, sc2);
-   krb5_free_sam_challenge_2_body(context, sc2b);
-   krb5_free_data_contents(context, &sr2.sam_enc_nonce_or_sad.ciphertext);
-
-   if (retval) {
-       return (retval);
-   }
-
-   /* Almost there, just need to make padata !  */
-   sam_padata = malloc(sizeof(krb5_pa_data));
-   if (sam_padata == NULL) {
-       krb5_free_data(context, scratch);
-       return(ENOMEM);
-   }
-
-   sam_padata->magic = KV5M_PA_DATA;
-   sam_padata->pa_type = KRB5_PADATA_SAM_RESPONSE_2;
-   sam_padata->length = scratch->length;
-   sam_padata->contents = (krb5_octet *) scratch->data;
-
-   *out_padata = sam_padata;
-
-   return(0);
+    return 0;
 }
 
 /* FIXME - order significant? */
 static const pa_types_t pa_types[] = {
     {
-       KRB5_PADATA_PW_SALT,
-       pa_salt,
-       PA_INFO,
-    },
-    {
-       KRB5_PADATA_AFS3_SALT,
-       pa_salt,
-       PA_INFO,
+        KRB5_PADATA_FX_COOKIE,
+        pa_fx_cookie,
+        PA_INFO,
     },
-#if APPLE_PKINIT
     {
-       KRB5_PADATA_PK_AS_REQ,
-       pa_pkinit_gen_req,
-       PA_INFO,
+        KRB5_PADATA_S4U_X509_USER,
+        pa_s4u_x509_user,
+        PA_INFO,
     },
     {
-       KRB5_PADATA_PK_AS_REP,
-       pa_pkinit_parse_rep,
-       PA_REAL,
-    },
-#endif /* APPLE_PKINIT */
-    {
-       KRB5_PADATA_ENC_TIMESTAMP,
-       pa_enc_timestamp,
-       PA_REAL,
-    },
-    {
-       KRB5_PADATA_SAM_CHALLENGE_2,
-       pa_sam_2,
-       PA_REAL,
-    },
-    {
-       KRB5_PADATA_SAM_CHALLENGE,
-       pa_sam,
-       PA_REAL,
-    },
-    {
-       KRB5_PADATA_FX_COOKIE,
-       pa_fx_cookie,
-       PA_INFO,
-    },
-    {
-       -1,
-       NULL,
-       0,
+        -1,
+        NULL,
+        0,
     },
 };
 
@@ -1758,114 +754,100 @@ static const pa_types_t pa_types[] = {
  */
 krb5_error_code KRB5_CALLCONV
 krb5_do_preauth_tryagain(krb5_context kcontext,
-                        krb5_kdc_req *request,
-                        krb5_data *encoded_request_body,
-                        krb5_data *encoded_previous_request,
-                        krb5_pa_data **padata,
-                        krb5_pa_data ***return_padata,
-                        krb5_error *err_reply,
-                        krb5_data *salt, krb5_data *s2kparams,
-                        krb5_enctype *etype,
-                        krb5_keyblock *as_key,
-                        krb5_prompter_fct prompter, void *prompter_data,
-                        krb5_gic_get_as_key_fct gak_fct, void *gak_data,
-                        krb5_preauth_client_rock *get_data_rock,
-                        krb5_gic_opt_ext *opte)
+                         krb5_kdc_req *request,
+                         krb5_data *encoded_request_body,
+                         krb5_data *encoded_previous_request,
+                         krb5_pa_data **padata,
+                         krb5_pa_data ***return_padata,
+                         krb5_error *err_reply,
+                         krb5_pa_data **err_padata,
+                         krb5_prompter_fct prompter, void *prompter_data,
+                         krb5_clpreauth_rock preauth_rock,
+                         krb5_gic_opt_ext *opte)
 {
     krb5_error_code ret;
     krb5_pa_data **out_padata;
     krb5_preauth_context *context;
-    struct _krb5_preauth_context_module *module;
+    struct krb5_preauth_context_module_st *module;
     int i, j;
     int out_pa_list_size = 0;
 
     ret = KRB5KRB_ERR_GENERIC;
     if (kcontext->preauth_context == NULL) {
-       return KRB5KRB_ERR_GENERIC;
+        return KRB5KRB_ERR_GENERIC;
     }
     context = kcontext->preauth_context;
     if (context == NULL) {
-       return KRB5KRB_ERR_GENERIC;
+        return KRB5KRB_ERR_GENERIC;
     }
 
+    TRACE_PREAUTH_TRYAGAIN_INPUT(kcontext, padata);
+
     for (i = 0; padata[i] != NULL && padata[i]->pa_type != 0; i++) {
-       out_padata = NULL;
-       for (j = 0; j < context->n_modules; j++) {
-           module = &context->modules[j];
-           if (module->pa_type != padata[i]->pa_type) {
-               continue;
-           }
-           if (module->client_tryagain == NULL) {
-               continue;
-           }
-           if ((*module->client_tryagain)(kcontext,
-                                          module->plugin_context,
-                                          *module->request_context_pp,
-                                          (krb5_get_init_creds_opt *)opte,
-                                          client_data_proc,
-                                          get_data_rock,
-                                          request,
-                                          encoded_request_body,
-                                          encoded_previous_request,
-                                          padata[i],
-                                          err_reply,
-                                          prompter, prompter_data,
-                                          gak_fct, gak_data, salt, s2kparams,
-                                          as_key,
-                                          &out_padata) == 0) {
-               if (out_padata != NULL) {
-                   int k;
-                   for (k = 0; out_padata[k] != NULL; k++);
-                   grow_pa_list(return_padata, &out_pa_list_size,
-                                out_padata, k);
-                   free(out_padata);
-                   return 0;
-               }
-           }
-       }
+        out_padata = NULL;
+        for (j = 0; j < context->n_modules; j++) {
+            module = &context->modules[j];
+            if (module->pa_type != padata[i]->pa_type) {
+                continue;
+            }
+            if (module->client_tryagain == NULL) {
+                continue;
+            }
+            if ((*module->client_tryagain)(kcontext, module->moddata,
+                                           *module->modreq_p,
+                                           (krb5_get_init_creds_opt *)opte,
+                                           &callbacks, preauth_rock,
+                                           request,
+                                           encoded_request_body,
+                                           encoded_previous_request,
+                                           padata[i]->pa_type,
+                                           err_reply, err_padata,
+                                           prompter, prompter_data,
+                                           &out_padata) == 0) {
+                if (out_padata != NULL) {
+                    int k;
+                    for (k = 0; out_padata[k] != NULL; k++);
+                    grow_pa_list(return_padata, &out_pa_list_size,
+                                 out_padata, k);
+                    free(out_padata);
+                    TRACE_PREAUTH_TRYAGAIN_OUTPUT(kcontext, *return_padata);
+                    return 0;
+                }
+            }
+        }
     }
     return ret;
 }
 
 krb5_error_code KRB5_CALLCONV
-krb5_do_preauth(krb5_context context,
-               krb5_kdc_req *request,
-               krb5_data *encoded_request_body,
-               krb5_data *encoded_previous_request,
-               krb5_pa_data **in_padata, krb5_pa_data ***out_padata,
-               krb5_data *salt, krb5_data *s2kparams,
-               krb5_enctype *etype,
-               krb5_keyblock *as_key,
-               krb5_prompter_fct prompter, void *prompter_data,
-               krb5_gic_get_as_key_fct gak_fct, void *gak_data,
-               krb5_preauth_client_rock *get_data_rock,
-               krb5_gic_opt_ext *opte)
+krb5_do_preauth(krb5_context context, krb5_kdc_req *request,
+                krb5_data *encoded_request_body,
+                krb5_data *encoded_previous_request,
+                krb5_pa_data **in_padata, krb5_pa_data ***out_padata,
+                krb5_prompter_fct prompter, void *prompter_data,
+                krb5_clpreauth_rock rock, krb5_gic_opt_ext *opte,
+                krb5_boolean *got_real_out)
 {
     unsigned int h;
     int i, j, out_pa_list_size;
-    int seen_etype_info2 = 0;
     krb5_pa_data *out_pa = NULL, **out_pa_list = NULL;
-    krb5_data scratch;
-    krb5_etype_info etype_info = NULL;
     krb5_error_code ret;
     static const int paorder[] = { PA_INFO, PA_REAL };
     int realdone;
 
+    *got_real_out = FALSE;
+
     if (in_padata == NULL) {
-       *out_padata = NULL;
-       return(0);
+        *out_padata = NULL;
+        return(0);
     }
 
-#ifdef DEBUG
-    fprintf (stderr, "salt len=%d", (int) salt->length);
-    if ((int) salt->length > 0)
-       fprintf (stderr, " '%.*s'", salt->length, salt->data);
-    fprintf (stderr, "; preauth data types:");
-    for (i = 0; in_padata[i]; i++) {
-       fprintf (stderr, " %d", in_padata[i]->pa_type);
-    }
-    fprintf (stderr, "\n");
-#endif
+    TRACE_PREAUTH_INPUT(context, in_padata);
+
+    /* Scan the padata list and process etype-info or salt elements. */
+    ret = get_etype_info(context, in_padata, request, rock);
+    if (ret)
+        return ret;
 
     out_pa_list = NULL;
     out_pa_list_size = 0;
@@ -1873,202 +855,134 @@ krb5_do_preauth(krb5_context context,
     /* first do all the informational preauths, then the first real one */
 
     for (h=0; h<(sizeof(paorder)/sizeof(paorder[0])); h++) {
-       realdone = 0;
-       for (i=0; in_padata[i] && !realdone; i++) {
-           int k, l, etype_found, valid_etype_found;
-           /*
-            * This is really gross, but is necessary to prevent
-            * lossage when talking to a 1.0.x KDC, which returns an
-            * erroneous PA-PW-SALT when it returns a KRB-ERROR
-            * requiring additional preauth.
-            */
-           switch (in_padata[i]->pa_type) {
-           case KRB5_PADATA_ETYPE_INFO:
-           case KRB5_PADATA_ETYPE_INFO2:
-           {
-               krb5_preauthtype pa_type = in_padata[i]->pa_type;
-               if (etype_info) {
-                   if (seen_etype_info2 || pa_type != KRB5_PADATA_ETYPE_INFO2)
-                       continue;
-                   if (pa_type == KRB5_PADATA_ETYPE_INFO2) {
-                       krb5_free_etype_info( context, etype_info);
-                       etype_info = NULL;
-                   }
-               }
-
-               scratch.length = in_padata[i]->length;
-               scratch.data = (char *) in_padata[i]->contents;
-               if (pa_type == KRB5_PADATA_ETYPE_INFO2) {
-                   seen_etype_info2++;
-                   ret = decode_krb5_etype_info2(&scratch, &etype_info);
-               }
-               else ret = decode_krb5_etype_info(&scratch, &etype_info);
-               if (ret) {
-                   ret = 0; /*Ignore error and etype_info element*/
-                   if (etype_info) 
-                     krb5_free_etype_info( context, etype_info);
-                   etype_info = NULL;
-                   continue;
-               }
-               if (etype_info[0] == NULL) {
-                   krb5_free_etype_info(context, etype_info);
-                   etype_info = NULL;
-                   break;
-               }
-               /*
-                * Select first etype in our request which is also in
-                * etype-info (preferring client request ktype order).
-                */
-               for (etype_found = 0, valid_etype_found = 0, k = 0;
-                    !etype_found && k < request->nktypes; k++) {
-                   for (l = 0; etype_info[l]; l++) {
-                       if (etype_info[l]->etype == request->ktype[k]) {
-                           etype_found++;
-                           break;
-                       }
-                       /* check if program has support for this etype for more
-                        * precise error reporting.
-                        */
-                       if (krb5_c_valid_enctype(etype_info[l]->etype))
-                           valid_etype_found++;
-                   }
-               }
-               if (!etype_found) {
-                 if (valid_etype_found) {
-                       /* supported enctype but not requested */
-                   ret =  KRB5_CONFIG_ETYPE_NOSUPP;
-                   goto cleanup;
-                 }
-                 else {
-                   /* unsupported enctype */
-                   ret =  KRB5_PROG_ETYPE_NOSUPP;
-                   goto cleanup;
-                 }
-
-               }
-               scratch.data = (char *) etype_info[l]->salt;
-               scratch.length = etype_info[l]->length;
-               krb5_free_data_contents(context, salt);
-               if (scratch.length == KRB5_ETYPE_NO_SALT) 
-                 salt->data = NULL;
-               else
-                   if ((ret = krb5int_copy_data_contents( context, &scratch, salt)) != 0)
-                 goto cleanup;
-               *etype = etype_info[l]->etype;
-               krb5_free_data_contents(context, s2kparams);
-               if ((ret = krb5int_copy_data_contents(context,
-                                                     &etype_info[l]->s2kparams,
-                                                     s2kparams)) != 0)
-                 goto cleanup;
-#ifdef DEBUG
-               for (j = 0; etype_info[j]; j++) {
-                   krb5_etype_info_entry *e = etype_info[j];
-                   fprintf (stderr, "etype info %d: etype %d salt len=%d",
-                            j, e->etype, e->length);
-                   if (e->length > 0 && e->length != KRB5_ETYPE_NO_SALT)
-                       fprintf (stderr, " '%.*s'", e->length, e->salt);
-                   fprintf (stderr, "\n");
-               }
-#endif
-               break;
-           }
-           case KRB5_PADATA_PW_SALT:
-           case KRB5_PADATA_AFS3_SALT:
-               if (etype_info)
-                   continue;
-               break;
-           default:
-               ;
-           }
-           /* Try the internally-provided preauth type list. */
-           if (!realdone) for (j=0; pa_types[j].type >= 0; j++) {
-               if ((in_padata[i]->pa_type == pa_types[j].type) &&
-                   (pa_types[j].flags & paorder[h])) {
-#ifdef DEBUG
-                   fprintf (stderr, "calling internal function for pa_type "
-                            "%d, flag %d\n", pa_types[j].type, paorder[h]);
-#endif
-                   out_pa = NULL;
-
-                   if ((ret = ((*pa_types[j].fct)(context, request,
-                                                  in_padata[i], &out_pa,
-                                                  salt, s2kparams, etype, as_key,
-                                                  prompter, prompter_data,
-                                                  gak_fct, gak_data)))) {
-                        if (paorder[h] == PA_INFO) {
+        realdone = 0;
+        for (i=0; in_padata[i] && !realdone; i++) {
+            /* Try the internally-provided preauth type list. */
+            if (!realdone) for (j=0; pa_types[j].type >= 0; j++) {
+                    if ((in_padata[i]->pa_type == pa_types[j].type) &&
+                        (pa_types[j].flags & paorder[h])) {
 #ifdef DEBUG
-                            fprintf (stderr, 
-                                     "internal function for type %d, flag %d "
-                                     "failed with err %d\n",
-                                     in_padata[i]->pa_type, paorder[h], ret);
+                        fprintf (stderr, "calling internal function for pa_type "
+                                 "%d, flag %d\n", pa_types[j].type, paorder[h]);
 #endif
-                            ret = 0;
-                            continue; /* PA_INFO type failed, ignore */
+                        out_pa = NULL;
+
+                        ret = pa_types[j].fct(context, request, in_padata[i],
+                                              &out_pa, rock->salt,
+                                              rock->s2kparams, rock->etype,
+                                              rock->as_key, prompter,
+                                              prompter_data, *rock->gak_fct,
+                                              *rock->gak_data);
+                        if (ret) {
+                            if (paorder[h] == PA_INFO) {
+                                TRACE_PREAUTH_INFO_FAIL(context,
+                                                        in_padata[i]->pa_type,
+                                                        ret);
+                                ret = 0;
+                                continue; /* PA_INFO type failed, ignore */
+                            }
+
+                            goto cleanup;
                         }
-                        
-                     goto cleanup;
-                   }
-
-                   ret = grow_pa_list(&out_pa_list, &out_pa_list_size,
-                                      &out_pa, 1);
-                   if (ret != 0) {
-                           goto cleanup;
-                   }
-                   if (paorder[h] == PA_REAL)
-                       realdone = 1;
-               }
-           }
-
-           /* Try to use plugins now. */
-           if (!realdone) {
-               krb5_init_preauth_context(context);
-               if (context->preauth_context != NULL) {
-                   int module_ret = 0, module_flags;
+
+                        ret = grow_pa_list(&out_pa_list, &out_pa_list_size,
+                                           &out_pa, 1);
+                        if (ret != 0) {
+                            goto cleanup;
+                        }
+                        if (paorder[h] == PA_REAL)
+                            realdone = 1;
+                    }
+                }
+
+            /* Try to use plugins now. */
+            if (!realdone) {
+                krb5_init_preauth_context(context);
+                if (context->preauth_context != NULL) {
+                    int module_ret = 0, module_flags;
 #ifdef DEBUG
-                   fprintf (stderr, "trying modules for pa_type %d, flag %d\n",
-                            in_padata[i]->pa_type, paorder[h]);
+                    fprintf (stderr, "trying modules for pa_type %d, flag %d\n",
+                             in_padata[i]->pa_type, paorder[h]);
 #endif
-                   ret = krb5_run_preauth_plugins(context,
-                                                  paorder[h],
-                                                  request,
-                                                  encoded_request_body,
-                                                  encoded_previous_request,
-                                                  in_padata[i],
-                                                  prompter,
-                                                  prompter_data,
-                                                  gak_fct,
-                                                  salt, s2kparams,
-                                                  gak_data,
-                                                  get_data_rock,
-                                                  as_key,
-                                                  &out_pa_list,
-                                                  &out_pa_list_size,
-                                                  &module_ret,
-                                                  &module_flags,
-                                                  opte);
-                   if (ret == 0) {
-                       if (module_ret == 0) {
-                           if (paorder[h] == PA_REAL) {
-                               realdone = 1;
-                           }
-                       }
-                   }
-               }
-           }
-       }
+                    ret = run_preauth_plugins(context,
+                                              paorder[h],
+                                              request,
+                                              encoded_request_body,
+                                              encoded_previous_request,
+                                              in_padata[i],
+                                              prompter,
+                                              prompter_data,
+                                              rock,
+                                              &out_pa_list,
+                                              &out_pa_list_size,
+                                              &module_ret,
+                                              &module_flags,
+                                              opte);
+                    if (ret == 0) {
+                        if (module_ret == 0) {
+                            if (paorder[h] == PA_REAL) {
+                                realdone = 1;
+                            }
+                        }
+                    }
+                }
+            }
+        }
     }
 
+    TRACE_PREAUTH_OUTPUT(context, out_pa_list);
     *out_padata = out_pa_list;
-    if (etype_info)
-      krb5_free_etype_info(context, etype_info);
-    
+
+    *got_real_out = realdone;
     return(0);
- cleanup:
+cleanup:
     if (out_pa_list) {
-      out_pa_list[out_pa_list_size++] = NULL;
-      krb5_free_pa_data(context, out_pa_list);
+        out_pa_list[out_pa_list_size++] = NULL;
+        krb5_free_pa_data(context, out_pa_list);
     }
-    if (etype_info)
-      krb5_free_etype_info(context, etype_info);
     return (ret);
 }
+
+/*
+ * Give all the preauth plugins a look at the preauth option which
+ * has just been set
+ */
+krb5_error_code
+krb5_preauth_supply_preauth_data(krb5_context context, krb5_gic_opt_ext *opte,
+                                 const char *attr, const char *value)
+{
+    krb5_error_code retval = 0;
+    int i;
+    struct krb5_preauth_context_module_st *mod;
+    const char *emsg = NULL;
+
+    if (context->preauth_context == NULL)
+        krb5_init_preauth_context(context);
+    if (context->preauth_context == NULL) {
+        retval = EINVAL;
+        krb5_set_error_message(context, retval,
+                               _("Unable to initialize preauth context"));
+        return retval;
+    }
+
+    /*
+     * Go down the list of preauth modules, and supply them with the
+     * attribute/value pair.
+     */
+    for (i = 0; i < context->preauth_context->n_modules; i++) {
+        mod = &context->preauth_context->modules[i];
+        if (mod->client_supply_gic_opts == NULL)
+            continue;
+        retval = mod->client_supply_gic_opts(context, mod->moddata,
+                                             (krb5_get_init_creds_opt *)opte,
+                                             attr, value);
+        if (retval) {
+            emsg = krb5_get_error_message(context, retval);
+            krb5_set_error_message(context, retval, _("Preauth plugin %s: %s"),
+                                   mod->name, emsg);
+            krb5_free_error_message(context, emsg);
+            break;
+        }
+    }
+    return retval;
+}