Stop using SALT_TYPE_AFS_LENGTH
[krb5.git] / src / lib / krb5 / krb / get_in_tkt.c
index 5419f3723c499ffab79e739ce9f989aa650f6c75..738bd9c3778ed9020c730570d11edd1ddee766a3 100644 (file)
@@ -1,6 +1,6 @@
+/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
+/* lib/krb5/krb/get_in_tkt.c */
 /*
- * lib/krb5/krb/get_in_tkt.c
- *
  * Copyright 1990,1991, 2003, 2008 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
@@ -8,7 +8,7 @@
  *   require a specific license from the United States Government.
  *   It is the responsibility of any person or organization contemplating
  *   export to obtain such a license before exporting.
- * 
+ *
  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
  * distribute this software and its documentation for any purpose and
  * without fee is hereby granted, provided that the above copyright
@@ -22,9 +22,6 @@
  * M.I.T. makes no representations about the suitability of
  * this software for any purpose.  It is provided "as is" without express
  * or implied warranty.
- * 
- *
- * krb5_get_in_tkt()
  */
 
 #include <string.h>
 #include "k5-int.h"
 #include "int-proto.h"
 #include "os-proto.h"
-
-#if APPLE_PKINIT
-#define     IN_TKT_DEBUG    0
-#if        IN_TKT_DEBUG
-#define     inTktDebug(args...)       printf(args)
-#else
-#define     inTktDebug(args...)
-#endif
-#endif /* APPLE_PKINIT */
-
-/*
- All-purpose initial ticket routine, usually called via
- krb5_get_in_tkt_with_password or krb5_get_in_tkt_with_skey.
-
- Attempts to get an initial ticket for creds->client to use server
- creds->server, (realm is taken from creds->client), with options
- options, and using creds->times.starttime, creds->times.endtime,
- creds->times.renew_till as from, till, and rtime.  
- creds->times.renew_till is ignored unless the RENEWABLE option is requested.
-
- key_proc is called to fill in the key to be used for decryption.
- keyseed is passed on to key_proc.
-
- decrypt_proc is called to perform the decryption of the response (the
- encrypted part is in dec_rep->enc_part; the decrypted part should be
- allocated and filled into dec_rep->enc_part2
- arg is passed on to decrypt_proc.
-
- If addrs is non-NULL, it is used for the addresses requested.  If it is
- null, the system standard addresses are used.
-
- A succesful call will place the ticket in the credentials cache ccache
- and fill in creds with the ticket information used/returned..
-
- returns system errors, encryption errors
-
- */
-
+#include "fast.h"
+#include "init_creds_ctx.h"
 
 /* some typedef's for the function args to make things look a bit cleaner */
 
-typedef krb5_error_code (*git_key_proc) (krb5_context,
-                                                  krb5_enctype,
-                                                  krb5_data *,
-                                                  krb5_const_pointer,
-                                                  krb5_keyblock **);
-
-typedef krb5_error_code (*git_decrypt_proc) (krb5_context,
-                                                      const krb5_keyblock *,
-                                                      krb5_const_pointer,
-                                                      krb5_kdc_rep * );
-
-static krb5_error_code make_preauth_list (krb5_context, 
-                                                   krb5_preauthtype *,
-                                                   int, krb5_pa_data ***);
+static krb5_error_code make_preauth_list (krb5_context,
+                                          krb5_preauthtype *,
+                                          int, krb5_pa_data ***);
 static krb5_error_code sort_krb5_padata_sequence(krb5_context context,
-                                                krb5_data *realm,
-                                                krb5_pa_data **padata);
+                                                 krb5_data *realm,
+                                                 krb5_pa_data **padata);
 
 /*
  * This function performs 32 bit bounded addition so we can generate
  * lifetimes without overflowing krb5_int32
  */
-static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y)
+static krb5_int32
+krb5int_addint32 (krb5_int32 x, krb5_int32 y)
 {
     if ((x > 0) && (y > (KRB5_INT32_MAX - x))) {
         /* sum will be be greater than KRB5_INT32_MAX */
@@ -104,189 +55,102 @@ static krb5_int32 krb5int_addint32 (krb5_int32 x, krb5_int32 y)
         /* sum will be less than KRB5_INT32_MIN */
         return KRB5_INT32_MIN;
     }
-    
-    return x + y;
-}
 
-#if APPLE_PKINIT
-/*
- * Common code to generate krb5_kdc_req.nonce. Like the original MIT code this
- * just uses krb5_timeofday(); it should use a PRNG. Even more unfortunately this
- * value is used interchangeably with an explicit now_time throughout this module...
- */
-static krb5_error_code  
-gen_nonce(krb5_context  context,
-          krb5_int32    *nonce)
-{
-    krb5_int32 time_now;
-    krb5_error_code retval = krb5_timeofday(context, &time_now);
-    if(retval) {
-       return retval;
-    }
-    *nonce = time_now;
-    return 0;
+    return x + y;
 }
-#endif /* APPLE_PKINIT */
 
-/*
- * This function sends a request to the KDC, and gets back a response;
- * the response is parsed into ret_err_reply or ret_as_reply if the
- * reponse is a KRB_ERROR or a KRB_AS_REP packet.  If it is some other
- * unexpected response, an error is returned.
- */
 static krb5_error_code
-send_as_request(krb5_context           context,
-               krb5_data *packet, const krb5_data *realm,
-               krb5_error **           ret_err_reply,
-               krb5_kdc_rep **         ret_as_reply,
-               int                         *use_master)
+decrypt_as_reply(krb5_context context, krb5_kdc_req *request,
+                 krb5_kdc_rep *as_reply, krb5_keyblock *key)
 {
-    krb5_kdc_rep *as_reply = 0;
-    krb5_error_code retval;
-    krb5_data reply;
-    char k4_version;           /* same type as *(krb5_data::data) */
-    int tcp_only = 0;
-
-    reply.data = 0;
-
-    /* set the nonce if the caller expects us to do it */
-
-    k4_version = packet->data[0];
-send_again:
-    retval = krb5_sendto_kdc(context, packet, 
-                            realm,
-                            &reply, use_master, tcp_only);
-#if APPLE_PKINIT
-    inTktDebug("krb5_sendto_kdc returned %d\n", (int)retval);
-#endif /* APPLE_PKINIT */
-
-    if (retval)
-       goto cleanup;
-
-    /* now decode the reply...could be error or as_rep */
-    if (krb5_is_krb_error(&reply)) {
-       krb5_error *err_reply;
-
-       if ((retval = decode_krb5_error(&reply, &err_reply)))
-           /* some other error code--??? */        
-           goto cleanup;
-    
-       if (ret_err_reply) {
-           if (err_reply->error == KRB_ERR_RESPONSE_TOO_BIG
-               && tcp_only == 0) {
-               tcp_only = 1;
-               krb5_free_error(context, err_reply);
-               free(reply.data);
-               reply.data = 0;
-               goto send_again;
-           }
-           *ret_err_reply = err_reply;
-       } else
-           krb5_free_error(context, err_reply);
-       goto cleanup;
-    }
-
-    /*
-     * Check to make sure it isn't a V4 reply.
-     */
-    if (!krb5_is_as_rep(&reply)) {
-/* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
-#define V4_KRB_PROT_VERSION    4
-#define V4_AUTH_MSG_ERR_REPLY  (5<<1)
-       /* check here for V4 reply */
-       unsigned int t_switch;
-
-       /* From v4 g_in_tkt.c: This used to be
-          switch (pkt_msg_type(rpkt) & ~1) {
-          but SCO 3.2v4 cc compiled that incorrectly.  */
-       t_switch = reply.data[1];
-       t_switch &= ~1;
-
-       if (t_switch == V4_AUTH_MSG_ERR_REPLY
-           && (reply.data[0] == V4_KRB_PROT_VERSION
-               || reply.data[0] == k4_version)) {
-           retval = KRB5KRB_AP_ERR_V4_REPLY;
-       } else {
-           retval = KRB5KRB_AP_ERR_MSG_TYPE;
-       }
-       goto cleanup;
-    }
-
-    /* It must be a KRB_AS_REP message, or an bad returned packet */
-    if ((retval = decode_krb5_as_rep(&reply, &as_reply)))
-       /* some other error code ??? */
-       goto cleanup;
-
-    if (as_reply->msg_type != KRB5_AS_REP) {
-       retval = KRB5KRB_AP_ERR_MSG_TYPE;
-       krb5_free_kdc_rep(context, as_reply);
-       goto cleanup;
-    }
-
-    if (ret_as_reply)
-       *ret_as_reply = as_reply;
-    else
-       krb5_free_kdc_rep(context, as_reply);
+    if (as_reply->enc_part2)
+        return 0;
 
-cleanup:
-    if (reply.data)
-       free(reply.data);
-    return retval;
+    return krb5_kdc_rep_decrypt_proc(context, key, NULL, as_reply);
 }
 
+/**
+ * Fully anonymous replies include a pa_pkinit_kx padata type including the KDC
+ * contribution key.  This routine confirms that the session key is of the
+ * right form for fully anonymous requests.  It is here rather than in the
+ * preauth code because the session key cannot be verified until the AS reply
+ * is decrypted and the preauth code all runs before the AS reply is decrypted.
+ */
 static krb5_error_code
