Stop using SALT_TYPE_AFS_LENGTH
[krb5.git] / src / lib / krb5 / krb / preauth2.c
index 8b9cd36cda4396fff7f1fe8483fadc8f13a30c61..7c5452790218ae0128732bd4d4db72231a1fc05b 100644 (file)
@@ -1,6 +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
  */
 
 #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,
@@ -76,104 +102,94 @@ 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;
-        }
-    }
-
-    /* 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) {
+    /* 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;
+            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 (j == 0)
-                    context->modules[k].client_fini = table->fini;
+                if (first)
+                    mod->client_fini = vt->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;
+                    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
@@ -182,29 +198,35 @@ krb5_init_preauth_context(krb5_context kcontext)
                  * 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;
+                if (first) {
+                    mod->client_req_init = vt->request_init;
+                    mod->client_req_fini = vt->request_fini;
+                    rcpp = &mod->modreq;
                 } else {
-                    context->modules[k].client_req_init = NULL;
-                    context->modules[k].client_req_fini = NULL;
+                    mod->client_req_init = NULL;
+                    mod->client_req_fini = NULL;
                 }
-                context->modules[k].request_context_pp = rcpp;
+                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
@@ -221,52 +243,6 @@ krb5_clear_preauth_context_use_counts(krb5_context context)
     }
 }
 
-/*
- * 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);
-            krb5_free_error_message(context, 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
@@ -275,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
@@ -300,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);
-            }
-        }
+    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);
     }
 }
 
@@ -322,17 +292,16 @@ 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;
         }
     }
 }
@@ -405,96 +374,72 @@ grow_pa_list(krb5_pa_data ***out_pa_list, int *out_pa_list_size,
     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_error_code
-client_data_proc(krb5_context kcontext,
-                 krb5_preauth_client_rock *rock,
-                 krb5_int32 request_type,
-                 krb5_data **retdata)
+static krb5_keyblock *
+fast_armor(krb5_context context, krb5_clpreauth_rock rock)
 {
-    krb5_data *ret;
-    krb5_error_code retval;
-    char *data;
+    return rock->fast_state->armor_key;
+}
 
-    if (rock->magic != CLIENT_ROCK_MAGIC)
-        return EINVAL;
-    if (retdata == NULL)
-        return EINVAL;
+static krb5_error_code
+get_as_key(krb5_context context, krb5_clpreauth_rock rock,
+           krb5_keyblock **keyblock)
+{
+    krb5_error_code ret;
+    krb5_data *salt;
 
-    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 (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)
-            free(ret);
-        return retval;
+            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. */
@@ -534,12 +479,7 @@ run_preauth_plugins(krb5_context kcontext,
                     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_clpreauth_rock preauth_rock,
                     krb5_pa_data ***out_pa_list,
                     int *out_pa_list_size,
                     int *module_ret,
@@ -549,7 +489,7 @@ run_preauth_plugins(krb5_context kcontext,
     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;
@@ -566,10 +506,7 @@ run_preauth_plugins(krb5_context kcontext,
         /* 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
+                TRACE_PREAUTH_SKIP(kcontext, module->name, module->pa_type);
                 continue;
             }
             module->use_count++;
@@ -580,20 +517,15 @@ run_preauth_plugins(krb5_context kcontext,
         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,
+        ret = module->client_process(kcontext, module->moddata,
+                                     *module->modreq_p,
                                      (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);
+                                     &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;
@@ -624,25 +556,103 @@ padata2data(krb5_pa_data p)
     return d;
 }
 
+/* Set etype info parameters in rock based on padata. */
 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)
+get_etype_info(krb5_context context, krb5_pa_data **padata,
+               krb5_kdc_req *request, krb5_clpreauth_rock rock)
 {
-    krb5_data tmp;
-    krb5_error_code retval;
+    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;
 
-    tmp = padata2data(*in_padata);
-    krb5_free_data_contents(context, salt);
-    retval = krb5int_copy_data_contents(context, &tmp, salt);
-    if (retval)
-        return retval;
+    /* 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;
+        }
 
-    if (in_padata->pa_type == KRB5_PADATA_AFS3_SALT)
-        salt->length = SALT_TYPE_AFS_LENGTH;
+        /* 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
@@ -655,6 +665,8 @@ pa_fx_cookie(krb5_context context, krb5_kdc_req *request,
 {
     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;
     contents = malloc(in_padata->length);
@@ -669,1012 +681,6 @@ pa_fx_cookie(krb5_context context, krb5_kdc_req *request,
     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 == 0)
-            *etype = ENCTYPE_DES_CBC_CRC;
-
-        if ((ret = (gak_fct)(context, request->client, *etype, prompter,
-                             prompter_data, salt, s2kparams, as_key,
-                             gak_data))) {
-            krb5_free_sam_challenge(context, sam_challenge);
-            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.  */
-        krb5_free_sam_challenge(context, sam_challenge);
-        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;
-
-    krb5_free_sam_challenge(context, 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_helper(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)
-{
-    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};
-
-    kdcPkinitDebug("pa_pkinit_gen_req\n");
-
-    /* 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;
-    }
-
-    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];
-    }
-
-    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 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;
-}
-
-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};
-
-    *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;
-    }
-
-    /* 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) {
-        krb5_free_sam_challenge_2(context, sc2);
-        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) {
-        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);
-        krb5_free_data(context, scratch);
-        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);
-        krb5_free_data(context, scratch);
-        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;
-    free(scratch);
-
-    *out_padata = sam_padata;
-
-    return(0);
-}
-
 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,
@@ -1724,43 +730,6 @@ pa_s4u_x509_user(krb5_context context, krb5_kdc_req *request,
 
 /* 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,
-    },
-#if APPLE_PKINIT
-    {
-        KRB5_PADATA_PK_AS_REQ,
-        pa_pkinit_gen_req,
-        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,
@@ -1791,18 +760,15 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
                          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_pa_data **err_padata,
                          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_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;
 
@@ -1815,6 +781,8 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
         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++) {
@@ -1825,20 +793,16 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
             if (module->client_tryagain == NULL) {
                 continue;
             }
-            if ((*module->client_tryagain)(kcontext,
-                                           module->plugin_context,
-                                           *module->request_context_pp,
+            if ((*module->client_tryagain)(kcontext, module->moddata,
+                                           *module->modreq_p,
                                            (krb5_get_init_creds_opt *)opte,
-                                           client_data_proc,
-                                           get_data_rock,
+                                           &callbacks, preauth_rock,
                                            request,
                                            encoded_request_body,
                                            encoded_previous_request,
-                                           padata[i],
-                                           err_reply,
+                                           padata[i]->pa_type,
+                                           err_reply, err_padata,
                                            prompter, prompter_data,
-                                           gak_fct, gak_data, salt, s2kparams,
-                                           as_key,
                                            &out_padata) == 0) {
                 if (out_padata != NULL) {
                     int k;
@@ -1846,6 +810,7 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
                     grow_pa_list(return_padata, &out_pa_list_size,
                                  out_padata, k);
                     free(out_padata);
+                    TRACE_PREAUTH_TRYAGAIN_OUTPUT(kcontext, *return_padata);
                     return 0;
                 }
             }
@@ -1855,44 +820,34 @@ krb5_do_preauth_tryagain(krb5_context kcontext,
 }
 
 krb5_error_code KRB5_CALLCONV
-krb5_do_preauth(krb5_context context,
-                krb5_kdc_req *request,
+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_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);
     }
 
-#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;
@@ -1902,111 +857,6 @@ krb5_do_preauth(krb5_context context,
     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) &&
@@ -2017,18 +867,17 @@ krb5_do_preauth(krb5_context context,
 #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)))) {
+                        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) {
-#ifdef DEBUG
-                                fprintf (stderr,
-                                         "internal function for type %d, flag %d "
-                                         "failed with err %d\n",
-                                         in_padata[i]->pa_type, paorder[h], ret);
-#endif
+                                TRACE_PREAUTH_INFO_FAIL(context,
+                                                        in_padata[i]->pa_type,
+                                                        ret);
                                 ret = 0;
                                 continue; /* PA_INFO type failed, ignore */
                             }
@@ -2063,11 +912,7 @@ krb5_do_preauth(krb5_context context,
                                               in_padata[i],
                                               prompter,
                                               prompter_data,
-                                              gak_fct,
-                                              salt, s2kparams,
-                                              gak_data,
-                                              get_data_rock,
-                                              as_key,
+                                              rock,
                                               &out_pa_list,
                                               &out_pa_list_size,
                                               &module_ret,
@@ -2085,17 +930,59 @@ krb5_do_preauth(krb5_context context,
         }
     }
 
+    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:
     if (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;
+}