-decrypt_as_reply(krb5_context          context,
-                krb5_kdc_req           *request,
-                krb5_kdc_rep           *as_reply,
-                git_key_proc           key_proc,
-                krb5_const_pointer     keyseed,
-                krb5_keyblock *        key,
-                git_decrypt_proc       decrypt_proc,
-                krb5_const_pointer     decryptarg)
+verify_anonymous( krb5_context context, krb5_kdc_req *request,
+                  krb5_kdc_rep *reply, krb5_keyblock *as_key)
 {
-    krb5_error_code            retval;
-    krb5_keyblock *            decrypt_key = 0;
-    krb5_data                  salt;
-    
-    if (as_reply->enc_part2)
-       return 0;
-
-    if (key)
-           decrypt_key = key;
-    else {
-       if ((retval = krb5_principal2salt(context, request->client, &salt)))
-           return(retval);
-    
-       retval = (*key_proc)(context, as_reply->enc_part.enctype,
-                            &salt, keyseed, &decrypt_key);
-       free(salt.data);
-       if (retval)
-           goto cleanup;
+    krb5_error_code ret = 0;
+    krb5_pa_data *pa;
+    krb5_data scratch;
+    krb5_keyblock *kdc_key = NULL, *expected = NULL;
+    krb5_enc_data *enc = NULL;
+    krb5_keyblock *session = reply->enc_part2->session;
+
+    if (!krb5_principal_compare_any_realm(context, request->client,
+                                          krb5_anonymous_principal()))
+        return 0; /* Only applies to fully anonymous */
+    pa = krb5int_find_pa_data(context, reply->padata, KRB5_PADATA_PKINIT_KX);
+    if (pa == NULL)
+        goto verification_error;
+    scratch.length = pa->length;
+    scratch.data = (char *) pa->contents;
+    ret = decode_krb5_enc_data( &scratch, &enc);
+    if (ret)
+        goto cleanup;
+    scratch.data = k5alloc(enc->ciphertext.length, &ret);
+    if (ret)
+        goto cleanup;
+    scratch.length = enc->ciphertext.length;
+    ret = krb5_c_decrypt(context, as_key, KRB5_KEYUSAGE_PA_PKINIT_KX,
+                         NULL /*cipherstate*/, enc, &scratch);
+    if (ret) {
+        free(scratch.data);
+        goto cleanup;
     }
-    
-    if ((retval = (*decrypt_proc)(context, decrypt_key, decryptarg, as_reply)))
-       goto cleanup;
-
+    ret = decode_krb5_encryption_key( &scratch, &kdc_key);
+    zap(scratch.data, scratch.length);
+    free(scratch.data);
+    if (ret)
+        goto cleanup;
+    ret = krb5_c_fx_cf2_simple(context, kdc_key, "PKINIT",
+                               as_key, "KEYEXCHANGE", &expected);
+    if (ret)
+        goto cleanup;
+    if ((expected->enctype != session->enctype) ||
+        (expected->length != session->length) ||
+        (memcmp(expected->contents, session->contents, expected->length) != 0))
+        goto verification_error;
 cleanup:
-    if (!key && decrypt_key)
-       krb5_free_keyblock(context, decrypt_key);
-    return (retval);
+    if (kdc_key)
+        krb5_free_keyblock(context, kdc_key);
+    if (expected)
+        krb5_free_keyblock(context, expected);
+    if (enc)
+        krb5_free_enc_data(context, enc);
+    return ret;
+verification_error:
+    ret = KRB5_KDCREP_MODIFIED;
+    krb5_set_error_message(context, ret, _("Reply has wrong form of session "
+                                           "key for anonymous request"));
+    goto cleanup;
 }
 
 static krb5_error_code
-verify_as_reply(krb5_context           context,
-               krb5_timestamp          time_now,
-               krb5_kdc_req            *request,
-               krb5_kdc_rep            *as_reply)
+verify_as_reply(krb5_context            context,
+                krb5_timestamp          time_now,
+                krb5_kdc_req            *request,
+                krb5_kdc_rep            *as_reply)
 {
-    krb5_error_code            retval;
-    int                                canon_req;
-    int                                canon_ok;
+    krb5_error_code             retval;
+    int                         canon_req;
+    int                         canon_ok;
+    krb5_timestamp              time_offset;
 
     /* check the contents for sanity: */
     if (!as_reply->enc_part2->times.starttime)
-       as_reply->enc_part2->times.starttime =
-           as_reply->enc_part2->times.authtime;
+        as_reply->enc_part2->times.starttime =
+            as_reply->enc_part2->times.authtime;
 
     /*
      * We only allow the AS-REP server name to be changed if the
@@ -294,1202 +158,1557 @@ verify_as_reply(krb5_context          context,
      * principal) and we requested (and received) a TGT.
      */
     canon_req = ((request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
-       (krb5_princ_type(context, request->client) == KRB5_NT_ENTERPRISE_PRINCIPAL);
+        (krb5_princ_type(context, request->client) ==
+         KRB5_NT_ENTERPRISE_PRINCIPAL) ||
+        (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS);
     if (canon_req) {
-       canon_ok = IS_TGS_PRINC(context, request->server) &&
-                  IS_TGS_PRINC(context, as_reply->enc_part2->server);
+        canon_ok = IS_TGS_PRINC(context, request->server) &&
+            IS_TGS_PRINC(context, as_reply->enc_part2->server);
+        if (!canon_ok && (request->kdc_options & KDC_OPT_REQUEST_ANONYMOUS)) {
+            canon_ok = krb5_principal_compare_any_realm(context,
+                                                        as_reply->client,
+                                                        krb5_anonymous_principal());
+        }
     } else
-       canon_ok = 0;
+        canon_ok = 0;
+
     if ((!canon_ok &&
-        (!krb5_principal_compare(context, as_reply->client, request->client) ||
-         !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)))
-       || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server)
-       || (request->nonce != as_reply->enc_part2->nonce)
-       /* XXX check for extraneous flags */
-       /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
-       || ((request->kdc_options & KDC_OPT_POSTDATED) &&
-           (request->from != 0) &&
-           (request->from != as_reply->enc_part2->times.starttime))
-       || ((request->till != 0) &&
-           (as_reply->enc_part2->times.endtime > request->till))
-       || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
-           (request->rtime != 0) &&
-           (as_reply->enc_part2->times.renew_till > request->rtime))
-       || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
-           !(request->kdc_options & KDC_OPT_RENEWABLE) &&
-           (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
-           (request->till != 0) &&
-           (as_reply->enc_part2->times.renew_till > request->till))
-       ) {
-#if APPLE_PKINIT
-       inTktDebug("verify_as_reply: KDCREP_MODIFIED\n");
-       #if IN_TKT_DEBUG
-       if(request->client->realm.length && request->client->data->length)
-           inTktDebug("request: name %s realm %s\n", 
-               request->client->realm.data, request->client->data->data);
-       if(as_reply->client->realm.length && as_reply->client->data->length)
-           inTktDebug("reply  : name %s realm %s\n", 
-               as_reply->client->realm.data, as_reply->client->data->data);
-       #endif
-#endif /* APPLE_PKINIT */
-       return KRB5_KDCREP_MODIFIED;
+         (!krb5_principal_compare(context, as_reply->client, request->client) ||
+          !krb5_principal_compare(context, as_reply->enc_part2->server, request->server)))
+        || !krb5_principal_compare(context, as_reply->enc_part2->server, as_reply->ticket->server)
+        || (request->nonce != as_reply->enc_part2->nonce)
+        /* XXX check for extraneous flags */
+        /* XXX || (!krb5_addresses_compare(context, addrs, as_reply->enc_part2->caddrs)) */
+        || ((request->kdc_options & KDC_OPT_POSTDATED) &&
+            (request->from != 0) &&
+            (request->from != as_reply->enc_part2->times.starttime))
+        || ((request->till != 0) &&
+            (as_reply->enc_part2->times.endtime > request->till))
+        || ((request->kdc_options & KDC_OPT_RENEWABLE) &&
+            (request->rtime != 0) &&
+            (as_reply->enc_part2->times.renew_till > request->rtime))
+        || ((request->kdc_options & KDC_OPT_RENEWABLE_OK) &&
+            !(request->kdc_options & KDC_OPT_RENEWABLE) &&
+            (as_reply->enc_part2->flags & KDC_OPT_RENEWABLE) &&
+            (request->till != 0) &&
+            (as_reply->enc_part2->times.renew_till > request->till))
+    ) {
+        return KRB5_KDCREP_MODIFIED;
     }
 
     if (context->library_options & KRB5_LIBOPT_SYNC_KDCTIME) {
-       retval = krb5_set_real_time(context,
-                                   as_reply->enc_part2->times.authtime, -1);
-       if (retval)
-           return retval;
+        time_offset = as_reply->enc_part2->times.authtime - time_now;
+        retval = krb5_set_time_offsets(context, time_offset, 0);
+        if (retval)
+            return retval;
     } else {
-       if ((request->from == 0) &&
-           (labs(as_reply->enc_part2->times.starttime - time_now)
-            > context->clockskew))
-           return (KRB5_KDCREP_SKEW);
+        if ((request->from == 0) &&
+            (labs(as_reply->enc_part2->times.starttime - time_now)
+             > context->clockskew))
+            return (KRB5_KDCREP_SKEW);
     }
     return 0;
 }
 
 static krb5_error_code
-stash_as_reply(krb5_context            context,
-              krb5_timestamp           time_now,
-              krb5_kdc_req             *request,
-              krb5_kdc_rep             *as_reply,
-              krb5_creds *             creds,
-              krb5_ccache              ccache)
+stash_as_reply(krb5_context             context,
+               krb5_timestamp           time_now,
+               krb5_kdc_req             *request,
+               krb5_kdc_rep             *as_reply,
+               krb5_creds *             creds,
+               krb5_ccache              ccache)
 {
-    krb5_error_code            retval;
-    krb5_data *                        packet;
-    krb5_principal             client;
-    krb5_principal             server;
+    krb5_error_code             retval;
+    krb5_data *                 packet;
+    krb5_principal              client;
+    krb5_principal              server;
 
     client = NULL;
     server = NULL;
 
     if (!creds->client)
         if ((retval = krb5_copy_principal(context, as_reply->client, &client)))
-           goto cleanup;
+            goto cleanup;
 
     if (!creds->server)
-       if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
-                                         &server)))
-           goto cleanup;
+        if ((retval = krb5_copy_principal(context, as_reply->enc_part2->server,
+                                          &server)))
+            goto cleanup;
 
     /* fill in the credentials */
-    if ((retval = krb5_copy_keyblock_contents(context, 
-                                             as_reply->enc_part2->session,
-                                             &creds->keyblock)))
-       goto cleanup;
+    if ((retval = krb5_copy_keyblock_contents(context,
+                                              as_reply->enc_part2->session,
+                                              &creds->keyblock)))
+        goto cleanup;
 
     creds->times = as_reply->enc_part2->times;
-    creds->is_skey = FALSE;            /* this is an AS_REQ, so cannot
-                                          be encrypted in skey */
+    creds->is_skey = FALSE;             /* this is an AS_REQ, so cannot
+                                           be encrypted in skey */
     creds->ticket_flags = as_reply->enc_part2->flags;
     if ((retval = krb5_copy_addresses(context, as_reply->enc_part2->caddrs,
-                                     &creds->addresses)))
-       goto cleanup;
+                                      &creds->addresses)))
+        goto cleanup;
 
     creds->second_ticket.length = 0;
     creds->second_ticket.data = 0;
 
     if ((retval = encode_krb5_ticket(as_reply->ticket, &packet)))
-       goto cleanup;
+        goto cleanup;
 
     creds->ticket = *packet;
     free(packet);
 
     /* store it in the ccache! */
     if (ccache)
-       if ((retval = krb5_cc_store_cred(context, ccache, creds)))
-           goto cleanup;
+        if ((retval = krb5_cc_store_cred(context, ccache, creds)))
+            goto cleanup;
 
     if (!creds->client)
-       creds->client = client;
+        creds->client = client;
     if (!creds->server)
-       creds->server = server;
+        creds->server = server;
 
 cleanup:
     if (retval) {
-       if (client)
-           krb5_free_principal(context, client);
-       if (server)
-           krb5_free_principal(context, server);
-       if (creds->keyblock.contents) {
-           memset(creds->keyblock.contents, 0,
-                  creds->keyblock.length);
-           free(creds->keyblock.contents);
-           creds->keyblock.contents = 0;
-           creds->keyblock.length = 0;
-       }
-       if (creds->ticket.data) {
-           free(creds->ticket.data);
-           creds->ticket.data = 0;
-       }
-       if (creds->addresses) {
-           krb5_free_addresses(context, creds->addresses);
-           creds->addresses = 0;
-       }
+        if (client)
+            krb5_free_principal(context, client);
+        if (server)
+            krb5_free_principal(context, server);
+        if (creds->keyblock.contents) {
+            memset(creds->keyblock.contents, 0,
+                   creds->keyblock.length);
+            free(creds->keyblock.contents);
+            creds->keyblock.contents = 0;
+            creds->keyblock.length = 0;
+        }
+        if (creds->ticket.data) {
+            free(creds->ticket.data);
+            creds->ticket.data = 0;
+        }
+        if (creds->addresses) {
+            krb5_free_addresses(context, creds->addresses);
+            creds->addresses = 0;
+        }
     }
     return (retval);
 }
 
 static krb5_error_code
-make_preauth_list(krb5_context context,
-                 krb5_preauthtype *    ptypes,
-                 int                   nptypes,
-                 krb5_pa_data ***      ret_list)
+make_preauth_list(krb5_context  context,
+                  krb5_preauthtype *    ptypes,
+                  int                   nptypes,
+                  krb5_pa_data ***      ret_list)
 {
-    krb5_preauthtype *         ptypep;
-    krb5_pa_data **            preauthp;
-    int                                i;
+    krb5_preauthtype *          ptypep;
+    krb5_pa_data **             preauthp;
+    int                         i;
 
     if (nptypes < 0) {
-       for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
-           ;
+        for (nptypes=0, ptypep = ptypes; *ptypep; ptypep++, nptypes++)
+            ;
     }
+
     /* allocate space for a NULL to terminate the list */
+
     if ((preauthp =
-        (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
-       return(ENOMEM);
+         (krb5_pa_data **) malloc((nptypes+1)*sizeof(krb5_pa_data *))) == NULL)
+        return(ENOMEM);
+
     for (i=0; i<nptypes; i++) {
-       if ((preauthp[i] =
-            (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
-           for (; i>=0; i--)
-               free(preauthp[i]);
-           free(preauthp);
-           return (ENOMEM);
-       }
-       preauthp[i]->magic = KV5M_PA_DATA;
-       preauthp[i]->pa_type = ptypes[i];
-       preauthp[i]->length = 0;
-       preauthp[i]->contents = 0;
+        if ((preauthp[i] =
+             (krb5_pa_data *) malloc(sizeof(krb5_pa_data))) == NULL) {
+            for (; i>=0; i--)
+                free(preauthp[i]);
+            free(preauthp);
+            return (ENOMEM);
+        }
+        preauthp[i]->magic = KV5M_PA_DATA;
+        preauthp[i]->pa_type = ptypes[i];
+        preauthp[i]->length = 0;
+        preauthp[i]->contents = 0;
     }
-     
+
     /* fill in the terminating NULL */
+
     preauthp[nptypes] = NULL;
+
     *ret_list = preauthp;
     return 0;
 }
 
 #define MAX_IN_TKT_LOOPS 16
-static const krb5_enctype get_in_tkt_enctypes[] = {
-    ENCTYPE_DES3_CBC_SHA1,
-    ENCTYPE_ARCFOUR_HMAC,
-    ENCTYPE_DES_CBC_MD5,
-    ENCTYPE_DES_CBC_MD4,
-    ENCTYPE_DES_CBC_CRC,
-    0
-};
 
-krb5_error_code KRB5_CALLCONV
-krb5_get_in_tkt(krb5_context context,
-               krb5_flags options,
-               krb5_address * const * addrs,
-               krb5_enctype * ktypes,
-               krb5_preauthtype * ptypes,
-               git_key_proc key_proc,
-               krb5_const_pointer keyseed,
-               git_decrypt_proc decrypt_proc,
-               krb5_const_pointer decryptarg,
-               krb5_creds * creds,
-               krb5_ccache ccache,
-               krb5_kdc_rep ** ret_as_reply)
+static krb5_error_code
+request_enc_pa_rep(krb5_pa_data ***padptr)
 {
-    krb5_error_code    retval;
-    krb5_timestamp     time_now;
-    krb5_keyblock *    decrypt_key = 0;
-    krb5_kdc_req       request;
-    krb5_data *encoded_request;
-    krb5_pa_data       **padata = 0;
-    krb5_error *       err_reply;
-    krb5_kdc_rep *     as_reply = 0;
-    krb5_pa_data  **   preauth_to_use = 0;
-    int                        loopcount = 0;
-    krb5_int32         do_more = 0;
-    int                        canon_flag;
-    int             use_master = 0;
-    int                        referral_count = 0;
-    krb5_principal_data        referred_client;
-
-#if APPLE_PKINIT
-    inTktDebug("krb5_get_in_tkt top\n");
-#endif /* APPLE_PKINIT */
-
-    if (! krb5_realm_compare(context, creds->client, creds->server))
-       return KRB5_IN_TKT_REALM_MISMATCH;
-
-    if (ret_as_reply)
-       *ret_as_reply = 0;
-
-    referred_client = *(creds->client);
-    referred_client.realm.data = NULL;
-    referred_client.realm.length = 0;
+    size_t size = 0;
+    krb5_pa_data **pad = *padptr;
+    krb5_pa_data *pa= NULL;
+    if (pad)
+        for (size=0; pad[size]; size++);
+    pad = realloc(pad, sizeof(*pad)*(size+2));
+
+    if (pad == NULL)
+        return ENOMEM;
+    pad[size+1] = NULL;
+    pa = malloc(sizeof(krb5_pa_data));
+    if (pa == NULL)
+        return ENOMEM;
+    pa->contents = NULL;
+    pa->length = 0;
+    pa->pa_type = KRB5_ENCPADATA_REQ_ENC_PA_REP;
+    pad[size] = pa;
+    *padptr = pad;
+    return 0;
+}
 
-    /* per referrals draft, enterprise principals imply canonicalization */
-    canon_flag = ((options & KDC_OPT_CANONICALIZE) != 0) ||
-       creds->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
-    /*
-     * Set up the basic request structure
-     */
-    request.magic = KV5M_KDC_REQ;
-    request.msg_type = KRB5_AS_REQ;
-    request.addresses = 0;
-    request.ktype = 0;
-    request.padata = 0;
-    if (addrs)
-       request.addresses = (krb5_address **) addrs;
-    else
-       if ((retval = krb5_os_localaddr(context, &request.addresses)))
-           goto cleanup;
-    request.kdc_options = options;
-    request.client = creds->client;
-    request.server = creds->server;
-    request.nonce = 0;
-    request.from = creds->times.starttime;
-    request.till = creds->times.endtime;
-    request.rtime = creds->times.renew_till;
-#if APPLE_PKINIT
-    retval = gen_nonce(context, (krb5_int32 *)&time_now);
-    if(retval) {
-       goto cleanup;
-    }
-    request.nonce = time_now;
-#endif /* APPLE_PKINIT */
+/* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
+ * libdefaults entry are listed before any others. */
+static krb5_error_code
+sort_krb5_padata_sequence(krb5_context context, krb5_data *realm,
+                          krb5_pa_data **padata)
+{
+    int i, j, base;
+    krb5_error_code ret;
+    const char *p;
+    long l;
+    char *q, *preauth_types = NULL;
+    krb5_pa_data *tmp;
+    int need_free_string = 1;
 
-    request.ktype = malloc (sizeof(get_in_tkt_enctypes));
-    if (request.ktype == NULL) {
-       retval = ENOMEM;
-       goto cleanup;
-    }
-    memcpy(request.ktype, get_in_tkt_enctypes, sizeof(get_in_tkt_enctypes));
-    for (request.nktypes = 0;request.ktype[request.nktypes];request.nktypes++);
-    if (ktypes) {
-       int i, req, next = 0;
-       for (req = 0; ktypes[req]; req++) {
-           if (ktypes[req] == request.ktype[next]) {
-               next++;
-               continue;
-           }
-           for (i = next + 1; i < request.nktypes; i++)
-               if (ktypes[req] == request.ktype[i]) {
-                   /* Found the enctype we want, but not in the
-                      position we want.  Move it, but keep the old
-                      one from the desired slot around in case it's
-                      later in our requested-ktypes list.  */
-                   krb5_enctype t;
-                   t = request.ktype[next];
-                   request.ktype[next] = request.ktype[i];
-                   request.ktype[i] = t;
-                   next++;
-                   break;
-               }
-           /* If we didn't find it, don't do anything special, just
-              drop it.  */
-       }
-       request.ktype[next] = 0;
-       request.nktypes = next;
+    if ((padata == NULL) || (padata[0] == NULL)) {
+        return 0;
     }
-    request.authorization_data.ciphertext.length = 0;
-    request.authorization_data.ciphertext.data = 0;
-    request.unenc_authdata = 0;
-    request.second_ticket = 0;
 
-    /*
-     * If a list of preauth types are passed in, convert it to a
-     * preauth_to_use list.
-     */
-    if (ptypes) {
-       retval = make_preauth_list(context, ptypes, -1, &preauth_to_use);
-       if (retval)
-           goto cleanup;
+    ret = krb5int_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES,
+                                    &preauth_types);
+    if ((ret != 0) || (preauth_types == NULL)) {
+        /* Try to use PKINIT first. */
+        preauth_types = "17, 16, 15, 14";
+        need_free_string = 0;
     }
-           
-    while (1) {
-       if (loopcount++ > MAX_IN_TKT_LOOPS) {
-           retval = KRB5_GET_IN_TKT_LOOP;
-           goto cleanup;
-       }
-
-#if APPLE_PKINIT
-       inTktDebug("krb5_get_in_tkt calling krb5_obtain_padata\n");
-#endif /* APPLE_PKINIT */
-       if ((retval = krb5_obtain_padata(context, preauth_to_use, key_proc,
-                                        keyseed, creds, &request)) != 0)
-           goto cleanup;
-       if (preauth_to_use)
-           krb5_free_pa_data(context, preauth_to_use);
-       preauth_to_use = 0;
-       
-       err_reply = 0;
-       as_reply = 0;
-
-        if ((retval = krb5_timeofday(context, &time_now)))
-           goto cleanup;
 
-        /*
-         * XXX we know they are the same size... and we should do
-         * something better than just the current time
-         */
-       request.nonce = (krb5_int32) time_now;
-
-       if ((retval = encode_krb5_as_req(&request, &encoded_request)) != 0)
-           goto cleanup;
-       retval = send_as_request(context, encoded_request,
-                                krb5_princ_realm(context, request.client), &err_reply,
-                                &as_reply, &use_master);
-       krb5_free_data_contents(context, encoded_request);
-       if (retval != 0)
-           goto cleanup;
-
-       if (err_reply) {
-           if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
-               err_reply->e_data.length > 0) {
-               retval = decode_krb5_padata_sequence(&err_reply->e_data,
-                                                    &preauth_to_use);
-               krb5_free_error(context, err_reply);
-               if (retval)
-                   goto cleanup;
-                retval = sort_krb5_padata_sequence(context,
-                                                  &request.server->realm,
-                                                  padata);
-               if (retval)
-                   goto cleanup;
-               continue;
-           } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) {
-               if (++referral_count > KRB5_REFERRAL_MAXHOPS ||
-                   err_reply->client == NULL ||
-                   err_reply->client->realm.length == 0) {
-                   retval = KRB5KDC_ERR_WRONG_REALM;
-                   krb5_free_error(context, err_reply);
-                   goto cleanup;
-               }
-               /* Rewrite request.client with realm from error reply */
-               if (referred_client.realm.data) {
-                   krb5_free_data_contents(context, &referred_client.realm);
-                   referred_client.realm.data = NULL;
-               }
-               retval = krb5int_copy_data_contents(context,
-                                                   &err_reply->client->realm,
-                                                   &referred_client.realm);
-               krb5_free_error(context, err_reply);            
-               if (retval)
-                   goto cleanup;
-               request.client = &referred_client;
-               continue;
-           } else {
-               retval = (krb5_error_code) err_reply->error 
-                   + ERROR_TABLE_BASE_krb5;
-               krb5_free_error(context, err_reply);
-               goto cleanup;
-           }
-       } else if (!as_reply) {
-           retval = KRB5KRB_AP_ERR_MSG_TYPE;
-           goto cleanup;
-       }
-       if ((retval = krb5_process_padata(context, &request, as_reply,
-                                         key_proc, keyseed, decrypt_proc, 
-                                         &decrypt_key, creds,
-                                         &do_more)) != 0)
-           goto cleanup;
-
-       if (!do_more)
-           break;
+#ifdef DEBUG
+    fprintf (stderr, "preauth data types before sorting:");
+    for (i = 0; padata[i]; i++) {
+        fprintf (stderr, " %d", padata[i]->pa_type);
     }
-    
-    if ((retval = decrypt_as_reply(context, &request, as_reply, key_proc,
-                                  keyseed, decrypt_key, decrypt_proc,
-                                  decryptarg)))
-       goto cleanup;
+    fprintf (stderr, "\n");
+#endif
 
-    if ((retval = verify_as_reply(context, time_now, &request, as_reply)))
-       goto cleanup;
+    base = 0;
+    for (p = preauth_types; *p != '\0';) {
+        /* skip whitespace to find an entry */
+        p += strspn(p, ", ");
+        if (*p != '\0') {
+            /* see if we can extract a number */
+            l = strtol(p, &q, 10);
+            if ((q != NULL) && (q > p)) {
+                /* got a valid number; search for a matchin entry */
+                for (i = base; padata[i] != NULL; i++) {
+                    /* bubble the matching entry to the front of the list */
+                    if (padata[i]->pa_type == l) {
+                        tmp = padata[i];
+                        for (j = i; j > base; j--)
+                            padata[j] = padata[j - 1];
+                        padata[base] = tmp;
+                        base++;
+                        break;
+                    }
+                }
+                p = q;
+            } else {
+                break;
+            }
+        }
+    }
+    if (need_free_string)
+        free(preauth_types);
 
-    if ((retval = stash_as_reply(context, time_now, &request, as_reply,
-                                creds, ccache)))
-       goto cleanup;
+#ifdef DEBUG
+    fprintf (stderr, "preauth data types after sorting:");
+    for (i = 0; padata[i]; i++)
+        fprintf (stderr, " %d", padata[i]->pa_type);
+    fprintf (stderr, "\n");
+#endif
 
-cleanup:
-    if (request.ktype)
-       free(request.ktype);
-    if (!addrs && request.addresses)
-       krb5_free_addresses(context, request.addresses);
-    if (request.padata)
-       krb5_free_pa_data(context, request.padata);
-    if (padata)
-       krb5_free_pa_data(context, padata);
-    if (preauth_to_use)
-       krb5_free_pa_data(context, preauth_to_use);
-    if (decrypt_key)
-       krb5_free_keyblock(context, decrypt_key);
-    if (as_reply) {
-       if (ret_as_reply)
-           *ret_as_reply = as_reply;
-       else
-           krb5_free_kdc_rep(context, as_reply);
-    }
-    if (referred_client.realm.data)
-       krb5_free_data_contents(context, &referred_client.realm);
-    return (retval);
+    return 0;
 }
 
-/* begin libdefaults parsing code.  This should almost certainly move
-   somewhere else, but I don't know where the correct somewhere else
-   is yet. */
+static krb5_error_code
+build_in_tkt_name(krb5_context context,
+                  char *in_tkt_service,
+                  krb5_const_principal client,
+                  krb5_principal *server)
+{
+    krb5_error_code ret;
 
-/* XXX Duplicating this is annoying; try to work on a better way.*/
-static const char *const conf_yes[] = {
-    "y", "yes", "true", "t", "1", "on",
-    0,
-};
+    *server = NULL;
 
-static const char *const conf_no[] = {
-    "n", "no", "false", "nil", "0", "off",
-    0,
-};
+    if (in_tkt_service) {
+        /* this is ugly, because so are the data structures involved.  I'm
+           in the library, so I'm going to manipulate the data structures
+           directly, otherwise, it will be worse. */
+
+        if ((ret = krb5_parse_name(context, in_tkt_service, server)))
+            return ret;
+
+        /* stuff the client realm into the server principal.
+           realloc if necessary */
+        if ((*server)->realm.length < client->realm.length) {
+            char *p = realloc((*server)->realm.data,
+                              client->realm.length);
+            if (p == NULL) {
+                krb5_free_principal(context, *server);
+                *server = NULL;
+                return ENOMEM;
+            }
+            (*server)->realm.data = p;
+        }
 
-int
-_krb5_conf_boolean(const char *s)
+        (*server)->realm.length = client->realm.length;
+        memcpy((*server)->realm.data, client->realm.data, client->realm.length);
+    } else {
+        ret = krb5_build_principal_ext(context, server,
+                                       client->realm.length,
+                                       client->realm.data,
+                                       KRB5_TGS_NAME_SIZE,
+                                       KRB5_TGS_NAME,
+                                       client->realm.length,
+                                       client->realm.data,
+                                       0);
+        if (ret)
+            return ret;
+    }
+    /*
+     * Windows Server 2008 R2 RODC insists on TGS principal names having the
+     * right name type.
+     */
+    if (krb5_princ_size(context, *server) == 2 &&
+        data_eq_string(*krb5_princ_component(context, *server, 0),
+                       KRB5_TGS_NAME)) {
+        krb5_princ_type(context, *server) = KRB5_NT_SRV_INST;
+    }
+    return 0;
+}
+
+void KRB5_CALLCONV
+krb5_init_creds_free(krb5_context context,
+                     krb5_init_creds_context ctx)
 {
-    const char *const *p;
+    if (ctx == NULL)
+        return;
 
-    for(p=conf_yes; *p; p++) {
-       if (!strcasecmp(*p,s))
-           return 1;
+    if (ctx->opte != NULL && krb5_gic_opt_is_shadowed(ctx->opte)) {
+        krb5_get_init_creds_opt_free(context,
+                                     (krb5_get_init_creds_opt *)ctx->opte);
     }
+    free(ctx->in_tkt_service);
+    zap(ctx->password.data, ctx->password.length);
+    krb5_free_data_contents(context, &ctx->password);
+    krb5_free_error(context, ctx->err_reply);
+    krb5_free_pa_data(context, ctx->err_padata);
+    krb5_free_cred_contents(context, &ctx->cred);
+    krb5_free_kdc_req(context, ctx->request);
+    krb5_free_kdc_rep(context, ctx->reply);
+    krb5_free_data(context, ctx->outer_request_body);
+    krb5_free_data(context, ctx->inner_request_body);
+    krb5_free_data(context, ctx->encoded_previous_request);
+    krb5int_fast_free_state(context, ctx->fast_state);
+    krb5_free_pa_data(context, ctx->preauth_to_use);
+    krb5_free_data_contents(context, &ctx->salt);
+    krb5_free_data_contents(context, &ctx->s2kparams);
+    krb5_free_keyblock_contents(context, &ctx->as_key);
+    free(ctx);
+}
+
+krb5_error_code
+k5_init_creds_get(krb5_context context, krb5_init_creds_context ctx,
+                  int *use_master)
+{
+    krb5_error_code code;
+    krb5_data request;
+    krb5_data reply;
+    krb5_data realm;
+    unsigned int flags = 0;
+    int tcp_only = 0;
 
-    for(p=conf_no; *p; p++) {
-       if (!strcasecmp(*p,s))
-           return 0;
+    request.length = 0;
+    request.data = NULL;
+    reply.length = 0;
+    reply.data = NULL;
+    realm.length = 0;
+    realm.data = NULL;
+
+    for (;;) {
+        code = krb5_init_creds_step(context,
+                                    ctx,
+                                    &reply,
+                                    &request,
+                                    &realm,
+                                    &flags);
+        if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) {
+            TRACE_INIT_CREDS_RETRY_TCP(context);
+            tcp_only = 1;
+        } else if (code != 0 || !(flags & KRB5_INIT_CREDS_STEP_FLAG_CONTINUE))
+            break;
+
+        krb5_free_data_contents(context, &reply);
+
+        code = krb5_sendto_kdc(context, &request, &realm,
+                               &reply, use_master, tcp_only);
+        if (code != 0)
+            break;
+
+        krb5_free_data_contents(context, &request);
+        krb5_free_data_contents(context, &realm);
     }
 
-    /* Default to "no" */
-    return 0;
+    krb5_free_data_contents(context, &request);
+    krb5_free_data_contents(context, &reply);
+    krb5_free_data_contents(context, &realm);
+
+    return code;
 }
 
-static krb5_error_code
-krb5_libdefault_string(krb5_context context, const krb5_data *realm,
-                      const char *option, char **ret_value)
+/* Heimdal API */
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_get(krb5_context context,
+                    krb5_init_creds_context ctx)
 {
-    profile_t profile;
-    const char *names[5];
-    char **nameval = NULL;
-    krb5_error_code retval;
-    char realmstr[1024];
+    int use_master = 0;
+
+    return k5_init_creds_get(context, ctx, &use_master);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_get_creds(krb5_context context,
+                          krb5_init_creds_context ctx,
+                          krb5_creds *creds)
+{
+    if (!ctx->complete)
+        return KRB5_NO_TKT_SUPPLIED;
 
-    if (realm->length > sizeof(realmstr)-1)
-       return(EINVAL);
+    return krb5int_copy_creds_contents(context, &ctx->cred, creds);
+}
 
-    strncpy(realmstr, realm->data, realm->length);
-    realmstr[realm->length] = '\0';
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_get_times(krb5_context context,
+                          krb5_init_creds_context ctx,
+                          krb5_ticket_times *times)
+{
+    if (!ctx->complete)
+        return KRB5_NO_TKT_SUPPLIED;
 
-    if (!context || (context->magic != KV5M_CONTEXT)) 
-       return KV5M_CONTEXT;
+    *times = ctx->cred.times;
 
-    profile = context->profile;
-           
-    names[0] = KRB5_CONF_LIBDEFAULTS;
+    return 0;
+}
 
-    /*
-     * Try number one:
-     *
-     * [libdefaults]
-     *         REALM = {
-     *                 option = <boolean>
-     *         }
-     */
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_get_error(krb5_context context,
+                          krb5_init_creds_context ctx,
+                          krb5_error **error)
+{
+    krb5_error_code code;
+    krb5_error *ret = NULL;
 
-    names[1] = realmstr;
-    names[2] = option;
-    names[3] = 0;
-    retval = profile_get_values(profile, names, &nameval);
-    if (retval == 0 && nameval && nameval[0])
-       goto goodbye;
+    *error = NULL;
 
-    /*
-     * Try number two:
-     *
-     * [libdefaults]
-     *         option = <boolean>
-     */
-    
-    names[1] = option;
-    names[2] = 0;
-    retval = profile_get_values(profile, names, &nameval);
-    if (retval == 0 && nameval && nameval[0])
-       goto goodbye;
-
-goodbye:
-    if (!nameval) 
-       return(ENOENT);
-
-    if (!nameval[0]) {
-        retval = ENOENT;
-    } else {
-        *ret_value = strdup(nameval[0]);
-        if (!*ret_value)
-            retval = ENOMEM;
-    }
+    if (ctx->err_reply == NULL)
+        return 0;
 
-    profile_free_list(nameval);
+    ret = k5alloc(sizeof(*ret), &code);
+    if (code != 0)
+        goto cleanup;
 
-    return retval;
-}
+    ret->magic = KV5M_ERROR;
+    ret->ctime = ctx->err_reply->ctime;
+    ret->cusec = ctx->err_reply->cusec;
+    ret->susec = ctx->err_reply->susec;
+    ret->stime = ctx->err_reply->stime;
+    ret->error = ctx->err_reply->error;
+
+    if (ctx->err_reply->client != NULL) {
+        code = krb5_copy_principal(context, ctx->err_reply->client,
+                                   &ret->client);
+        if (code != 0)
+            goto cleanup;
+    }
 
-/* not static so verify_init_creds() can call it */
-/* as well as the DNS code */
+    code = krb5_copy_principal(context, ctx->err_reply->server, &ret->server);
+    if (code != 0)
+        goto cleanup;
 
-krb5_error_code
-krb5_libdefault_boolean(krb5_context context, const krb5_data *realm,
-                       const char *option, int *ret_value)
-{
-    char *string = NULL;
-    krb5_error_code retval;
+    code = krb5int_copy_data_contents(context, &ctx->err_reply->text,
+                                      &ret->text);
+    if (code != 0)
+        goto cleanup;
 
-    retval = krb5_libdefault_string(context, realm, option, &string);
+    code = krb5int_copy_data_contents(context, &ctx->err_reply->e_data,
+                                      &ret->e_data);
+    if (code != 0)
+        goto cleanup;
 
-    if (retval)
-       return(retval);
+    *error = ret;
 
-    *ret_value = _krb5_conf_boolean(string);
-    free(string);
+cleanup:
+    if (code != 0)
+        krb5_free_error(context, ret);
 
-    return(0);
+    return code;
 }
 
-/* Sort a pa_data sequence so that types named in the "preferred_preauth_types"
- * libdefaults entry are listed before any others. */
+/**
+ * Throw away any state related to specific realm either at the beginning of a
+ * request, or when a realm changes, or when we start to use FAST after
+ * assuming we would not do so.
+ *
+ * @param padata padata from an error if an error from the realm we now expect
+ * to talk to caused the restart.  Used to infer negotiation characteristics
+ * such as whether FAST is used.
+ */
 static krb5_error_code
-sort_krb5_padata_sequence(krb5_context context, krb5_data *realm,
-                         krb5_pa_data **padata)
+restart_init_creds_loop(krb5_context context, krb5_init_creds_context ctx,
+                        krb5_pa_data **padata)
 {
-    int i, j, base;
-    krb5_error_code ret;
-    const char *p;
-    long l;
-    char *q, *preauth_types = NULL;
-    krb5_pa_data *tmp;
-    int need_free_string = 1;
-
-    if ((padata == NULL) || (padata[0] == NULL)) {
-       return 0;
+    krb5_error_code code = 0;
+    unsigned char random_buf[4];
+    krb5_data random_data;
+    if (ctx->preauth_to_use) {
+        krb5_free_pa_data(context, ctx->preauth_to_use);
+        ctx->preauth_to_use = NULL;
     }
 
-    ret = krb5_libdefault_string(context, realm, KRB5_CONF_PREFERRED_PREAUTH_TYPES,
-                                &preauth_types);
-    if ((ret != 0) || (preauth_types == NULL)) {
-       /* Try to use PKINIT first. */
-       preauth_types = "17, 16, 15, 14";
-       need_free_string = 0;
+    if (ctx->fast_state) {
+        krb5int_fast_free_state(context, ctx->fast_state);
+        ctx->fast_state = NULL;
     }
-
-#ifdef DEBUG
-    fprintf (stderr, "preauth data types before sorting:");
-    for (i = 0; padata[i]; i++) {
-       fprintf (stderr, " %d", padata[i]->pa_type);
+    code = krb5int_fast_make_state(context, &ctx->fast_state);
+    if (code != 0)
+        goto cleanup;
+    ctx->preauth_rock.fast_state = ctx->fast_state;
+    krb5_preauth_request_context_init(context);
+    if (ctx->outer_request_body) {
+        krb5_free_data(context, ctx->outer_request_body);
+        ctx->outer_request_body = NULL;
+    }
+    if (ctx->opte &&
+        (ctx->opte->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) {
+        if ((code = make_preauth_list(context, ctx->opte->preauth_list,
+                                      ctx->opte->preauth_list_length,
+                                      &ctx->preauth_to_use)))
+            goto cleanup;
     }
-    fprintf (stderr, "\n");
-#endif
 
-    base = 0;
-    for (p = preauth_types; *p != '\0';) {
-       /* skip whitespace to find an entry */
-       p += strspn(p, ", ");
-       if (*p != '\0') {
-           /* see if we can extract a number */
-           l = strtol(p, &q, 10);
-           if ((q != NULL) && (q > p)) {
-               /* got a valid number; search for a matchin entry */
-               for (i = base; padata[i] != NULL; i++) {
-                   /* bubble the matching entry to the front of the list */
-                   if (padata[i]->pa_type == l) {
-                       tmp = padata[i];
-                       for (j = i; j > base; j--)
-                           padata[j] = padata[j - 1];
-                       padata[base] = tmp;
-                       base++;
-                       break;
-                   }
-               }
-               p = q;
-           } else {
-               break;
-           }
-       }
+    /* Set the request nonce. */
+    random_data.length = 4;
+    random_data.data = (char *)random_buf;
+    code = krb5_c_random_make_octets(context, &random_data);
+    if (code !=0)
+        goto cleanup;
+    /*
+     * See RT ticket 3196 at MIT.  If we set the high bit, we may have
+     * compatibility problems with Heimdal, because we (incorrectly) encode
+     * this value as signed.
+     */
+    ctx->request->nonce = 0x7fffffff & load_32_n(random_buf);
+    krb5_free_principal(context, ctx->request->server);
+    ctx->request->server = NULL;
+
+    code = build_in_tkt_name(context, ctx->in_tkt_service,
+                             ctx->request->client,
+                             &ctx->request->server);
+    if (code != 0)
+        goto cleanup;
+
+    ctx->request_time = time(NULL);
+
+    code = krb5int_fast_as_armor(context, ctx->fast_state,
+                                 ctx->opte, ctx->request);
+    if (code != 0)
+        goto cleanup;
+    if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) {
+        code = krb5int_fast_as_armor(context, ctx->fast_state,
+                                     ctx->opte, ctx->request);
+        if (code != 0)
+            goto cleanup;
     }
-    if (need_free_string)
-       free(preauth_types);
+    /* give the preauth plugins a chance to prep the request body */
+    krb5_preauth_prepare_request(context, ctx->opte, ctx->request);
 
-#ifdef DEBUG
-    fprintf (stderr, "preauth data types after sorting:");
-    for (i = 0; padata[i]; i++)
-       fprintf (stderr, " %d", padata[i]->pa_type);
-    fprintf (stderr, "\n");
-#endif
+    ctx->request->from = krb5int_addint32(ctx->request_time,
+                                          ctx->start_time);
+    ctx->request->till = krb5int_addint32(ctx->request->from,
+                                          ctx->tkt_life);
 
-    return 0;
+    if (ctx->renew_life > 0) {
+        ctx->request->rtime =
+            krb5int_addint32(ctx->request->from, ctx->renew_life);
+        if (ctx->request->rtime < ctx->request->till) {
+            /* don't ask for a smaller renewable time than the lifetime */
+            ctx->request->rtime = ctx->request->till;
+        }
+        ctx->request->kdc_options &= ~(KDC_OPT_RENEWABLE_OK);
+    } else
+        ctx->request->rtime = 0;
+    code = krb5int_fast_prep_req_body(context, ctx->fast_state,
+                                      ctx->request,
+                                      &ctx->outer_request_body);
+    if (code != 0)
+        goto cleanup;
+cleanup:
+    return code;
 }
 
 krb5_error_code KRB5_CALLCONV
-krb5_get_init_creds(krb5_context context,
-                   krb5_creds *creds,
-                   krb5_principal client,
-                   krb5_prompter_fct prompter,
-                   void *prompter_data,
-                   krb5_deltat start_time,
-                   char *in_tkt_service,
-                   krb5_gic_opt_ext *options,
-                   krb5_gic_get_as_key_fct gak_fct,
-                   void *gak_data,
-                   int  *use_master,
-                   krb5_kdc_rep **as_reply)
+krb5_init_creds_init(krb5_context context,
+                     krb5_principal client,
+                     krb5_prompter_fct prompter,
+                     void *data,
+                     krb5_deltat start_time,
+                     krb5_get_init_creds_opt *options,
+                     krb5_init_creds_context *pctx)
 {
-    krb5_error_code ret;
-    krb5_kdc_req request;
-    krb5_data *encoded_request_body, *encoded_previous_request;
-    krb5_pa_data **preauth_to_use, **kdc_padata;
-    int tempint;
-    char *tempstr;
-    krb5_deltat tkt_life;
-    krb5_deltat renew_life;
-    int loopcount;
-    krb5_data salt;
-    krb5_data s2kparams;
-    krb5_keyblock as_key;
-    krb5_error *err_reply;
-    krb5_kdc_rep *local_as_reply;
-    krb5_timestamp time_now;
-    krb5_enctype etype = 0;
-    krb5_preauth_client_rock get_data_rock;
-    int canon_flag = 0;
-    krb5_principal_data referred_client;
-
-    /* initialize everything which will be freed at cleanup */
-
-    s2kparams.data = NULL;
-    s2kparams.length = 0;
-    request.server = NULL;
-    request.ktype = NULL;
-    request.addresses = NULL;
-    request.padata = NULL;
-    encoded_request_body = NULL;
-    encoded_previous_request = NULL;
-    preauth_to_use = NULL;
-    kdc_padata = NULL;
-    as_key.length = 0;
-    salt.length = 0;
-    salt.data = NULL;
-
-       local_as_reply = 0;
-#if APPLE_PKINIT
-    inTktDebug("krb5_get_init_creds top\n");
-#endif /* APPLE_PKINIT */
-    
-    err_reply = NULL;
-
-    /* referred_client is used to rewrite the client realm for referrals */
-    referred_client = *client;
-    referred_client.realm.data = NULL;
-    referred_client.realm.length = 0;
+    krb5_error_code code;
+    krb5_init_creds_context ctx;
+    int tmp;
+    char *str = NULL;
+    krb5_gic_opt_ext *opte;
+    krb5_get_init_creds_opt local_opts;
 
-    /*
-     * Set up the basic request structure
-     */
-    request.magic = KV5M_KDC_REQ;
-    request.msg_type = KRB5_AS_REQ;
+    TRACE_INIT_CREDS(context, client);
 
-    /* request.nonce is filled in when we send a request to the kdc */
-    request.nonce = 0;
+    ctx = k5alloc(sizeof(*ctx), &code);
+    if (code != 0)
+        goto cleanup;
 
-    /* request.padata is filled in later */
+    ctx->request = k5alloc(sizeof(krb5_kdc_req), &code);
+    if (code != 0)
+        goto cleanup;
+    ctx->enc_pa_rep_permitted = 1;
+    code = krb5_copy_principal(context, client, &ctx->request->client);
+    if (code != 0)
+        goto cleanup;
 
-    request.kdc_options = context->kdc_default_options;
+    ctx->prompter = prompter;
+    ctx->prompter_data = data;
+    ctx->gak_fct = krb5_get_as_key_password;
+    ctx->gak_data = &ctx->password;
 
-    /* forwardable */
+    ctx->request_time = 0; /* filled in later */
+    ctx->start_time = start_time;
 
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE))
-       tempint = options->forwardable;
-    else if ((ret = krb5_libdefault_boolean(context, &client->realm,
-                                           KRB5_CONF_FORWARDABLE, &tempint)) == 0)
-           ;
+    if (options == NULL) {
+        /*
+         * We initialize a non-extended options because that way the shadowed
+         * flag will be sent and they will be freed when the init_creds context
+         * is freed. The options will be extended and copied off the stack into
+         * storage by opt_to_opte.
+         */
+        krb5_get_init_creds_opt_init(&local_opts);
+        options = &local_opts;
+    }
+
+    code = krb5int_gic_opt_to_opte(context, options,
+                                   &ctx->opte, 1, "krb5_init_creds_init");
+    if (code != 0)
+        goto cleanup;
+
+    opte = ctx->opte;
+
+    ctx->preauth_rock.magic = CLIENT_ROCK_MAGIC;
+    ctx->preauth_rock.etype = &ctx->etype;
+    ctx->preauth_rock.as_key = &ctx->as_key;
+    ctx->preauth_rock.gak_fct = &ctx->gak_fct;
+    ctx->preauth_rock.gak_data = &ctx->gak_data;
+    ctx->preauth_rock.default_salt = &ctx->default_salt;
+    ctx->preauth_rock.salt = &ctx->salt;
+    ctx->preauth_rock.s2kparams = &ctx->s2kparams;
+    ctx->preauth_rock.client = client;
+    ctx->preauth_rock.prompter = prompter;
+    ctx->preauth_rock.prompter_data = data;
+
+    /* Initialise request parameters as per krb5_get_init_creds() */
+    ctx->request->kdc_options = context->kdc_default_options;
+
+    /* forwaradble */
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_FORWARDABLE)
+        tmp = opte->forwardable;
+    else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
+                                        KRB5_CONF_FORWARDABLE, &tmp) == 0)
+        ;
     else
-       tempint = 0;
-    if (tempint)
-       request.kdc_options |= KDC_OPT_FORWARDABLE;
+        tmp = 0;
+    if (tmp)
+        ctx->request->kdc_options |= KDC_OPT_FORWARDABLE;
 
     /* proxiable */
-
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE))
-       tempint = options->proxiable;
-    else if ((ret = krb5_libdefault_boolean(context, &client->realm,
-                                           KRB5_CONF_PROXIABLE, &tempint)) == 0)
-           ;
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_PROXIABLE)
+        tmp = opte->proxiable;
+    else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
+                                        KRB5_CONF_PROXIABLE, &tmp) == 0)
+        ;
     else
-       tempint = 0;
-    if (tempint)
-       request.kdc_options |= KDC_OPT_PROXIABLE;
+        tmp = 0;
+    if (tmp)
+        ctx->request->kdc_options |= KDC_OPT_PROXIABLE;
 
     /* canonicalize */
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE))
-       tempint = 1;
-    else if ((ret = krb5_libdefault_boolean(context, &client->realm,
-                                           KRB5_CONF_CANONICALIZE, &tempint)) == 0)
-       ;
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_CANONICALIZE)
+        tmp = 1;
+    else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
+                                        KRB5_CONF_CANONICALIZE, &tmp) == 0)
+        ;
     else
-       tempint = 0;
-    if (tempint)
-       request.kdc_options |= KDC_OPT_CANONICALIZE;
+        tmp = 0;
+    if (tmp)
+        ctx->request->kdc_options |= KDC_OPT_CANONICALIZE;
 
     /* allow_postdate */
-    
-    if (start_time > 0)
-       request.kdc_options |= (KDC_OPT_ALLOW_POSTDATE|KDC_OPT_POSTDATED);
-    
+    if (ctx->start_time > 0)
+        ctx->request->kdc_options |= KDC_OPT_ALLOW_POSTDATE | KDC_OPT_POSTDATED;
+
     /* ticket lifetime */
-    
-    if ((ret = krb5_timeofday(context, &request.from)))
-       goto cleanup;
-    request.from = krb5int_addint32(request.from, start_time);
-    
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)) {
-        tkt_life = options->tkt_life;
-    } else if ((ret = krb5_libdefault_string(context, &client->realm,
-                                            KRB5_CONF_TICKET_LIFETIME, &tempstr))
-              == 0) {
-       ret = krb5_string_to_deltat(tempstr, &tkt_life);
-       free(tempstr);
-       if (ret) {
-           goto cleanup;
-       }
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_TKT_LIFE)
+        ctx->tkt_life = options->tkt_life;
+    else if (krb5int_libdefault_string(context, &ctx->request->client->realm,
+                                       KRB5_CONF_TICKET_LIFETIME, &str) == 0) {
+        code = krb5_string_to_deltat(str, &ctx->tkt_life);
+        if (code != 0)
+            goto cleanup;
+        free(str);
+        str = NULL;
+    } else
+        ctx->tkt_life = 24 * 60 * 60; /* previously hardcoded in kinit */
+
+    /* renewable lifetime */
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)
+        ctx->renew_life = options->renew_life;
+    else if (krb5int_libdefault_string(context, &ctx->request->client->realm,
+                                       KRB5_CONF_RENEW_LIFETIME, &str) == 0) {
+        code = krb5_string_to_deltat(str, &ctx->renew_life);
+        if (code != 0)
+            goto cleanup;
+        free(str);
+        str = NULL;
+    } else
+        ctx->renew_life = 0;
+
+    if (ctx->renew_life > 0)
+        ctx->request->kdc_options |= KDC_OPT_RENEWABLE;
+
+    /* enctypes */
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST) {
+        ctx->request->ktype =
+            k5alloc((opte->etype_list_length * sizeof(krb5_enctype)),
+                    &code);
+        if (code != 0)
+            goto cleanup;
+        ctx->request->nktypes = opte->etype_list_length;
+        memcpy(ctx->request->ktype, opte->etype_list,
+               ctx->request->nktypes * sizeof(krb5_enctype));
+    } else if (krb5_get_default_in_tkt_ktypes(context,
+                                              &ctx->request->ktype) == 0) {
+        ctx->request->nktypes = krb5int_count_etypes(ctx->request->ktype);
     } else {
-       /* this used to be hardcoded in kinit.c */
-       tkt_life = 24*60*60;
+        /* there isn't any useful default here. */
+        code = KRB5_CONFIG_ETYPE_NOSUPP;
+        goto cleanup;
     }
-    request.till = krb5int_addint32(request.from, tkt_life);
-    
-    /* renewable lifetime */
-    
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_RENEW_LIFE)) {
-       renew_life = options->renew_life;
-    } else if ((ret = krb5_libdefault_string(context, &client->realm,
-                                            KRB5_CONF_RENEW_LIFETIME, &tempstr))
-              == 0) {
-       ret = krb5_string_to_deltat(tempstr, &renew_life);
-       free(tempstr);
-       if (ret) {
-           goto cleanup;
-       }
+
+    /*
+     * Set a default enctype for optimistic preauth.  If we're not doing
+     * optimistic preauth, this should ordinarily get overwritten when we
+     * process the etype-info2 of the preauth-required error.
+     */
+    if (ctx->request->nktypes > 0)
+        ctx->etype = ctx->request->ktype[0];
+
+    /* addresess */
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST) {
+        code = krb5_copy_addresses(context, opte->address_list,
+                                   &ctx->request->addresses);
+        if (code != 0)
+            goto cleanup;
+    } else if (krb5int_libdefault_boolean(context, &ctx->request->client->realm,
+                                          KRB5_CONF_NOADDRESSES, &tmp) != 0
+               || tmp) {
+        ctx->request->addresses = NULL;
     } else {
-       renew_life = 0;
+        code = krb5_os_localaddr(context, &ctx->request->addresses);
+        if (code != 0)
+            goto cleanup;
     }
-    if (renew_life > 0)
-       request.kdc_options |= KDC_OPT_RENEWABLE;
-    
-    if (renew_life > 0) {
-       request.rtime = krb5int_addint32(request.from, renew_life);
-        if (request.rtime < request.till) {
-            /* don't ask for a smaller renewable time than the lifetime */
-            request.rtime = request.till;
-        }
-        /* we are already asking for renewable tickets so strip this option */
-       request.kdc_options &= ~(KDC_OPT_RENEWABLE_OK);
+
+    if (opte->flags & KRB5_GET_INIT_CREDS_OPT_SALT) {
+        code = krb5int_copy_data_contents(context, opte->salt, &ctx->salt);
+        if (code != 0)
+            goto cleanup;
+        ctx->default_salt = FALSE;
     } else {
-       request.rtime = 0;
+        ctx->salt = empty_data();
+        ctx->default_salt = TRUE;
     }
-    
-    /* client */
 
-    request.client = client;
+    /* Anonymous. */
+    if(opte->flags & KRB5_GET_INIT_CREDS_OPT_ANONYMOUS) {
+        ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS;
+        /* Remap @REALM to WELLKNOWN/ANONYMOUS@REALM. */
+        if (client->length == 1 && client->data[0].length ==0) {
+            krb5_principal new_client;
+            code = krb5_build_principal_ext(context, &new_client,
+                                            client->realm.length,
+                                            client->realm.data,
+                                            strlen(KRB5_WELLKNOWN_NAMESTR),
+                                            KRB5_WELLKNOWN_NAMESTR,
+                                            strlen(KRB5_ANONYMOUS_PRINCSTR),
+                                            KRB5_ANONYMOUS_PRINCSTR,
+                                            0);
+            if (code)
+                goto cleanup;
+            krb5_free_principal(context, ctx->request->client);
+            ctx->request->client = new_client;
+            krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN;
+        }
+    }
+    /* We will also handle anonymous if the input principal is the anonymous
+     * principal. */
+    if (krb5_principal_compare_any_realm(context, ctx->request->client,
+                                         krb5_anonymous_principal())) {
+        ctx->request->kdc_options |= KDC_OPT_REQUEST_ANONYMOUS;
+        krb5_princ_type(context, ctx->request->client) = KRB5_NT_WELLKNOWN;
+    }
+    code = restart_init_creds_loop(context, ctx, NULL);
+    if (code)
+        goto cleanup;
 
-    /* per referrals draft, enterprise principals imply canonicalization */
-    canon_flag = ((request.kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
-       client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
+    *pctx = ctx;
+    ctx = NULL;
 
-    /* service */
-    
-    if (in_tkt_service) {
-       /* this is ugly, because so are the data structures involved.  I'm
-          in the library, so I'm going to manipulate the data structures
-          directly, otherwise, it will be worse. */
-
-        if ((ret = krb5_parse_name(context, in_tkt_service, &request.server)))
-           goto cleanup;
-
-       /* stuff the client realm into the server principal.
-          realloc if necessary */
-       if (request.server->realm.length < request.client->realm.length) {
-           char *p = realloc(request.server->realm.data,
-                             request.client->realm.length);
-           if (p == NULL) {
-               ret = ENOMEM;
-               goto cleanup;
-           }
-           request.server->realm.data = p;
-       }
-
-       request.server->realm.length = request.client->realm.length;
-       memcpy(request.server->realm.data, request.client->realm.data,
-              request.client->realm.length);
-    } else {
-       if ((ret = krb5_build_principal_ext(context, &request.server,
-                                          request.client->realm.length,
-                                          request.client->realm.data,
-                                          KRB5_TGS_NAME_SIZE,
-                                          KRB5_TGS_NAME,
-                                          request.client->realm.length,
-                                          request.client->realm.data,
-                                          0)))
-           goto cleanup;
+cleanup:
+    krb5_init_creds_free(context, ctx);
+    free(str);
+
+    return code;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_set_service(krb5_context context,
+                            krb5_init_creds_context ctx,
+                            const char *service)
+{
+    char *s;
+
+    TRACE_INIT_CREDS_SERVICE(context, service);
+
+    s = strdup(service);
+    if (s == NULL)
+        return ENOMEM;
+
+    free(ctx->in_tkt_service);
+    ctx->in_tkt_service = s;
+
+    krb5_preauth_request_context_fini(context);
+    return restart_init_creds_loop(context, ctx, NULL);
+}
+
+static krb5_error_code
+init_creds_validate_reply(krb5_context context,
+                          krb5_init_creds_context ctx,
+                          krb5_data *reply)
+{
+    krb5_error_code code;
+    krb5_error *error = NULL;
+    krb5_kdc_rep *as_reply = NULL;
+
+    krb5_free_error(context, ctx->err_reply);
+    ctx->err_reply = NULL;
+
+    krb5_free_kdc_rep(context, ctx->reply);
+    ctx->reply = NULL;
+
+    if (krb5_is_krb_error(reply)) {
+        code = decode_krb5_error(reply, &error);
+        if (code != 0)
+            return code;
+
+        assert(error != NULL);
+
+        TRACE_INIT_CREDS_ERROR_REPLY(context,
+                                     error->error + ERROR_TABLE_BASE_krb5);
+        if (error->error == KRB_ERR_RESPONSE_TOO_BIG) {
+            krb5_free_error(context, error);
+            return KRB5KRB_ERR_RESPONSE_TOO_BIG;
+        } else {
+            ctx->err_reply = error;
+            return 0;
+        }
     }
 
-    krb5_preauth_request_context_init(context);
+    /*
+     * Check to make sure it isn't a V4 reply.
+     */
+    if (reply->length != 0 && !krb5_is_as_rep(reply)) {
+/* these are in <kerberosIV/prot.h> as well but it isn't worth including. */
+#define V4_KRB_PROT_VERSION     4
+#define V4_AUTH_MSG_ERR_REPLY   (5<<1)
+        /* check here for V4 reply */
+        unsigned int t_switch;
+
+        /* From v4 g_in_tkt.c: This used to be
+           switch (pkt_msg_type(rpkt) & ~1) {
+           but SCO 3.2v4 cc compiled that incorrectly.  */
+        t_switch = reply->data[1];
+        t_switch &= ~1;
+
+        if (t_switch == V4_AUTH_MSG_ERR_REPLY
+            && reply->data[0] == V4_KRB_PROT_VERSION) {
+            code = KRB5KRB_AP_ERR_V4_REPLY;
+        } else {
+            code = KRB5KRB_AP_ERR_MSG_TYPE;
+        }
+        return code;
+    }
 
+    /* It must be a KRB_AS_REP message, or an bad returned packet */
+    code = decode_krb5_as_rep(reply, &as_reply);
+    if (code != 0)
+        return code;
 
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST)) {
-       request.ktype = options->etype_list;
-       request.nktypes = options->etype_list_length;
-    } else if ((ret = krb5_get_default_in_tkt_ktypes(context,
-                                                    &request.ktype)) == 0) {
-       for (request.nktypes = 0;
-            request.ktype[request.nktypes];
-            request.nktypes++)
-           ;
-    } else {
-       /* there isn't any useful default here.  ret is set from above */
-       goto cleanup;
+    if (as_reply->msg_type != KRB5_AS_REP) {
+        krb5_free_kdc_rep(context, as_reply);
+        return KRB5KRB_AP_ERR_MSG_TYPE;
     }
 
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST)) {
-       request.addresses = options->address_list;
+    ctx->reply = as_reply;
+
+    return 0;
+}
+
+static krb5_error_code
+init_creds_step_request(krb5_context context,
+                        krb5_init_creds_context ctx,
+                        krb5_data *out)
+{
+    krb5_error_code code;
+    krb5_boolean got_real;
+    char random_buf[4];
+    krb5_data random_data;
+
+    if (ctx->loopcount >= MAX_IN_TKT_LOOPS) {
+        code = KRB5_GET_IN_TKT_LOOP;
+        goto cleanup;
     }
-    /* it would be nice if this parsed out an address list, but
-       that would be work. */
-    else if (((ret = krb5_libdefault_boolean(context, &client->realm,
-                                           KRB5_CONF_NOADDRESSES, &tempint)) != 0)
-            || (tempint == 1)) {
-           ;
+    /*
+     * RFC 6113 requires a new nonce for the inner request on each try. It's
+     * permitted to change the nonce even for non-FAST so we do here.
+     */
+    random_data.length = 4;
+    random_data.data = (char *)random_buf;
+    code = krb5_c_random_make_octets(context, &random_data);
+    if (code !=0)
+        goto cleanup;
+    /*
+     * See RT ticket 3196 at MIT.  If we set the high bit, we may have
+     * compatibility problems with Heimdal, because we (incorrectly) encode
+     * this value as signed.
+     */
+    ctx->request->nonce = 0x7fffffff & load_32_n(random_buf);
+    krb5_free_data(context, ctx->inner_request_body);
+    ctx->inner_request_body = NULL;
+    code = encode_krb5_kdc_req_body(ctx->request, &ctx->inner_request_body);
+    if (code)
+        goto cleanup;
+
+    if (ctx->err_reply == NULL) {
+        /* either our first attempt, or retrying after PREAUTH_NEEDED */
+        code = krb5_do_preauth(context,
+                               ctx->request,
+                               ctx->inner_request_body,
+                               ctx->encoded_previous_request,
+                               ctx->preauth_to_use,
+                               &ctx->request->padata,
+                               ctx->prompter,
+                               ctx->prompter_data,
+                               &ctx->preauth_rock,
+                               ctx->opte,
+                               &got_real);
+        if (code == 0 && !got_real && ctx->preauth_required)
+            code = KRB5_PREAUTH_FAILED;
+        if (code != 0)
+            goto cleanup;
     } else {
-       if ((ret = krb5_os_localaddr(context, &request.addresses)))
-           goto cleanup;
+        if (ctx->preauth_to_use != NULL) {
+            /*
+             * Retry after an error other than PREAUTH_NEEDED,
+             * using ctx->err_padata to figure out what to change.
+             */
+            code = krb5_do_preauth_tryagain(context,
+                                            ctx->request,
+                                            ctx->inner_request_body,
+                                            ctx->encoded_previous_request,
+                                            ctx->preauth_to_use,
+                                            &ctx->request->padata,
+                                            ctx->err_reply,
+                                            ctx->err_padata,
+                                            ctx->prompter,
+                                            ctx->prompter_data,
+                                            &ctx->preauth_rock,
+                                            ctx->opte);
+        } else {
+            /* No preauth supplied, so can't query the plugins. */
+            code = KRB5KRB_ERR_GENERIC;
+        }
+        if (code != 0) {
+            /* couldn't come up with anything better */
+            code = ctx->err_reply->error + ERROR_TABLE_BASE_krb5;
+            goto cleanup;
+        }
     }
 
-    request.authorization_data.ciphertext.length = 0;
-    request.authorization_data.ciphertext.data = 0;
-    request.unenc_authdata = 0;
-    request.second_ticket = 0;
+    if (ctx->encoded_previous_request != NULL) {
+        krb5_free_data(context, ctx->encoded_previous_request);
+        ctx->encoded_previous_request = NULL;
+    }
+    if (ctx->request->padata)
+        ctx->sent_nontrivial_preauth = 1;
+    if (ctx->enc_pa_rep_permitted)
+        code = request_enc_pa_rep(&ctx->request->padata);
+    if (code)
+        goto cleanup;
+    code = krb5int_fast_prep_req(context, ctx->fast_state,
+                                 ctx->request, ctx->outer_request_body,
+                                 encode_krb5_as_req,
+                                 &ctx->encoded_previous_request);
+    if (code != 0)
+        goto cleanup;
 
-    /* set up the other state.  */
+    code = krb5int_copy_data_contents(context,
+                                      ctx->encoded_previous_request,
+                                      out);
+    if (code != 0)
+        goto cleanup;
 
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_PREAUTH_LIST)) {
-       if ((ret = make_preauth_list(context, options->preauth_list,
-                                    options->preauth_list_length,
-                                    &preauth_to_use)))
-           goto cleanup;
+cleanup:
+    krb5_free_pa_data(context, ctx->request->padata);
+    ctx->request->padata = NULL;
+    return code;
+}
+
+/*
+ * The control flow is complicated.  In order to switch from non-FAST mode to
+ * FAST mode, we need to reset our pre-authentication state.  FAST negotiation
+ * attempts to make sure we rarely have to do this.  When FAST negotiation is
+ * working, we record whether FAST is available when we obtain an armor ticket;
+ * if so, we start out with FAST enabled .  There are two complicated
+ * situations.
+ *
+ * First, if we get a PREAUTH_REQUIRED error including PADATA_FX_FAST back from
+ * a KDC in a case where we were not expecting to use FAST, and we have an
+ * armor ticket available, then we want to use FAST.  That involves clearing
+ * out the pre-auth state, reinitializing the plugins and trying again with an
+ * armor key.
+ *
+ * Secondly, using the negotiation can cause problems with some older KDCs.
+ * Negotiation involves including a special padata item.  Some KDCs, including
+ * MIT prior to 1.7, will return PREAUTH_FAILED rather than PREAUTH_REQUIRED in
+ * pre-authentication is required and unknown padata are included in the
+ * request.  To make matters worse, these KDCs typically do not include a list
+ * of padata in PREAUTH_FAILED errors.  So, if we get PREAUTH_FAILED and we
+ * generated no pre-authentication other than the negotiation then we want to
+ * retry without negotiation.  In this case it is probably also desirable to
+ * retry with the preauth plugin state cleared.
+ *
+ * In all these cases we should not start over more than once.  Control flow is
+ * managed by several variables.
+ *
+ *   sent_nontrivial_preauth: if true, we sent preauth other than negotiation;
+ *   no restart on PREAUTH_FAILED
+ *
+ *   KRB5INT_FAST_ARMOR_AVAIL: fast_state_flag if desired we could generate
+ *   armor; if not set, then we can't use FAST even if the KDC wants to.
+ *
+ *   have_restarted: true if we've already restarted
+ */
+static krb5_boolean
+negotiation_requests_restart(krb5_context context, krb5_init_creds_context ctx,
+                             krb5_pa_data **padata)
+{
+    if (ctx->have_restarted)
+        return FALSE;
+    if (krb5int_upgrade_to_fast_p(context, ctx->fast_state, padata)) {
+        TRACE_INIT_CREDS_RESTART_FAST(context);
+        return TRUE;
+    }
+    if (ctx->err_reply->error == KDC_ERR_PREAUTH_FAILED &&
+        !ctx->sent_nontrivial_preauth) {
+        TRACE_INIT_CREDS_RESTART_PREAUTH_FAILED(context);
+        return TRUE;
     }
+    return FALSE;
+}
 
-    /* the salt is allocated from somewhere, unless it is from the caller,
-       then it is a reference */
+/* Ensure that the reply enctype was among the requested enctypes. */
+static krb5_error_code
+check_reply_enctype(krb5_init_creds_context ctx)
+{
+    int i;
 
-    if (options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT)) {
-       salt = *options->salt;
-    } else {
-       salt.length = SALT_TYPE_AFS_LENGTH;
-       salt.data = NULL;
+    for (i = 0; i < ctx->request->nktypes; i++) {
+        if (ctx->request->ktype[i] == ctx->reply->enc_part.enctype)
+            return 0;
     }
+    return KRB5_CONFIG_ETYPE_NOSUPP;
+}
 
+/* Note the difference between the KDC's time, as reported to us in a
+ * preauth-required error, and the current time. */
+static void
+note_req_timestamp(krb5_context kcontext, krb5_clpreauth_rock rock,
+                   krb5_timestamp kdc_time, krb5_int32 kdc_usec)
+{
+    krb5_timestamp now;
+    krb5_int32 usec;
+
+    if (k5_time_with_offset(0, 0, &now, &usec) != 0)
+        return;
+    rock->pa_offset = kdc_time - now;
+    rock->pa_offset_usec = kdc_usec - usec;
+    rock->pa_offset_state = (rock->fast_state->armor_key != NULL) ?
+        AUTH_OFFSET : UNAUTH_OFFSET;
+}
 
-    /* set the request nonce */
-    if ((ret = krb5_timeofday(context, &time_now)))
-       goto cleanup;
-    /*
-     * XXX we know they are the same size... and we should do
-     * something better than just the current time
-     */
-    {
-       unsigned char random_buf[4];
-       krb5_data random_data;
-
-       random_data.length = 4;
-       random_data.data = (char *)random_buf;
-       if (krb5_c_random_make_octets(context, &random_data) == 0)
-           /* See RT ticket 3196 at MIT.  If we set the high bit, we
-              may have compatibility problems with Heimdal, because
-              we (incorrectly) encode this value as signed.  */
-           request.nonce = 0x7fffffff & load_32_n(random_buf);
-       else
-           /* XXX  Yuck.  Old version.  */
-           request.nonce = (krb5_int32) time_now;
-    }
-    /* give the preauth plugins a chance to prep the request body */
-    krb5_preauth_prepare_request(context, options, &request);
-    ret = encode_krb5_kdc_req_body(&request, &encoded_request_body);
-    if (ret)
+static krb5_error_code
+init_creds_step_reply(krb5_context context,
+                      krb5_init_creds_context ctx,
+                      krb5_data *in)
+{
+    krb5_error_code code;
+    krb5_pa_data **kdc_padata = NULL;
+    krb5_boolean retry = FALSE;
+    int canon_flag = 0;
+    krb5_keyblock *strengthen_key = NULL;
+    krb5_keyblock encrypting_key;
+    krb5_boolean fast_avail, got_real;
+
+    encrypting_key.length = 0;
+    encrypting_key.contents = NULL;
+
+    /* process previous KDC response */
+    code = init_creds_validate_reply(context, ctx, in);
+    if (code != 0)
         goto cleanup;
 
-    get_data_rock.magic = CLIENT_ROCK_MAGIC;
-    get_data_rock.as_reply = NULL;
-
-    /* now, loop processing preauth data and talking to the kdc */
-    for (loopcount = 0; loopcount < MAX_IN_TKT_LOOPS; loopcount++) {
-       if (request.padata) {
-           krb5_free_pa_data(context, request.padata);
-           request.padata = NULL;
-       }
-       if (!err_reply) {
-            /* either our first attempt, or retrying after PREAUTH_NEEDED */
-           if ((ret = krb5_do_preauth(context,
-                                      &request,
-                                      encoded_request_body,
-                                      encoded_previous_request,
-                                      preauth_to_use, &request.padata,
-                                      &salt, &s2kparams, &etype, &as_key,
-                                      prompter, prompter_data,
-                                      gak_fct, gak_data,
-                                      &get_data_rock, options)))
-               goto cleanup;
-       } else {
-           if (preauth_to_use != NULL) {
-               /*
-                * Retry after an error other than PREAUTH_NEEDED,
-                * using e-data to figure out what to change.
-                */
-               ret = krb5_do_preauth_tryagain(context,
-                                              &request,
-                                              encoded_request_body,
-                                              encoded_previous_request,
-                                              preauth_to_use, &request.padata,
-                                              err_reply,
-                                              &salt, &s2kparams, &etype,
-                                              &as_key,
-                                              prompter, prompter_data,
-                                              gak_fct, gak_data,
-                                              &get_data_rock, options);
-           } else {
-               /* No preauth supplied, so can't query the plug-ins. */
-               ret = KRB5KRB_ERR_GENERIC;
-           }
-           if (ret) {
-               /* couldn't come up with anything better */
-               ret = err_reply->error + ERROR_TABLE_BASE_krb5;
-           }
-           krb5_free_error(context, err_reply);
-           err_reply = NULL;
-           if (ret)
-               goto cleanup;
-       }
-
-        if (encoded_previous_request != NULL) {
-           krb5_free_data(context, encoded_previous_request);
-           encoded_previous_request = NULL;
+    /* per referrals draft, enterprise principals imply canonicalization */
+    canon_flag = ((ctx->request->kdc_options & KDC_OPT_CANONICALIZE) != 0) ||
+        ctx->request->client->type == KRB5_NT_ENTERPRISE_PRINCIPAL;
+
+    if (ctx->err_reply != NULL) {
+        code = krb5int_fast_process_error(context, ctx->fast_state,
+                                          &ctx->err_reply, &ctx->err_padata,
+                                          &retry);
+        if (code != 0)
+            goto cleanup;
+        if (negotiation_requests_restart(context, ctx, ctx->err_padata)) {
+            ctx->have_restarted = 1;
+            krb5_preauth_request_context_fini(context);
+            if ((ctx->fast_state->fast_state_flags & KRB5INT_FAST_DO_FAST) ==0)
+                ctx->enc_pa_rep_permitted = 0;
+            code = restart_init_creds_loop(context, ctx, ctx->err_padata);
+            krb5_free_error(context, ctx->err_reply);
+            ctx->err_reply = NULL;
+            krb5_free_pa_data(context, ctx->err_padata);
+            ctx->err_padata = NULL;
+        } else if (ctx->err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
+                   retry) {
+            /* reset the list of preauth types to try */
+            krb5_free_pa_data(context, ctx->preauth_to_use);
+            ctx->preauth_to_use = ctx->err_padata;
+            ctx->err_padata = NULL;
+            note_req_timestamp(context, &ctx->preauth_rock,
+                               ctx->err_reply->stime, ctx->err_reply->susec);
+            /* this will trigger a new call to krb5_do_preauth() */
+            krb5_free_error(context, ctx->err_reply);
+            ctx->err_reply = NULL;
+            code = sort_krb5_padata_sequence(context,
+                                             &ctx->request->client->realm,
+                                             ctx->preauth_to_use);
+            ctx->preauth_required = TRUE;
+
+        } else if (canon_flag && ctx->err_reply->error == KDC_ERR_WRONG_REALM) {
+            if (ctx->err_reply->client == NULL ||
+                !krb5_princ_realm(context, ctx->err_reply->client)->length) {
+                code = KRB5KDC_ERR_WRONG_REALM;
+                goto cleanup;
+            }
+            TRACE_INIT_CREDS_REFERRAL(context, &ctx->err_reply->client->realm);
+            /* Rewrite request.client with realm from error reply */
+            krb5_free_data_contents(context, &ctx->request->client->realm);
+            code = krb5int_copy_data_contents(context,
+                                              &ctx->err_reply->client->realm,
+                                              &ctx->request->client->realm);
+            /* this will trigger a new call to krb5_do_preauth() */
+            krb5_free_error(context, ctx->err_reply);
+            ctx->err_reply = NULL;
+            krb5_preauth_request_context_fini(context);
+            /* Permit another negotiation based restart. */
+            ctx->have_restarted = 0;
+            ctx->sent_nontrivial_preauth = 0;
+            code = restart_init_creds_loop(context, ctx, NULL);
+            if (code != 0)
+                goto cleanup;
+        } else {
+            if (retry) {
+                code = 0;
+            } else {
+                /* error + no hints = give up */
+                code = (krb5_error_code)ctx->err_reply->error +
+                    ERROR_TABLE_BASE_krb5;
+            }
         }
-        ret = encode_krb5_as_req(&request, &encoded_previous_request);
-       if (ret)
-           goto cleanup;
-
-       err_reply = 0;
-       local_as_reply = 0;
-       if ((ret = send_as_request(context, encoded_previous_request,
-                                  krb5_princ_realm(context, request.client), &err_reply,
-                                  &local_as_reply, use_master)))
-           goto cleanup;
-
-       if (err_reply) {
-           if (err_reply->error == KDC_ERR_PREAUTH_REQUIRED &&
-               err_reply->e_data.length > 0) {
-               /* reset the list of preauth types to try */
-               if (preauth_to_use) {
-                   krb5_free_pa_data(context, preauth_to_use);
-                   preauth_to_use = NULL;
-               }
-               ret = decode_krb5_padata_sequence(&err_reply->e_data,
-                                                 &preauth_to_use);
-               krb5_free_error(context, err_reply);
-               err_reply = NULL;
-               if (ret)
-                   goto cleanup;
-               ret = sort_krb5_padata_sequence(context,
-                                               &request.server->realm,
-                                               preauth_to_use);
-               if (ret)
-                   goto cleanup;
-               /* continue to next iteration */
-           } else if (canon_flag && err_reply->error == KDC_ERR_WRONG_REALM) {
-               if (err_reply->client == NULL ||
-                   err_reply->client->realm.length == 0) {
-                   ret = KRB5KDC_ERR_WRONG_REALM;
-                   krb5_free_error(context, err_reply);
-                   goto cleanup;
-               }
-               /* Rewrite request.client with realm from error reply */
-               if (referred_client.realm.data) {
-                   krb5_free_data_contents(context, &referred_client.realm);
-                   referred_client.realm.data = NULL;
-               }
-               ret = krb5int_copy_data_contents(context,
-                                                &err_reply->client->realm,
-                                                &referred_client.realm);
-               krb5_free_error(context, err_reply);
-               err_reply = NULL;
-               if (ret)
-                   goto cleanup;
-               request.client = &referred_client;
-           } else {
-               if (err_reply->e_data.length > 0) {
-                   /* continue to next iteration */
-               } else {
-                   /* error + no hints = give up */
-                   ret = (krb5_error_code) err_reply->error
-                         + ERROR_TABLE_BASE_krb5;
-                   krb5_free_error(context, err_reply);
-                   goto cleanup;
-               }
-           }
-       } else if (local_as_reply) {
-           break;
-       } else {
-           ret = KRB5KRB_AP_ERR_MSG_TYPE;
-           goto cleanup;
-       }
-    }
 
-#if APPLE_PKINIT
-    inTktDebug("krb5_get_init_creds done with send_as_request loop lc %d\n",
-       (int)loopcount);
-#endif /* APPLE_PKINIT */
-    if (loopcount == MAX_IN_TKT_LOOPS) {
-       ret = KRB5_GET_IN_TKT_LOOP;
-       goto cleanup;
+        /* Return error code, or continue with next iteration */
+        goto cleanup;
     }
 
+    /* We have a response. Process it. */
+    assert(ctx->reply != NULL);
+
+    /* Check for replies (likely forged) with unasked-for enctypes. */
+    code = check_reply_enctype(ctx);
+    if (code != 0)
+        goto cleanup;
+
     /* process any preauth data in the as_reply */
     krb5_clear_preauth_context_use_counts(context);
-    if ((ret = sort_krb5_padata_sequence(context, &request.server->realm,
-                                        local_as_reply->padata)))
-       goto cleanup;
-    get_data_rock.as_reply = local_as_reply;
-    if ((ret = krb5_do_preauth(context,
-                              &request,
-                              encoded_request_body, encoded_previous_request,
-                              local_as_reply->padata, &kdc_padata,
-                              &salt, &s2kparams, &etype, &as_key, prompter,
-                              prompter_data, gak_fct, gak_data,
-                              &get_data_rock, options))) {
-#if APPLE_PKINIT
-        inTktDebug("krb5_get_init_creds krb5_do_preauth returned %d\n", (int)ret);
-#endif /* APPLE_PKINIT */
-       goto cleanup;
-       }
+    code = krb5int_fast_process_response(context, ctx->fast_state,
+                                         ctx->reply, &strengthen_key);
+    if (code != 0)
+        goto cleanup;
+
+    code = sort_krb5_padata_sequence(context, &ctx->request->client->realm,
+                                     ctx->reply->padata);
+    if (code != 0)
+        goto cleanup;
+
+    ctx->etype = ctx->reply->enc_part.enctype;
+
+    code = krb5_do_preauth(context,
+                           ctx->request,
+                           ctx->inner_request_body,
+                           ctx->encoded_previous_request,
+                           ctx->reply->padata,
+                           &kdc_padata,
+                           ctx->prompter,
+                           ctx->prompter_data,
+                           &ctx->preauth_rock,
+                           ctx->opte,
+                           &got_real);
+    if (code != 0)
+        goto cleanup;
+
+    /*
+     * If we haven't gotten a salt from another source yet, set up one
+     * corresponding to the client principal returned by the KDC.  We
+     * could get the same effect by passing local_as_reply->client to
+     * gak_fct below, but that would put the canonicalized client name
+     * in the prompt, which raises issues of needing to sanitize
+     * unprintable characters.  So for now we just let it affect the
+     * salt.  local_as_reply->client will be checked later on in
+     * verify_as_reply.
+     */
+    if (ctx->default_salt) {
+        code = krb5_principal2salt(context, ctx->reply->client, &ctx->salt);
+        TRACE_INIT_CREDS_SALT_PRINC(context, &ctx->salt);
+        if (code != 0)
+            goto cleanup;
+    }
 
     /* XXX For 1.1.1 and prior KDC's, when SAM is used w/ USE_SAD_AS_KEY,
        the AS_REP comes back encrypted in the user's longterm key
        instead of in the SAD. If there was a SAM preauth, there
        will be an as_key here which will be the SAD. If that fails,
        use the gak_fct to get the password, and try again. */
-      
+
     /* XXX because etypes are handled poorly (particularly wrt SAM,
        where the etype is fixed by the kdc), we may want to try
        decrypt_as_reply twice.  If there's an as_key available, try
        it.  If decrypting the as_rep fails, or if there isn't an
        as_key at all yet, then use the gak_fct to get one, and try
        again.  */
-
-    if (as_key.length)
-       ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
-                              NULL, &as_key, krb5_kdc_rep_decrypt_proc,
-                              NULL);
-    else
-       ret = -1;
-          
-    if (ret) {
-       /* if we haven't get gotten a key, get it now */
-
-       if ((ret = ((*gak_fct)(context, request.client,
-                              local_as_reply->enc_part.enctype,
-                              prompter, prompter_data, &salt, &s2kparams,
-                              &as_key, gak_data))))
-           goto cleanup;
-
-       if ((ret = decrypt_as_reply(context, NULL, local_as_reply, NULL,
-                                   NULL, &as_key, krb5_kdc_rep_decrypt_proc,
-                                   NULL)))
-           goto cleanup;
+    if (ctx->as_key.length) {
+        TRACE_INIT_CREDS_AS_KEY_PREAUTH(context, &ctx->as_key);
+        code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
+                                      &encrypting_key);
+        if (code != 0)
+            goto cleanup;
+        code = decrypt_as_reply(context, NULL, ctx->reply, &encrypting_key);
+        if (code != 0)
+            TRACE_INIT_CREDS_PREAUTH_DECRYPT_FAIL(context, code);
+    } else
+        code = -1;
+
+    if (code != 0) {
+        /* if we haven't get gotten a key, get it now */
+        TRACE_INIT_CREDS_GAK(context, &ctx->salt, &ctx->s2kparams);
+        code = (*ctx->gak_fct)(context, ctx->request->client,
+                               ctx->reply->enc_part.enctype,
+                               ctx->prompter, ctx->prompter_data,
+                               &ctx->salt, &ctx->s2kparams,
+                               &ctx->as_key, ctx->gak_data);
+        if (code != 0)
+            goto cleanup;
+        TRACE_INIT_CREDS_AS_KEY_GAK(context, &ctx->as_key);
+
+        code = krb5int_fast_reply_key(context, strengthen_key, &ctx->as_key,
+                                      &encrypting_key);
+        if (code != 0)
+            goto cleanup;
+
+        code = decrypt_as_reply(context, NULL, ctx->reply, &encrypting_key);
+        if (code != 0)
+            goto cleanup;
     }
 
-    if ((ret = verify_as_reply(context, time_now, &request, local_as_reply)))
-       goto cleanup;
+    TRACE_INIT_CREDS_DECRYPTED_REPLY(context, ctx->reply->enc_part2->session);
 
-    /* XXX this should be inside stash_as_reply, but as long as
-       get_in_tkt is still around using that arg as an in/out, I can't
-       do that */
-    memset(creds, 0, sizeof(*creds));
+    code = krb5int_fast_verify_nego(context, ctx->fast_state,
+                                    ctx->reply, ctx->encoded_previous_request,
+                                    &encrypting_key, &fast_avail);
+    if (code)
+        goto cleanup;
+    code = verify_as_reply(context, ctx->request_time,
+                           ctx->request, ctx->reply);
+    if (code != 0)
+        goto cleanup;
+    code = verify_anonymous(context, ctx->request, ctx->reply,
+                            &ctx->as_key);
+    if (code)
+        goto cleanup;
 
-    if ((ret = stash_as_reply(context, time_now, &request, local_as_reply,
-                             creds, NULL)))
-       goto cleanup;
+    code = stash_as_reply(context, ctx->request_time, ctx->request,
+                          ctx->reply, &ctx->cred, NULL);
+    if (code != 0)
+        goto cleanup;
+    if (ctx->opte && ctx->opte->opt_private->out_ccache) {
+        krb5_ccache out_ccache = ctx->opte->opt_private->out_ccache;
+        krb5_data config_data;
+        code = krb5_cc_initialize(context, out_ccache, ctx->cred.client);
+        if (code != 0)
+            goto cc_cleanup;
+        code = krb5_cc_store_cred(context, out_ccache, &ctx->cred);
+        if (code != 0)
+            goto cc_cleanup;
+        if (fast_avail) {
+            config_data.data = "yes";
+            config_data.length = strlen(config_data.data);
+            code = krb5_cc_set_config(context, out_ccache, ctx->cred.server,
+                                      KRB5_CONF_FAST_AVAIL, &config_data);
+        }
+    cc_cleanup:
+        if (code !=0) {
+            const char *msg;
+            msg = krb5_get_error_message(context, code);
+            krb5_set_error_message(context, code,
+                                   _("%s while storing credentials"), msg);
+            krb5_free_error_message(context, msg);
+        }
+    }
+
+    krb5_preauth_request_context_fini(context);
 
     /* success */
+    code = 0;
+    ctx->complete = TRUE;
+
+cleanup:
+    krb5_free_pa_data(context, kdc_padata);
+    krb5_free_keyblock(context, strengthen_key);
+    krb5_free_keyblock_contents(context, &encrypting_key);
+
+    return code;
+}
+
+/*
+ * Do next step of credentials acquisition.
+ *
+ * On success returns 0 or KRB5KRB_ERR_RESPONSE_TOO_BIG if the request
+ * should be sent with TCP.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_init_creds_step(krb5_context context,
+                     krb5_init_creds_context ctx,
+                     krb5_data *in,
+                     krb5_data *out,
+                     krb5_data *realm,
+                     unsigned int *flags)
+{
+    krb5_error_code code = 0, code2;
+
+    *flags = 0;
+
+    out->data = NULL;
+    out->length = 0;
+
+    realm->data = NULL;
+    realm->length = 0;
+
+    if (ctx->complete)
+        return EINVAL;
+
+    if (in->length != 0) {
+        code = init_creds_step_reply(context, ctx, in);
+        if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
+            code2 = krb5int_copy_data_contents(context,
+                                               ctx->encoded_previous_request,
+                                               out);
+            if (code2 != 0) {
+                code = code2;
+                goto cleanup;
+            }
+            goto copy_realm;
+        }
+        if (code != 0 || ctx->complete)
+            goto cleanup;
+    }
+
+    code = init_creds_step_request(context, ctx, out);
+    if (code != 0)
+        goto cleanup;
+
+    /* Only a new request increments the loop count, not a TCP retry */
+    ctx->loopcount++;
 
-    ret = 0;
+copy_realm:
+    assert(ctx->request->server != NULL);
+
+    code2 = krb5int_copy_data_contents(context,
+                                       &ctx->request->server->realm,
+                                       realm);
+    if (code2 != 0) {
+        code = code2;
+        goto cleanup;
+    }
 
 cleanup:
-    if (ret != 0) {
-       char *client_name;
-       /* See if we can produce a more detailed error message.  */
-       switch (ret) {
-       case KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN:
-           client_name = NULL;
-           if (krb5_unparse_name(context, client, &client_name) == 0) {
-               krb5_set_error_message(context, ret,
-                                      "Client '%s' not found in Kerberos database",
-                                      client_name);
-               free(client_name);
-           }
-           break;
-       default:
-           break;
-       }
+    if (code == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN) {
+        char *client_name;
+
+        /* See if we can produce a more detailed error message */
+        code2 = krb5_unparse_name(context, ctx->request->client, &client_name);
+        if (code2 == 0) {
+            krb5_set_error_message(context, code,
+                                   _("Client '%s' not found in Kerberos "
+                                     "database"), client_name);
+            krb5_free_unparsed_name(context, client_name);
+        }
     }
-    krb5_preauth_request_context_fini(context);
-    if (encoded_previous_request != NULL) {
-       krb5_free_data(context, encoded_previous_request);
-       encoded_previous_request = NULL;
+
+    *flags = ctx->complete ? 0 : KRB5_INIT_CREDS_STEP_FLAG_CONTINUE;
+    return code;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5int_get_init_creds(krb5_context context,
+                       krb5_creds *creds,
+                       krb5_principal client,
+                       krb5_prompter_fct prompter,
+                       void *prompter_data,
+                       krb5_deltat start_time,
+                       char *in_tkt_service,
+                       krb5_get_init_creds_opt *options,
+                       krb5_gic_get_as_key_fct gak_fct,
+                       void *gak_data,
+                       int  *use_master,
+                       krb5_kdc_rep **as_reply)
+{
+    krb5_error_code code;
+    krb5_init_creds_context ctx = NULL;
+
+    code = krb5_init_creds_init(context,
+                                client,
+                                prompter,
+                                prompter_data,
+                                start_time,
+                                options,
+                                &ctx);
+    if (code != 0)
+        goto cleanup;
+
+    ctx->gak_fct = gak_fct;
+    ctx->gak_data = gak_data;
+
+    if (in_tkt_service) {
+        code = krb5_init_creds_set_service(context, ctx, in_tkt_service);
+        if (code != 0)
+            goto cleanup;
+    }
+
+    code = k5_init_creds_get(context, ctx, use_master);
+    if (code != 0)
+        goto cleanup;
+
+    code = krb5_init_creds_get_creds(context, ctx, creds);
+    if (code != 0)
+        goto cleanup;
+
+    if (as_reply != NULL) {
+        *as_reply = ctx->reply;
+        ctx->reply = NULL;
+    }
+
+cleanup:
+    krb5_init_creds_free(context, ctx);
+
+    return code;
+}
+
+krb5_error_code
+krb5int_populate_gic_opt(krb5_context context, krb5_get_init_creds_opt **out,
+                         krb5_flags options, krb5_address *const *addrs,
+                         krb5_enctype *ktypes,
+                         krb5_preauthtype *pre_auth_types, krb5_creds *creds)
+{
+    int i;
+    krb5_int32 starttime;
+    krb5_get_init_creds_opt *opt;
+    krb5_error_code retval;
+
+    *out = NULL;
+    retval = krb5_get_init_creds_opt_alloc(context, &opt);
+    if (retval)
+        return(retval);
+
+    if (addrs)
+        krb5_get_init_creds_opt_set_address_list(opt, (krb5_address **) addrs);
+    if (ktypes) {
+        i = krb5int_count_etypes(ktypes);
+        if (i)
+            krb5_get_init_creds_opt_set_etype_list(opt, ktypes, i);
+    }
+    if (pre_auth_types) {
+        for (i=0; pre_auth_types[i]; i++);
+        if (i)
+            krb5_get_init_creds_opt_set_preauth_list(opt, pre_auth_types, i);
     }
-    if (encoded_request_body != NULL) {
-       krb5_free_data(context, encoded_request_body);
-       encoded_request_body = NULL;
+    if (options&KDC_OPT_FORWARDABLE)
+        krb5_get_init_creds_opt_set_forwardable(opt, 1);
+    else krb5_get_init_creds_opt_set_forwardable(opt, 0);
+    if (options&KDC_OPT_PROXIABLE)
+        krb5_get_init_creds_opt_set_proxiable(opt, 1);
+    else krb5_get_init_creds_opt_set_proxiable(opt, 0);
+    if (creds && creds->times.endtime) {
+        retval = krb5_timeofday(context, &starttime);
+        if (retval)
+            goto cleanup;
+        if (creds->times.starttime) starttime = creds->times.starttime;
+        krb5_get_init_creds_opt_set_tkt_life(opt, creds->times.endtime - starttime);
     }
-    if (request.server)
-       krb5_free_principal(context, request.server);
-    if (request.ktype &&
-       (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_ETYPE_LIST))))
-       free(request.ktype);
-    if (request.addresses &&
-       (!(options &&
-          (options->flags & KRB5_GET_INIT_CREDS_OPT_ADDRESS_LIST))))
-       krb5_free_addresses(context, request.addresses);
-    if (preauth_to_use)
-       krb5_free_pa_data(context, preauth_to_use);
-    if (kdc_padata)
-       krb5_free_pa_data(context, kdc_padata);
-    if (request.padata)
-       krb5_free_pa_data(context, request.padata);
-    if (as_key.length)
-       krb5_free_keyblock_contents(context, &as_key);
-    if (salt.data &&
-       (!(options && (options->flags & KRB5_GET_INIT_CREDS_OPT_SALT))))
-       free(salt.data);
-    krb5_free_data_contents(context, &s2kparams);
-    if (as_reply)
-       *as_reply = local_as_reply;
-    else if (local_as_reply)
-       krb5_free_kdc_rep(context, local_as_reply);
-    if (referred_client.realm.data)
-       krb5_free_data_contents(context, &referred_client.realm);
-
-    return(ret);
+    *out = opt;
+    return 0;
+
+cleanup:
+    krb5_get_init_creds_opt_free(context, opt);
+    return retval;
 }