From e6377c106423f2fae965fcfabdb228e9bf97811e Mon Sep 17 00:00:00 2001
From: Greg Hudson <ghudson@mit.edu>
Date: Wed, 14 Apr 2010 14:36:32 +0000
Subject: [PATCH 111/120] Introduce new krb5_tkt_creds API

Merged from branches/iakerb: add new asynchronous krb5_tkt_creds APIs,
which allow a caller to take responsibility for transporting requests
to the KDC and getting responses back.  Rewrite the existing
krb5_get_credentials API in terms of the new functions.  Get rid of
krb5_get_cred_from_kdc and friends, since they are no longer used.

ticket: 6700

git-svn-id: svn://anonsvn.mit.edu/krb5/trunk@23900 dc483132-0cff-0310-8789-dd5450dbe970
---
 src/include/k5-int.h          |    9 -
 src/include/krb5/krb5.hin     |   96 +++
 src/lib/krb5/krb/Makefile.in  |    3 -
 src/lib/krb5/krb/gc_frm_kdc.c | 1397 -----------------------------------------
 src/lib/krb5/krb/get_creds.c  | 1180 +++++++++++++++++++++++++++++++---
 src/lib/krb5/krb/int-proto.h  |    5 -
 src/lib/krb5/libkrb5.exports  |    8 +-
 7 files changed, 1206 insertions(+), 1492 deletions(-)
 delete mode 100644 src/lib/krb5/krb/gc_frm_kdc.c

diff --git a/src/include/k5-int.h b/src/include/k5-int.h
index 43b780e..5dc2d49 100644
--- a/src/include/k5-int.h
+++ b/src/include/k5-int.h
@@ -2576,15 +2576,6 @@ krb5_error_code krb5_kdc_rep_decrypt_proc(krb5_context, const krb5_keyblock *,
 krb5_error_code KRB5_CALLCONV krb5_decrypt_tkt_part(krb5_context,
                                                     const krb5_keyblock *,
                                                     krb5_ticket * );
-krb5_error_code krb5_get_cred_from_kdc(krb5_context, krb5_ccache,
-                                       krb5_creds *, krb5_creds **,
-                                       krb5_creds *** );
-krb5_error_code krb5_get_cred_from_kdc_validate(krb5_context, krb5_ccache,
-                                                krb5_creds *, krb5_creds **,
-                                                krb5_creds *** );
-krb5_error_code krb5_get_cred_from_kdc_renew(krb5_context, krb5_ccache,
-                                             krb5_creds *, krb5_creds **,
-                                             krb5_creds *** );
 
 krb5_error_code krb5_get_cred_via_tkt(krb5_context, krb5_creds *, krb5_flags,
                                       krb5_address *const *, krb5_creds *,
diff --git a/src/include/krb5/krb5.hin b/src/include/krb5/krb5.hin
index 740390c..945d424 100644
--- a/src/include/krb5/krb5.hin
+++ b/src/include/krb5/krb5.hin
@@ -2415,6 +2415,102 @@ krb5_error_code KRB5_CALLCONV
 krb5_init_creds_get_times(krb5_context context, krb5_init_creds_context ctx,
                           krb5_ticket_times *times);
 
+struct _krb5_tkt_creds_context;
+typedef struct _krb5_tkt_creds_context *krb5_tkt_creds_context;
+
+/**
+ * Create a context to get credentials from a KDC's Ticket Granting Service.
+ *
+ * The resulting TGS acquisition context can be used asynchronously with
+ * krb5_tkt_creds_step() or synchronously with krb5_tkt_creds_get().  See also
+ * krb5_get_credentials() for synchrous use.
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ccache   A credentials cache containing the desired credentials
+ *                      or a Ticket Granting Ticket (TGT) for the client realm.
+ *                      TGT and service credentials may be stored into this
+ *                      cache as they are acquired.
+ * @param[in]  options  KRB5_GC_* options for this request.
+ * @param[out] ctx      The TGS acquisition context.
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache,
+                    krb5_creds *creds, krb5_flags options,
+                    krb5_tkt_creds_context *ctx);
+
+/**
+ * Synchronously obtain credentials within an acquisition context.
+ *
+ * This function repeatedly generates requests, sends them to the appropriate
+ * realms' KDCs, and processes the replies until credentials are available for
+ * retrieval with krb5_tkt_creds_get_creds().
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ctx      A TGS acquisition context (see krb5_tkt_creds_init())
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get(krb5_context context, krb5_tkt_creds_context ctx);
+
+/**
+ * Retrieve credentials from an acquisition context, filling in @a creds.
+ *
+ * The acquisition context must have completed obtaining credentials via either
+ * krb5_tkt_creds_get() or krb5_tkt_creds_step().
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ctx      A TGS acquisition context (see krb5_tkt_creds_init())
+ * @param[out] creds    The acquired credentials
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_creds *creds);
+
+/**
+ * Release the resources used by an acquisition context.
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ctx      A TGS acquisition context (see krb5_tkt_creds_init())
+ */
+void KRB5_CALLCONV
+krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx);
+
+#define KRB5_TKT_CREDS_CONTINUE 1  /* Another KDC response is needed. */
+
+/**
+ * Process a response and generate the next request to acquire credentials.
+ *
+ * On the first call, @a in should be empty or NULL.  If more responses are
+ * needed, the @a flags output parameter will contain @a
+ * KRB5_TKT_CREDS_CONTINUE.  In this case, the caller must transport @a out to
+ * a KDC for @a realm and receive a response, which should be provided as @a
+ * in to the next call.
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ctx      A TGS acquisition context (see krb5_tkt_creds_init())
+ * @param[in]  in       The last response (empty or NULL for first call)
+ * @param[out] out      The next request to be sent
+ * @param[out] realm    The realm to which the next request should be sent
+ * @param[out] flags    Indicates whether more responses are needed
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx,
+                    krb5_data *in, krb5_data *out, krb5_data *realm,
+                    unsigned int *flags);
+
+/**
+ * Retrieve ticket times for obtained credentials, filling in @a times.
+ *
+ * The acquisition context must have completed obtaining credentials via either
+ * krb5_tkt_creds_get() or krb5_tkt_creds_step().
+ *
+ * @param[in]  context  A krb5 library context (see krb5_init_context())
+ * @param[in]  ctx      A TGS acquisition context (see krb5_tkt_creds_init())
+ * @param[out] times    Ticket times for the acquired credentials
+ */
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_ticket_times *times);
+
 krb5_error_code KRB5_CALLCONV
 krb5_get_init_creds_keytab(krb5_context context, krb5_creds *creds,
                            krb5_principal client, krb5_keytab arg_keytab,
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in
index deeb43b..54337da 100644
--- a/src/lib/krb5/krb/Makefile.in
+++ b/src/lib/krb5/krb/Makefile.in
@@ -44,7 +44,6 @@ STLIBOBJS= \
 	etype_list.o	\
 	fast.o \
 	fwd_tgt.o	\
-	gc_frm_kdc.o	\
 	gc_via_tkt.o	\
 	gen_seqnum.o	\
 	gen_subkey.o	\
@@ -139,7 +138,6 @@ OBJS=	$(OUTPRE)addr_comp.$(OBJEXT)	\
 	$(OUTPRE)etype_list.$(OBJEXT)	\
 	$(OUTPRE)fast.$(OBJEXT) \
 	$(OUTPRE)fwd_tgt.$(OBJEXT)	\
-	$(OUTPRE)gc_frm_kdc.$(OBJEXT)	\
 	$(OUTPRE)gc_via_tkt.$(OBJEXT)	\
 	$(OUTPRE)gen_seqnum.$(OBJEXT)	\
 	$(OUTPRE)gen_subkey.$(OBJEXT)	\
@@ -235,7 +233,6 @@ SRCS=	$(srcdir)/addr_comp.c	\
 	$(srcdir)/etype_list.c	\
 	$(srcdir)/fast.c \
 	$(srcdir)/fwd_tgt.c	\
-	$(srcdir)/gc_frm_kdc.c	\
 	$(srcdir)/gc_via_tkt.c	\
 	$(srcdir)/gen_seqnum.c	\
 	$(srcdir)/gen_subkey.c	\
diff --git a/src/lib/krb5/krb/gc_frm_kdc.c b/src/lib/krb5/krb/gc_frm_kdc.c
deleted file mode 100644
index 30e1c40..0000000
--- a/src/lib/krb5/krb/gc_frm_kdc.c
+++ /dev/null
@@ -1,1397 +0,0 @@
-/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
-/*
- * Copyright (c) 1994,2003,2005,2007 by the Massachusetts Institute of Technology.
- * Copyright (c) 1994 CyberSAFE Corporation
- * Copyright (c) 1993 Open Computing Security Group
- * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology.
- * All Rights Reserved.
- *
- * Export of this software from the United States of America may
- *   require a specific license from the United States Government.
- *   It is the responsibility of any person or organization contemplating
- *   export to obtain such a license before exporting.
- *
- * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
- * distribute this software and its documentation for any purpose and
- * without fee is hereby granted, provided that the above copyright
- * notice appear in all copies and that both that copyright notice and
- * this permission notice appear in supporting documentation, and that
- * the name of M.I.T. not be used in advertising or publicity pertaining
- * to distribution of the software without specific, written prior
- * permission.  Furthermore if you modify this software you must label
- * your software as modified software and not distribute it in such a
- * fashion that it might be confused with the original M.I.T. software.
- * Neither M.I.T., the Open Computing Security Group, nor
- * CyberSAFE Corporation make any representations about the suitability of
- * this software for any purpose.  It is provided "as is" without express
- * or implied warranty.
- *
- * krb5_get_cred_from_kdc() and related functions:
- *
- * Get credentials from some KDC somewhere, possibly accumulating TGTs
- * along the way.
- */
-
-#include "k5-int.h"
-#include <stdio.h>
-#include "int-proto.h"
-
-struct tr_state;
-
-/*
- * Ring buffer abstraction for TGTs returned from a ccache; avoids
- * lots of excess copying.
- */
-
-#define NCC_TGTS 2
-struct cc_tgts {
-    krb5_creds cred[NCC_TGTS];
-    int dirty[NCC_TGTS];
-    unsigned int cur, nxt;
-};
-
-/* NOTE: This only checks if NXT_TGT is CUR_CC_TGT. */
-#define NXT_TGT_IS_CACHED(ts)                   \
-    ((ts)->nxt_tgt == (ts)->cur_cc_tgt)
-
-#define MARK_CUR_CC_TGT_CLEAN(ts)                       \
-    do {                                                \
-        (ts)->cc_tgts.dirty[(ts)->cc_tgts.cur] = 0;     \
-    } while (0)
-
-static void init_cc_tgts(struct tr_state *);
-static void shift_cc_tgts(struct tr_state *);
-static void clean_cc_tgts(struct tr_state *);
-
-/*
- * State struct for do_traversal() and helpers.
- *
- * CUR_TGT and NXT_TGT can each point either into CC_TGTS or into
- * KDC_TGTS.
- *
- * CUR_TGT is the "working" TGT, which will be used to obtain new
- * TGTs.  NXT_TGT will be CUR_TGT for the next iteration of the loop.
- *
- * Part of the baroqueness of this setup is to deal with annoying
- * differences between krb5_cc_retrieve_cred() and
- * krb5_get_cred_via_tkt(); krb5_cc_retrieve_cred() fills in a
- * caller-allocated krb5_creds, while krb5_get_cred_via_tkt()
- * allocates a krb5_creds for return.
- */
-struct tr_state {
-    krb5_context ctx;
-    krb5_ccache ccache;
-    krb5_principal *kdc_list;
-    unsigned int nkdcs;
-    krb5_principal *cur_kdc;
-    krb5_principal *nxt_kdc;
-    krb5_principal *lst_kdc;
-    krb5_creds *cur_tgt;
-    krb5_creds *nxt_tgt;
-    krb5_creds **kdc_tgts;
-    struct cc_tgts cc_tgts;
-    krb5_creds *cur_cc_tgt;
-    krb5_creds *nxt_cc_tgt;
-    unsigned int ntgts;
-    krb5_creds *offpath_tgt;
-};
-
-/*
- * Debug support
- */
-#ifdef DEBUG_GC_FRM_KDC
-
-#define TR_DBG(ts, prog) tr_dbg(ts, prog)
-#define TR_DBG_RET(ts, prog, ret) tr_dbg_ret(ts, prog, ret)
-#define TR_DBG_RTREE(ts, prog, princ) tr_dbg_rtree(ts, prog, princ)
-
-static void tr_dbg(struct tr_state *, const char *);
-static void tr_dbg_ret(struct tr_state *, const char *, krb5_error_code);
-static void tr_dbg_rtree(struct tr_state *, const char *, krb5_principal);
-
-#else
-
-#define TR_DBG(ts, prog)
-#define TR_DBG_RET(ts, prog, ret)
-#define TR_DBG_RTREE(ts, prog, princ)
-
-#endif /* !DEBUG_GC_FRM_KDC */
-
-#ifdef DEBUG_REFERRALS
-
-#define DPRINTF(x) printf x
-#define DFPRINTF(x) fprintf x
-#define DUMP_PRINC(x, y) krb5int_dbgref_dump_principal((x), (y))
-
-#else
-
-#define DPRINTF(x)
-#define DFPRINTF(x)
-#define DUMP_PRINC(x, y)
-
-#endif
-
-/* Convert ticket flags to necessary KDC options */
-#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
-
-/*
- * Certain krb5_cc_retrieve_cred() errors are soft errors when looking
- * for a cross-realm TGT.
- */
-#define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND &&       \
-                        (r) != KRB5_CC_NOT_KTYPE)
-
-/*
- * Flags for ccache lookups of cross-realm TGTs.
- *
- * A cross-realm TGT may be issued by some other intermediate realm's
- * KDC, so we use KRB5_TC_MATCH_SRV_NAMEONLY.
- */
-#define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES)
-
-/*
- * Prototypes of helper functions
- */
-static krb5_error_code tgt_mcred(krb5_context, krb5_principal,
-                                 krb5_principal, krb5_principal, krb5_creds *);
-static krb5_error_code retr_local_tgt(struct tr_state *, krb5_principal);
-static krb5_error_code try_ccache(struct tr_state *, krb5_creds *);
-static krb5_error_code find_nxt_kdc(struct tr_state *);
-static krb5_error_code try_kdc(struct tr_state *, krb5_creds *);
-static krb5_error_code kdc_mcred(struct tr_state *, krb5_principal,
-                                 krb5_creds *mcreds);
-static krb5_error_code next_closest_tgt(struct tr_state *, krb5_principal);
-static krb5_error_code init_rtree(struct tr_state *,
-                                  krb5_principal, krb5_principal);
-static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache,
-                                    krb5_principal client, krb5_principal server,
-                                    krb5_creds *out_cc_tgt, krb5_creds **out_tgt,
-                                    krb5_creds ***out_kdc_tgts, int *tgtptr_isoffpath);
-static krb5_error_code chase_offpath(struct tr_state *, krb5_principal,
-                                     krb5_principal);
-static krb5_error_code offpath_loopchk(struct tr_state *ts,
-                                       krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount);
-
-/*
- * init_cc_tgts()
- *
- * Initialize indices for cached-TGT ring buffer.  Caller must zero
- * CC_TGTS, CC_TGT_DIRTY arrays prior to calling.
- */
-static void
-init_cc_tgts(struct tr_state *ts)
-{
-
-    ts->cc_tgts.cur = 0;
-    ts->cc_tgts.nxt = 1;
-    ts->cur_cc_tgt = &ts->cc_tgts.cred[0];
-    ts->nxt_cc_tgt = &ts->cc_tgts.cred[1];
-}
-
-/*
- * shift_cc_tgts()
- *
- * Given a fresh assignment to NXT_CC_TGT, mark NXT_CC_TGT as dirty,
- * and shift indices so old NXT_CC_TGT becomes new CUR_CC_TGT.  Clean
- * the new NXT_CC_TGT.
- */
-static void
-shift_cc_tgts(struct tr_state *ts)
-{
-    unsigned int i;
-    struct cc_tgts *rb;
-
-    rb = &ts->cc_tgts;
-    i = rb->cur = rb->nxt;
-    rb->dirty[i] = 1;
-    ts->cur_cc_tgt = ts->nxt_cc_tgt;
-
-    i = (i + 1) % NCC_TGTS;
-
-    rb->nxt = i;
-    ts->nxt_cc_tgt = &rb->cred[i];
-    if (rb->dirty[i]) {
-        krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
-        rb->dirty[i] = 0;
-    }
-}
-
-/*
- * clean_cc_tgts()
- *
- * Free CC_TGTS which were dirty, then mark them clean.
- */
-static void
-clean_cc_tgts(struct tr_state *ts)
-{
-    unsigned int i;
-    struct cc_tgts *rb;
-
-    rb = &ts->cc_tgts;
-    for (i = 0; i < NCC_TGTS; i++) {
-        if (rb->dirty[i]) {
-            krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
-            rb->dirty[i] = 0;
-        }
-    }
-}
-
-/*
- * Debug support
- */
-#ifdef DEBUG_GC_FRM_KDC
-static void
-tr_dbg(struct tr_state *ts, const char *prog)
-{
-    krb5_error_code retval;
-    char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str;
-
-    cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL;
-    retval = krb5_unparse_name(ts->ctx, ts->cur_tgt->server, &cur_tgt_str);
-    if (retval) goto cleanup;
-    retval = krb5_unparse_name(ts->ctx, *ts->cur_kdc, &cur_kdc_str);
-    if (retval) goto cleanup;
-    retval = krb5_unparse_name(ts->ctx, *ts->nxt_kdc, &nxt_kdc_str);
-    if (retval) goto cleanup;
-    fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str);
-    fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str);
-    fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str);
-cleanup:
-    if (cur_tgt_str)
-        krb5_free_unparsed_name(ts->ctx, cur_tgt_str);
-    if (cur_kdc_str)
-        krb5_free_unparsed_name(ts->ctx, cur_kdc_str);
-    if (nxt_kdc_str)
-        krb5_free_unparsed_name(ts->ctx, nxt_kdc_str);
-}
-
-static void
-tr_dbg_ret(struct tr_state *ts, const char *prog, krb5_error_code ret)
-{
-    fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret,
-            error_message(ret));
-}
-
-static void
-tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ)
-{
-    char *str;
-
-    if (krb5_unparse_name(ts->ctx, princ, &str))
-        return;
-    fprintf(stderr, "%s: %s\n", prog, str);
-    krb5_free_unparsed_name(ts->ctx, str);
-}
-#endif /* DEBUG_GC_FRM_KDC */
-
-/*
- * tgt_mcred()
- *
- * Return MCREDS for use as a match criterion.
- *
- * Resulting credential has CLIENT as the client principal, and
- * krbtgt/realm_of(DST)@realm_of(SRC) as the server principal.  Zeroes
- * MCREDS first, does not allocate MCREDS, and cleans MCREDS on
- * failure.  The peculiar ordering of DST and SRC args is for
- * consistency with krb5int_tgtname().
- */
-static krb5_error_code
-tgt_mcred(krb5_context ctx, krb5_principal client,
-          krb5_principal dst, krb5_principal src,
-          krb5_creds *mcreds)
-{
-    krb5_error_code retval;
-
-    retval = 0;
-    memset(mcreds, 0, sizeof(*mcreds));
-
-    retval = krb5_copy_principal(ctx, client, &mcreds->client);
-    if (retval)
-        goto cleanup;
-
-    retval = krb5int_tgtname(ctx, krb5_princ_realm(ctx, dst),
-                          krb5_princ_realm(ctx, src), &mcreds->server);
-    if (retval)
-        goto cleanup;
-
-cleanup:
-    if (retval)
-        krb5_free_cred_contents(ctx, mcreds);
-
-    return retval;
-}
-
-/*
- * init_rtree()
- *
- * Populate KDC_LIST with the output of krb5_walk_realm_tree().
- */
-static krb5_error_code
-init_rtree(struct tr_state *ts,
-           krb5_principal client, krb5_principal server)
-{
-    krb5_error_code retval;
-
-    ts->kdc_list = NULL;
-    retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client),
-                                  krb5_princ_realm(ts->ctx, server),
-                                  &ts->kdc_list, KRB5_REALM_BRANCH_CHAR);
-    if (retval)
-        return retval;
-
-    for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) {
-        assert(krb5_princ_size(ts->ctx, ts->kdc_list[ts->nkdcs]) == 2);
-        TR_DBG_RTREE(ts, "init_rtree", ts->kdc_list[ts->nkdcs]);
-    }
-    assert(ts->nkdcs > 1);
-    ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1;
-
-    ts->kdc_tgts = calloc(ts->nkdcs + 1, sizeof(krb5_creds));
-    if (ts->kdc_tgts == NULL)
-        return ENOMEM;
-
-    return 0;
-}
-
-/*
- * retr_local_tgt()
- *
- * Prime CUR_TGT with the cached TGT of the client's local realm.
- */
-static krb5_error_code
-retr_local_tgt(struct tr_state *ts, krb5_principal client)
-{
-    krb5_error_code retval;
-    krb5_creds tgtq;
-
-    memset(&tgtq, 0, sizeof(tgtq));
-    retval = tgt_mcred(ts->ctx, client, client, client, &tgtq);
-    if (retval)
-        return retval;
-
-    /* Match realm, unlike other ccache retrievals here. */
-    retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache,
-                                   KRB5_TC_SUPPORTED_KTYPES,
-                                   &tgtq, ts->nxt_cc_tgt);
-    krb5_free_cred_contents(ts->ctx, &tgtq);
-    if (!retval) {
-        shift_cc_tgts(ts);
-        ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt;
-    }
-    return retval;
-}
-
-/*
- * try_ccache()
- *
- * Attempt to retrieve desired NXT_TGT from ccache.  Point NXT_TGT to
- * it if successful.
- */
-static krb5_error_code
-try_ccache(struct tr_state *ts, krb5_creds *tgtq)
-{
-    krb5_error_code retval;
-
-    TR_DBG(ts, "try_ccache");
-    retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache, RETR_FLAGS,
-                                   tgtq, ts->nxt_cc_tgt);
-    if (!retval) {
-        shift_cc_tgts(ts);
-        ts->nxt_tgt = ts->cur_cc_tgt;
-    }
-    TR_DBG_RET(ts, "try_ccache", retval);
-    return retval;
-}
-
-/*
- * find_nxt_kdc()
- *
- * A NXT_TGT gotten from an intermediate KDC might actually be a
- * referral.  Search KDC_LIST forward starting from CUR_KDC, looking
- * for the KDC with the same remote realm as NXT_TGT.  If we don't
- * find it, the intermediate KDC is leading us off the transit path.
- *
- * Match on CUR_KDC's remote realm, not local realm, because, among
- * other reasons, we can get a referral to the final realm; e.g.,
- * given
- *
- *     KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2,
- *                   krbtgt/R4@R3, NULL }
- *     CUR_TGT->SERVER == krbtgt/R2@R1
- *     NXT_TGT->SERVER == krbtgt/R4@R2
- *
- * i.e., we got a ticket issued by R2 with remote realm R4, we want to
- * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT
- * with R3 as its local realm.
- *
- * Set up for next iteration of do_traversal() loop by pointing
- * NXT_KDC to one entry forward of the match.
- */
-static krb5_error_code
-find_nxt_kdc(struct tr_state *ts)
-{
-    krb5_data *r1, *r2;
-    krb5_principal *kdcptr;
-
-    TR_DBG(ts, "find_nxt_kdc");
-    assert(ts->ntgts > 0);
-    assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]);
-    if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2)
-        return KRB5_KDCREP_MODIFIED;
-
-    r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1);
-
-    for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) {
-
-        r2 = krb5_princ_component(ts->ctx, *kdcptr, 1);
-
-        if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) {
-            break;
-        }
-    }
-    if (*kdcptr != NULL) {
-        ts->nxt_kdc = kdcptr;
-        TR_DBG_RET(ts, "find_nxt_kdc", 0);
-        return 0;
-    }
-
-    r2 = krb5_princ_component(ts->ctx, ts->kdc_list[0], 1);
-    if (r1 != NULL && r2 != NULL &&
-        r1->length == r2->length &&
-        !memcmp(r1->data, r2->data, r1->length)) {
-        TR_DBG_RET(ts, "find_nxt_kdc: looped back to local",
-                   KRB5_KDCREP_MODIFIED);
-        return KRB5_KDCREP_MODIFIED;
-    }
-
-    /*
-     * Realm is not in our list; we probably got an unexpected realm
-     * referral.
-     */
-    ts->offpath_tgt = ts->nxt_tgt;
-    if (ts->cur_kdc == ts->kdc_list) {
-        /*
-         * Local KDC referred us off path; trust it for caching
-         * purposes.
-         */
-        return 0;
-    }
-    /*
-     * Unlink the off-path TGT from KDC_TGTS but don't free it,
-     * because we should return it.
-     */
-    ts->kdc_tgts[--ts->ntgts] = NULL;
-    ts->nxt_tgt = ts->cur_tgt;
-    TR_DBG_RET(ts, "find_nxt_kdc", 0);
-    return 0;
-}
-
-/*
- * try_kdc()
- *
- * Using CUR_TGT, attempt to get desired NXT_TGT.  Update NXT_KDC if
- * successful.
- */
-static krb5_error_code
-try_kdc(struct tr_state *ts, krb5_creds *tgtq)
-{
-    krb5_error_code retval;
-    krb5_creds ltgtq;
-
-    TR_DBG(ts, "try_kdc");
-    /* This check should probably be in gc_via_tkt. */
-    if (!krb5_c_valid_enctype(ts->cur_tgt->keyblock.enctype))
-        return KRB5_PROG_ETYPE_NOSUPP;
-
-    ltgtq = *tgtq;
-    ltgtq.is_skey = FALSE;
-    ltgtq.ticket_flags = ts->cur_tgt->ticket_flags;
-    retval = krb5_get_cred_via_tkt(ts->ctx, ts->cur_tgt,
-                                   FLAGS2OPTS(ltgtq.ticket_flags),
-                                   ts->cur_tgt->addresses,
-                                   &ltgtq, &ts->kdc_tgts[ts->ntgts++]);
-    if (retval) {
-        ts->ntgts--;
-        ts->nxt_tgt = ts->cur_tgt;
-        TR_DBG_RET(ts, "try_kdc", retval);
-        return retval;
-    }
-    ts->nxt_tgt = ts->kdc_tgts[ts->ntgts-1];
-    retval = find_nxt_kdc(ts);
-    TR_DBG_RET(ts, "try_kdc", retval);
-    return retval;
-}
-
-/*
- * kdc_mcred()
- *
- * Return MCREDS for use as a match criterion.
- *
- * Resulting credential has CLIENT as the client principal, and
- * krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server
- * principal.  Zeroes MCREDS first, does not allocate MCREDS, and
- * cleans MCREDS on failure.
- */
-static krb5_error_code
-kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds)
-{
-    krb5_error_code retval;
-    krb5_data *rdst, *rsrc;
-
-    retval = 0;
-    memset(mcreds, 0, sizeof(*mcreds));
-
-    rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1);
-    rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1);
-    retval = krb5_copy_principal(ts->ctx, client, &mcreds->client);
-    if (retval)
-        goto cleanup;
-
-    retval = krb5int_tgtname(ts->ctx, rdst, rsrc, &mcreds->server);
-    if (retval)
-        goto cleanup;
-
-cleanup:
-    if (retval)
-        krb5_free_cred_contents(ts->ctx, mcreds);
-
-    return retval;
-}
-
-/*
- * next_closest_tgt()
- *
- * Using CUR_TGT, attempt to get the cross-realm TGT having its remote
- * realm closest to the target principal's.  Update NXT_TGT, NXT_KDC
- * accordingly.
- */
-static krb5_error_code
-next_closest_tgt(struct tr_state *ts, krb5_principal client)
-{
-    krb5_error_code retval;
-    krb5_creds tgtq;
-
-    retval = 0;
-    memset(&tgtq, 0, sizeof(tgtq));
-
-    for (ts->nxt_kdc = ts->lst_kdc;
-         ts->nxt_kdc > ts->cur_kdc;
-         ts->nxt_kdc--) {
-
-        krb5_free_cred_contents(ts->ctx, &tgtq);
-        retval = kdc_mcred(ts, client, &tgtq);
-        if (retval)
-            goto cleanup;
-        /* Don't waste time retrying ccache for direct path. */
-        if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) {
-            retval = try_ccache(ts, &tgtq);
-            if (!retval)
-                break;
-            if (HARD_CC_ERR(retval))
-                goto cleanup;
-        }
-        /* Not in the ccache, so talk to a KDC. */
-        retval = try_kdc(ts, &tgtq);
-        if (!retval) {
-            break;
-        }
-        /*
-         * In case of errors in try_kdc() or find_nxt_kdc(), continue
-         * looping through the KDC list.
-         */
-    }
-    /*
-     * If we have a non-zero retval, we either have a hard error or we
-     * failed to find a closer TGT.
-     */
-cleanup:
-    krb5_free_cred_contents(ts->ctx, &tgtq);
-    return retval;
-}
-
-/*
- * do_traversal()
- *
- * Find final TGT needed to get CLIENT a ticket for SERVER.  Point
- * OUT_TGT at the desired TGT, which may be an existing cached TGT
- * (copied into OUT_CC_TGT) or one of the newly obtained TGTs
- * (collected in OUT_KDC_TGTS).
- *
- * Get comfortable; this is somewhat complicated.
- *
- * Nomenclature: Cross-realm TGS principal names have the form:
- *
- *     krbtgt/REMOTE@LOCAL
- *
- * krb5_walk_realm_tree() returns a list like:
- *
- *     krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, ...
- *
- * These are prinicpal names, not realm names.  We only really use the
- * remote parts of the TGT principal names.
- *
- * The do_traversal loop calls next_closest_tgt() to find the next
- * closest TGT to the destination realm.  next_closest_tgt() updates
- * NXT_KDC for the following iteration of the do_traversal() loop.
- *
- * At the beginning of any given iteration of the do_traversal() loop,
- * CUR_KDC's remote realm is the remote realm of CUR_TGT->SERVER.  The
- * local realms of CUR_KDC and CUR_TGT->SERVER may not match due to
- * short-circuit paths provided by intermediate KDCs, e.g., CUR_KDC
- * might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B.
- *
- * For example, given KDC_LIST of
- *
- * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3,
- * krbtgt/R5@R4
- *
- * The next_closest_tgt() loop moves NXT_KDC to the left starting from
- * R5, stopping before it reaches CUR_KDC.  When next_closest_tgt()
- * returns, the do_traversal() loop updates CUR_KDC to be NXT_KDC, and
- * calls next_closest_tgt() again.
- *
- * next_closest_tgt() at start of its loop:
- *
- *      CUR                 NXT
- *       |                   |
- *       V                   V
- *     +----+----+----+----+----+
- *     | R1 | R2 | R3 | R4 | R5 |
- *     +----+----+----+----+----+
- *
- * next_closest_tgt() returns after finding a ticket for krbtgt/R3@R1:
- *
- *      CUR       NXT
- *       |         |
- *       V         V
- *     +----+----+----+----+----+
- *     | R1 | R2 | R3 | R4 | R5 |
- *     +----+----+----+----+----+
- *
- * do_traversal() updates CUR_KDC:
- *
- *                NXT
- *                CUR
- *                 |
- *                 V
- *     +----+----+----+----+----+
- *     | R1 | R2 | R3 | R4 | R5 |
- *     +----+----+----+----+----+
- *
- * next_closest_tgt() at start of its loop:
- *
- *                CUR       NXT
- *                 |         |
- *                 V         V
- *     +----+----+----+----+----+
- *     | R1 | R2 | R3 | R4 | R5 |
- *     +----+----+----+----+----+
- *
- * etc.
- *
- * The algorithm executes in n*(n-1)/2 (the sum of integers from 1 to
- * n-1) attempts in the worst case, i.e., each KDC only has a
- * cross-realm ticket for the immediately following KDC in the transit
- * path.  Typically, short-circuit paths will cause execution occur
- * faster than this worst-case scenario.
- *
- * When next_closest_tgt() updates NXT_KDC, it may not perform a
- * simple increment from CUR_KDC, in part because some KDC may
- * short-circuit pieces of the transit path.
- */
-static krb5_error_code
-do_traversal(krb5_context ctx,
-             krb5_ccache ccache,
-             krb5_principal client,
-             krb5_principal server,
-             krb5_creds *out_cc_tgt,
-             krb5_creds **out_tgt,
-             krb5_creds ***out_kdc_tgts,
-             int *tgtptr_isoffpath)
-{
-    krb5_error_code retval;
-    struct tr_state state, *ts;
-
-    *out_tgt = NULL;
-    *out_kdc_tgts = NULL;
-    ts = &state;
-    memset(ts, 0, sizeof(*ts));
-    ts->ctx = ctx;
-    ts->ccache = ccache;
-    init_cc_tgts(ts);
-
-    retval = init_rtree(ts, client, server);
-    if (retval)
-        goto cleanup;
-
-    retval = retr_local_tgt(ts, client);
-    if (retval)
-        goto cleanup;
-
-    for (ts->cur_kdc = ts->kdc_list, ts->nxt_kdc = NULL;
-         ts->cur_kdc != NULL && ts->cur_kdc < ts->lst_kdc;
-         ts->cur_kdc = ts->nxt_kdc, ts->cur_tgt = ts->nxt_tgt) {
-
-        retval = next_closest_tgt(ts, client);
-        if (retval)
-            goto cleanup;
-
-        if (ts->offpath_tgt != NULL) {
-            retval = chase_offpath(ts, client, server);
-            if (retval)
-                goto cleanup;
-            break;
-        }
-        assert(ts->cur_kdc != ts->nxt_kdc);
-    }
-
-    if (NXT_TGT_IS_CACHED(ts)) {
-        assert(ts->offpath_tgt == NULL);
-        *out_cc_tgt = *ts->cur_cc_tgt;
-        *out_tgt = out_cc_tgt;
-        MARK_CUR_CC_TGT_CLEAN(ts);
-    } else if (ts->offpath_tgt != NULL){
-        *out_tgt = ts->offpath_tgt;
-    } else {
-        /* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */
-        *out_tgt = ts->nxt_tgt;
-    }
-
-cleanup:
-    clean_cc_tgts(ts);
-    if (ts->kdc_list != NULL)
-        krb5_free_realm_tree(ctx, ts->kdc_list);
-    if (ts->ntgts == 0) {
-        *out_kdc_tgts = NULL;
-        if (ts->kdc_tgts != NULL)
-            free(ts->kdc_tgts);
-    } else
-        *out_kdc_tgts = ts->kdc_tgts;
-    *tgtptr_isoffpath = (ts->offpath_tgt != NULL);
-    return retval;
-}
-
-/*
- * chase_offpath()
- *
- * Chase off-path TGT referrals.
- *
- * If we are traversing a trusted path (either hierarchically derived
- * or explicit capath) and get a TGT pointing to a realm off this
- * path, query the realm referenced by that off-path TGT.  Repeat
- * until we get to the destination realm or encounter an error.
- *
- * CUR_TGT is always either pointing into REFTGTS or is an alias for
- * TS->OFFPATH_TGT.
- */
-static krb5_error_code
-chase_offpath(struct tr_state *ts,
-              krb5_principal client, krb5_principal server)
-{
-    krb5_error_code retval;
-    krb5_creds mcred;
-    krb5_creds *cur_tgt, *nxt_tgt, *reftgts[KRB5_REFERRAL_MAXHOPS];
-    krb5_data *rsrc, *rdst, *r1;
-    unsigned int rcount, i;
-
-    rdst = krb5_princ_realm(ts->ctx, server);
-    cur_tgt = ts->offpath_tgt;
-
-    for (rcount = 0; rcount < KRB5_REFERRAL_MAXHOPS; rcount++) {
-        nxt_tgt = NULL;
-        memset(&mcred, 0, sizeof(mcred));
-        rsrc = krb5_princ_component(ts->ctx, cur_tgt->server, 1);
-        retval = krb5int_tgtname(ts->ctx, rdst, rsrc, &mcred.server);
-        if (retval)
-            goto cleanup;
-        mcred.client = client;
-        retval = krb5_get_cred_via_tkt(ts->ctx, cur_tgt,
-                                       FLAGS2OPTS(cur_tgt->ticket_flags),
-                                       cur_tgt->addresses, &mcred, &nxt_tgt);
-        mcred.client = NULL;
-        krb5_free_principal(ts->ctx, mcred.server);
-        mcred.server = NULL;
-        if (retval)
-            goto cleanup;
-        if (!IS_TGS_PRINC(ts->ctx, nxt_tgt->server)) {
-            retval = KRB5_KDCREP_MODIFIED;
-            goto cleanup;
-        }
-        r1 = krb5_princ_component(ts->ctx, nxt_tgt->server, 1);
-        if (rdst->length == r1->length &&
-            !memcmp(rdst->data, r1->data, rdst->length)) {
-            retval = 0;
-            goto cleanup;
-        }
-        retval = offpath_loopchk(ts, nxt_tgt, reftgts, rcount);
-        if (retval)
-            goto cleanup;
-        reftgts[rcount] = nxt_tgt;
-        cur_tgt = nxt_tgt;
-        nxt_tgt = NULL;
-    }
-    /* Max hop count exceeded. */
-    retval = KRB5_KDCREP_MODIFIED;
-
-cleanup:
-    if (mcred.server != NULL) {
-        krb5_free_principal(ts->ctx, mcred.server);
-    }
-    /*
-     * Don't free TS->OFFPATH_TGT if it's in the list of cacheable
-     * TGTs to be returned by do_traversal().
-     */
-    if (ts->offpath_tgt != ts->nxt_tgt) {
-        krb5_free_creds(ts->ctx, ts->offpath_tgt);
-    }
-    ts->offpath_tgt = NULL;
-    if (nxt_tgt != NULL) {
-        if (retval)
-            krb5_free_creds(ts->ctx, nxt_tgt);
-        else
-            ts->offpath_tgt = nxt_tgt;
-    }
-    for (i = 0; i < rcount; i++) {
-        krb5_free_creds(ts->ctx, reftgts[i]);
-    }
-    return retval;
-}
-
-/*
- * offpath_loopchk()
- *
- * Check for loop back to previously-visited realms, both off-path and
- * on-path.
- */
-static krb5_error_code
-offpath_loopchk(struct tr_state *ts,
-                krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount)
-{
-    krb5_data *r1, *r2;
-    unsigned int i;
-
-    r1 = krb5_princ_component(ts->ctx, tgt->server, 1);
-    for (i = 0; i < rcount; i++) {
-        r2 = krb5_princ_component(ts->ctx, reftgts[i]->server, 1);
-        if (r1->length == r2->length &&
-            !memcmp(r1->data, r2->data, r1->length))
-            return KRB5_KDCREP_MODIFIED;
-    }
-    for (i = 0; i < ts->ntgts; i++) {
-        r2 = krb5_princ_component(ts->ctx, ts->kdc_tgts[i]->server, 1);
-        if (r1->length == r2->length &&
-            !memcmp(r1->data, r2->data, r1->length))
-            return KRB5_KDCREP_MODIFIED;
-    }
-    return 0;
-}
-
-/*
- * krb5_get_cred_from_kdc_opt()
- * krb5_get_cred_from_kdc()
- * krb5_get_cred_from_kdc_validate()
- * krb5_get_cred_from_kdc_renew()
- *
- * Retrieve credentials for client IN_CRED->CLIENT, server
- * IN_CRED->SERVER, ticket flags IN_CRED->TICKET_FLAGS, possibly
- * second_ticket if needed.
- *
- * Request credentials from the KDC for the server's realm.  Point
- * TGTS to an allocated array of pointers to krb5_creds, containing
- * any intermediate credentials obtained in the process of contacting
- * the server's KDC; if no intermediate credentials were obtained,
- * TGTS is a null pointer.  Return intermediate credentials if
- * intermediate KDCs provided credentials, even if no useful end
- * ticket results.
- *
- * Caller must free TGTS, regardless of whether this function returns
- * success.
- *
- * This function does NOT cache the intermediate TGTs.
- *
- * Do not call this routine if desired credentials are already cached.
- *
- * On success, OUT_CRED contains the desired credentials; the caller
- * must free them.
- *
- * Beware memory management issues if you have modifications in mind.
- * With the addition of referral support, it is now the case that *tgts,
- * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to
- * the same credential at different times.
- *
- * Returns errors, system errors.
- */
-
-krb5_error_code
-krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
-                           krb5_creds *in_cred, krb5_creds **out_cred,
-                           krb5_creds ***tgts, int kdcopt)
-{
-    krb5_error_code retval, subretval;
-    krb5_principal client, server, supplied_server, out_supplied_server;
-    krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS];
-    krb5_creds *otgtptr = NULL;
-    int tgtptr_isoffpath = 0;
-    krb5_boolean old_use_conf_ktypes;
-    char **hrealms;
-    unsigned int referral_count, i;
-    krb5_authdata **supplied_authdata, **out_supplied_authdata = NULL;
-
-    /*
-     * Set up client and server pointers.  Make a fresh and modifyable
-     * copy of the in_cred server and save the supplied version.
-     */
-    client = in_cred->client;
-    if ((retval=krb5_copy_principal(context, in_cred->server, &server)))
-        return retval;
-    /* We need a second copy for the output creds. */
-    if ((retval = krb5_copy_principal(context, server,
-                                      &out_supplied_server)) != 0 ) {
-        krb5_free_principal(context, server);
-        return retval;
-    }
-    if (in_cred->authdata != NULL) {
-        if ((retval = krb5_copy_authdata(context, in_cred->authdata,
-                                         &out_supplied_authdata)) != 0) {
-            krb5_free_principal(context, out_supplied_server);
-            krb5_free_principal(context, server);
-            return retval;
-        }
-    }
-
-    supplied_server = in_cred->server;
-    in_cred->server=server;
-    supplied_authdata = in_cred->authdata;
-
-    DUMP_PRINC("gc_from_kdc initial client", client);
-    DUMP_PRINC("gc_from_kdc initial server", server);
-    memset(&cc_tgt, 0, sizeof(cc_tgt));
-    memset(&tgtq, 0, sizeof(tgtq));
-    memset(&referral_tgts, 0, sizeof(referral_tgts));
-
-    tgtptr = NULL;
-    *tgts = NULL;
-    *out_cred=NULL;
-    old_use_conf_ktypes = context->use_conf_ktypes;
-
-    /* Copy client realm to server if no hint. */
-    if (krb5_is_referral_realm(&server->realm)) {
-        /* Use the client realm. */
-        DPRINTF(("gc_from_kdc: no server realm supplied, "
-                 "using client realm.\n"));
-        krb5_free_data_contents(context, &server->realm);
-        server->realm.data = malloc(client->realm.length + 1);
-        if (server->realm.data == NULL) {
-            retval = ENOMEM;
-            goto cleanup;
-        }
-        memcpy(server->realm.data, client->realm.data, client->realm.length);
-        server->realm.length = client->realm.length;
-        server->realm.data[server->realm.length] = 0;
-    }
-    /*
-     * Retreive initial TGT to match the specified server, either for the
-     * local realm in the default (referral) case or for the remote
-     * realm if we're starting someplace non-local.
-     */
-    retval = tgt_mcred(context, client, server, client, &tgtq);
-    if (retval)
-        goto cleanup;
-
-    /* Fast path: Is it in the ccache? */
-    context->use_conf_ktypes = 1;
-    retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
-                                   &tgtq, &cc_tgt);
-    if (!retval) {
-        tgtptr = &cc_tgt;
-    } else if (!HARD_CC_ERR(retval)) {
-        DPRINTF(("gc_from_kdc: starting do_traversal to find "
-                 "initial TGT for referral\n"));
-        tgtptr_isoffpath = 0;
-        otgtptr = NULL;
-        retval = do_traversal(context, ccache, client, server,
-                              &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
-    }
-    if (retval) {
-        DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n"));
-        goto cleanup;
-    }
-
-    DUMP_PRINC("gc_from_kdc: server as requested", supplied_server);
-
-    if (in_cred->second_ticket.length != 0 &&
-        (kdcopt & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) {
-        kdcopt |= KDC_OPT_ENC_TKT_IN_SKEY;
-    }
-
-    /*
-     * Try requesting a service ticket from our local KDC with referrals
-     * turned on.  If the first referral succeeds, follow a referral-only
-     * path, otherwise fall back to old-style assumptions.
-     */
-
-    /*
-     * Save TGTPTR because we rewrite it in the referral loop, and
-     * we might need to explicitly free it later.
-     */
-    otgtptr = tgtptr;
-    for (referral_count = 0;
-         referral_count < KRB5_REFERRAL_MAXHOPS;
-         referral_count++) {
-#if 0
-        DUMP_PRINC("gc_from_kdc: referral loop: tgt in use", tgtptr->server);
-        DUMP_PRINC("gc_from_kdc: referral loop: request is for", server);
-#endif
-        retval = krb5_get_cred_via_tkt(context, tgtptr,
-                                       KDC_OPT_CANONICALIZE |
-                                       FLAGS2OPTS(tgtptr->ticket_flags) |
-                                       kdcopt,
-                                       tgtptr->addresses, in_cred, out_cred);
-        if (retval) {
-            DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",
-                     error_message(retval)));
-            /* If we haven't gone anywhere yet, fail through to the
-               non-referral case. */
-            if (referral_count==0) {
-                DPRINTF(("gc_from_kdc: initial referral failed; "
-                         "punting to fallback.\n"));
-                break;
-            }
-            /* Otherwise, try the same query without canonicalization
-               set, and fail hard if that doesn't work. */
-            DPRINTF(("gc_from_kdc: referral #%d failed; "
-                     "retrying without option.\n", referral_count + 1));
-            retval = krb5_get_cred_via_tkt(context, tgtptr,
-                                           FLAGS2OPTS(tgtptr->ticket_flags) |
-                                           kdcopt,
-                                           tgtptr->addresses,
-                                           in_cred, out_cred);
-            /* Whether or not that succeeded, we're done. */
-            goto cleanup;
-        }
-        /* Referral request succeeded; let's see what it is. */
-        if (krb5_principal_compare(context, in_cred->server,
-                                   (*out_cred)->server)) {
-            DPRINTF(("gc_from_kdc: request generated ticket "
-                     "for requested server principal\n"));
-            DUMP_PRINC("gc_from_kdc final referred reply",
-                       in_cred->server);
-
-            /*
-             * Check if the return enctype is one that we requested if
-             * needed.
-             */
-            if (old_use_conf_ktypes || !context->tgs_etypes)
-                goto cleanup;
-            for (i = 0; context->tgs_etypes[i]; i++) {
-                if ((*out_cred)->keyblock.enctype == context->tgs_etypes[i]) {
-                    /* Found an allowable etype, so we're done */
-                    goto cleanup;
-                }
-            }
-            /*
-             *  We need to try again, but this time use the
-             *  tgs_ktypes in the context. At this point we should
-             *  have all the tgts to succeed.
-             */
-
-            /* Free "wrong" credential */
-            krb5_free_creds(context, *out_cred);
-            *out_cred = NULL;
-            /* Re-establish tgs etypes */
-            context->use_conf_ktypes = old_use_conf_ktypes;
-            retval = krb5_get_cred_via_tkt(context, tgtptr,
-                                           KDC_OPT_CANONICALIZE |
-                                           FLAGS2OPTS(tgtptr->ticket_flags) |
-                                           kdcopt,
-                                           tgtptr->addresses,
-                                           in_cred, out_cred);
-            goto cleanup;
-        }
-        else if (IS_TGS_PRINC(context, (*out_cred)->server)) {
-            krb5_data *r1, *r2;
-
-            DPRINTF(("gc_from_kdc: request generated referral tgt\n"));
-            DUMP_PRINC("gc_from_kdc credential received",
-                       (*out_cred)->server);
-
-            if (referral_count == 0)
-                r1 = &tgtptr->server->data[1];
-            else
-                r1 = &referral_tgts[referral_count-1]->server->data[1];
-
-            r2 = &(*out_cred)->server->data[1];
-            if (data_eq(*r1, *r2)) {
-                DPRINTF(("gc_from_kdc: referred back to "
-                         "previous realm; fall back\n"));
-                krb5_free_creds(context, *out_cred);
-                *out_cred = NULL;
-                break;
-            }
-            /* Check for referral routing loop. */
-            for (i=0;i<referral_count;i++) {
-#if 0
-                DUMP_PRINC("gc_from_kdc: loop compare #1",
-                           (*out_cred)->server);
-                DUMP_PRINC("gc_from_kdc: loop compare #2",
-                           referral_tgts[i]->server);
-#endif
-                if (krb5_principal_compare(context,
-                                           (*out_cred)->server,
-                                           referral_tgts[i]->server)) {
-                    DFPRINTF((stderr,
-                              "krb5_get_cred_from_kdc_opt: "
-                              "referral routing loop - "
-                              "got referral back to hop #%d\n", i));
-                    retval=KRB5_KDC_UNREACH;
-                    goto cleanup;
-                }
-            }
-            /* Point current tgt pointer at newly-received TGT. */
-            if (tgtptr == &cc_tgt)
-                krb5_free_cred_contents(context, tgtptr);
-            tgtptr=*out_cred;
-            /* Save requested auth data with TGT in case it ends up stored */
-            if (supplied_authdata != NULL) {
-                /* Ensure we note TGT contains authorization data */
-                retval = krb5_copy_authdata(context,
-                                            supplied_authdata,
-                                            &(*out_cred)->authdata);
-                if (retval)
-                    goto cleanup;
-            }
-            /* Save pointer to tgt in referral_tgts. */
-            referral_tgts[referral_count]=*out_cred;
-            *out_cred = NULL;
-            /* Copy krbtgt realm to server principal. */
-            krb5_free_data_contents(context, &server->realm);
-            retval = krb5int_copy_data_contents(context,
-                                                &tgtptr->server->data[1],
-                                                &server->realm);
-            if (retval)
-                goto cleanup;
-            /* Don't ask for KDC to add auth data multiple times */
-            in_cred->authdata = NULL;
-            /*
-             * Future work: rewrite server principal per any
-             * supplied padata.
-             */
-        } else {
-            /* Not a TGT; punt to fallback. */
-            krb5_free_creds(context, *out_cred);
-            *out_cred = NULL;
-            break;
-        }
-    }
-
-    DUMP_PRINC("gc_from_kdc client at fallback", client);
-    DUMP_PRINC("gc_from_kdc server at fallback", server);
-
-    /*
-     * At this point referrals have been tried and have failed.  Go
-     * back to the server principal as originally issued and try the
-     * conventional path.
-     */
-
-    /*
-     * Referrals have failed.  Look up fallback realm if not
-     * originally provided.
-     */
-    if (krb5_is_referral_realm(&supplied_server->realm)) {
-        if (server->length >= 2) {
-            retval=krb5_get_fallback_host_realm(context, &server->data[1],
-                                                &hrealms);
-            if (retval) goto cleanup;
-#if 0
-            DPRINTF(("gc_from_kdc: using fallback realm of %s\n",
-                     hrealms[0]));
-#endif
-            krb5_free_data_contents(context,&in_cred->server->realm);
-            server->realm.data=hrealms[0];
-            server->realm.length=strlen(hrealms[0]);
-            free(hrealms);
-        }
-        else {
-            /*
-             * Problem case: Realm tagged for referral but apparently not
-             * in a <type>/<host> format that
-             * krb5_get_fallback_host_realm can deal with.
-             */
-            DPRINTF(("gc_from_kdc: referral specified "
-                     "but no fallback realm avaiable!\n"));
-            retval = KRB5_ERR_HOST_REALM_UNKNOWN;
-            goto cleanup;
-        }
-    }
-
-    DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite",
-               server);
-
-    /*
-     * Get a TGT for the target realm.
-     */
-
-    krb5_free_cred_contents(context, &tgtq);
-    retval = tgt_mcred(context, client, server, client, &tgtq);
-    if (retval)
-        goto cleanup;
-
-    /* Fast path: Is it in the ccache? */
-    /* Free tgtptr data if reused from above. */
-    if (tgtptr == &cc_tgt)
-        krb5_free_cred_contents(context, tgtptr);
-    tgtptr = NULL;
-    /* Free saved TGT in OTGTPTR if it was off-path. */
-    if (tgtptr_isoffpath)
-        krb5_free_creds(context, otgtptr);
-    otgtptr = NULL;
-    /* Free TGTS if previously filled by do_traversal() */
-    if (*tgts != NULL) {
-        for (i = 0; (*tgts)[i] != NULL; i++) {
-            krb5_free_creds(context, (*tgts)[i]);
-        }
-        free(*tgts);
-        *tgts = NULL;
-    }
-    context->use_conf_ktypes = 1;
-    retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
-                                   &tgtq, &cc_tgt);
-    if (!retval) {
-        tgtptr = &cc_tgt;
-    } else if (!HARD_CC_ERR(retval)) {
-        tgtptr_isoffpath = 0;
-        retval = do_traversal(context, ccache, client, server,
-                              &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
-    }
-    if (retval)
-        goto cleanup;
-    otgtptr = tgtptr;
-
-    /*
-     * Finally have TGT for target realm!  Try using it to get creds.
-     */
-
-    if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) {
-        retval = KRB5_PROG_ETYPE_NOSUPP;
-        goto cleanup;
-    }
-    context->use_conf_ktypes = old_use_conf_ktypes;
-    retval = krb5_get_cred_via_tkt(context, tgtptr,
-                                   FLAGS2OPTS(tgtptr->ticket_flags) |
-                                   kdcopt,
-                                   tgtptr->addresses, in_cred, out_cred);
-
-cleanup:
-    krb5_free_cred_contents(context, &tgtq);
-    if (tgtptr == &cc_tgt)
-        krb5_free_cred_contents(context, tgtptr);
-    if (tgtptr_isoffpath)
-        krb5_free_creds(context, otgtptr);
-    context->use_conf_ktypes = old_use_conf_ktypes;
-    /* Drop the original principal back into in_cred so that it's cached
-       in the expected format. */
-    DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup",
-               server);
-    krb5_free_principal(context, server);
-    in_cred->server = supplied_server;
-    in_cred->authdata = supplied_authdata;
-    if (*out_cred && !retval) {
-        /* Success: free server, swap supplied server back in. */
-        krb5_free_principal (context, (*out_cred)->server);
-        (*out_cred)->server = out_supplied_server;
-        assert((*out_cred)->authdata == NULL);
-        (*out_cred)->authdata = out_supplied_authdata;
-    }
-    else {
-        /*
-         * Failure: free out_supplied_server.  Don't free out_cred here
-         * since it's either null or a referral TGT that we free below,
-         * and we may need it to return.
-         */
-        krb5_free_principal(context, out_supplied_server);
-        krb5_free_authdata(context, out_supplied_authdata);
-    }
-    DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server);
-    /*
-     * Deal with ccache TGT management: If tgts has been set from
-     * initial non-referral TGT discovery, leave it alone.  Otherwise, if
-     * referral_tgts[0] exists return it as the only entry in tgts.
-     * (Further referrals are never cached, only the referral from the
-     * local KDC.)  This is part of cleanup because useful received TGTs
-     * should be cached even if the main request resulted in failure.
-     */
-
-    if (*tgts == NULL) {
-        if (referral_tgts[0]) {
-#if 0
-            /*
-             * This should possibly be a check on the candidate return
-             * credential against the cache, in the circumstance where we
-             * don't want to clutter the cache with near-duplicate
-             * credentials on subsequent iterations.  For now, it is
-             * disabled.
-             */
-            subretval=...?;
-            if (subretval) {
-#endif
-                /* Allocate returnable TGT list. */
-                *tgts = calloc(2, sizeof (krb5_creds *));
-                if (*tgts == NULL && retval == 0)
-                    retval = ENOMEM;
-                if (*tgts) {
-                    subretval = krb5_copy_creds(context, referral_tgts[0],
-                                                &((*tgts)[0]));
-                    if (subretval) {
-                        if (retval == 0)
-                            retval = subretval;
-                        free(*tgts);
-                        *tgts = NULL;
-                    } else {
-                        (*tgts)[1] = NULL;
-                        DUMP_PRINC("gc_from_kdc: referral TGT for ccache",
-                                   (*tgts)[0]->server);
-                    }
-                }
-#if 0
-            }
-#endif
-        }
-    }
-
-    /* Free referral TGTs list. */
-    for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
-        if(referral_tgts[i]) {
-            krb5_free_creds(context, referral_tgts[i]);
-        }
-    }
-    DPRINTF(("gc_from_kdc finishing with %s\n",
-             retval ? error_message(retval) : "no error"));
-    return retval;
-}
-
-krb5_error_code
-krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache,
-                       krb5_creds *in_cred, krb5_creds **out_cred,
-                       krb5_creds ***tgts)
-{
-    return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
-                                      0);
-}
-
-krb5_error_code
-krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache,
-                                krb5_creds *in_cred, krb5_creds **out_cred,
-                                krb5_creds ***tgts)
-{
-    return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
-                                      KDC_OPT_VALIDATE);
-}
-
-krb5_error_code
-krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache,
-                             krb5_creds *in_cred, krb5_creds **out_cred,
-                             krb5_creds ***tgts)
-{
-    return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
-                                      KDC_OPT_RENEW);
-}
diff --git a/src/lib/krb5/krb/get_creds.c b/src/lib/krb5/krb/get_creds.c
index 396ea79..e9356f1 100644
--- a/src/lib/krb5/krb/get_creds.c
+++ b/src/lib/krb5/krb/get_creds.c
@@ -2,7 +2,7 @@
 /*
  * lib/krb5/krb/get_creds.c
  *
- * Copyright 1990, 2008 by the Massachusetts Institute of Technology.
+ * Copyright 1990, 2008, 2010 by the Massachusetts Institute of Technology.
  * All Rights Reserved.
  *
  * Export of this software from the United States of America may
@@ -110,102 +110,1132 @@ krb5int_construct_matching_creds(krb5_context context, krb5_flags options,
     return 0;
 }
 
-krb5_error_code KRB5_CALLCONV
-krb5_get_credentials(krb5_context context, krb5_flags options,
-                     krb5_ccache ccache, krb5_creds *in_creds,
-                     krb5_creds **out_creds)
+/*
+ * krb5_tkt_creds_step() is implemented using a tail call style.  Every
+ * begin_*, step_*, or *_request function is responsible for returning an
+ * error, generating the next request, or delegating to another function using
+ * a tail call.
+ *
+ * The process is divided up into states which govern how the next input token
+ * should be interpreted.  Each state has a "begin_<state>" function to set up
+ * the context fields related to the state, a "step_<state>" function to
+ * process a reply and update the related context fields, and possibly a
+ * "<state>_request" function (invoked by the begin_ and step_ functions) to
+ * generate the next request.  If it's time to advance to another state, any of
+ * the three functions can make a tail call to begin_<nextstate> to do so.
+ *
+ * The overall process is as follows:
+ *   1. Get a TGT for the service principal's realm (STATE_GET_TGT).
+ *   2. Make one or more referrals queries (STATE_REFERRALS).
+ *   3. In some cases, get a TGT for the fallback realm (STATE_GET_TGT again).
+ *   4. In some cases, make a non-referral query (STATE_NON_REFERRAL).
+ *
+ * STATE_GET_TGT can precede either STATE_REFERRALS or STATE_NON_REFERRAL.  The
+ * getting_tgt_for field in the context keeps track of what state we will go to
+ * after successfully obtaining the TGT, and the end_get_tgt() function
+ * advances to the proper next state.
+ */
+
+enum state {
+    STATE_BEGIN,                /* Initial step (no input token) */
+    STATE_GET_TGT,              /* Getting TGT for service realm */
+    STATE_GET_TGT_OFFPATH,      /* Getting TGT via off-path referrals */
+    STATE_REFERRALS,            /* Retrieving service ticket or referral */
+    STATE_NON_REFERRAL,         /* Non-referral service ticket request */
+    STATE_COMPLETE              /* Creds ready for retrieval */
+};
+
+struct _krb5_tkt_creds_context {
+    enum state state;           /* What we should do with the next reply */
+    enum state getting_tgt_for; /* STATE_REFERRALS or STATE_NON_REFERRAL */
+
+    /* The following fields are set up at initialization time. */
+    krb5_creds *in_creds;       /* Creds requested by caller */
+    krb5_principal client;      /* Caller-requested client principal (alias) */
+    krb5_principal server;      /* Server principal (alias) */
+    krb5_principal req_server;  /* Caller-requested server principal */
+    krb5_ccache ccache;         /* Caller-provided ccache (alias) */
+    krb5_flags req_options;     /* Caller-requested KRB5_GC_* options */
+    krb5_flags req_kdcopt;      /* Caller-requested options as KDC options */
+    krb5_authdata **authdata;   /* Caller-requested authdata */
+
+    /* The following fields are used in multiple steps. */
+    krb5_creds *cur_tgt;        /* TGT to be used for next query */
+    krb5_data *realms_seen;     /* For loop detection */
+    krb5_error_code cache_code; /* KRB5_CC_NOTFOUND or KRB5_CC_NOT_KTYPE */
+
+    /* The following fields track state between request and reply. */
+    krb5_principal tgt_princ;   /* Storage for TGT principal */
+    krb5_creds tgt_in_creds;    /* Container for TGT matching creds */
+    krb5_creds *tgs_in_creds;   /* Input credentials of request (alias) */
+    krb5_timestamp timestamp;   /* Timestamp of request */
+    krb5_int32 nonce;           /* Nonce of request */
+    int kdcopt;                 /* KDC options of request */
+    krb5_keyblock *subkey;      /* subkey of request */
+    krb5_data previous_request; /* Encoded request (for TCP retransmission) */
+
+    /* The following fields are used when acquiring foreign TGTs. */
+    krb5_data *realm_path;      /* Path from client to server realm */
+    const krb5_data *last_realm;/* Last realm in realm_path */
+    const krb5_data *cur_realm; /* Position of cur_tgt in realm_path  */
+    const krb5_data *next_realm;/* Current target realm in realm_path */
+    unsigned int offpath_count; /* Offpath requests made */
+
+    /* The following fields are used during the referrals loop. */
+    unsigned int referral_count;/* Referral requests made */
+
+    /* The following fields are used within a _step call to avoid
+     * passing them as parameters everywhere. */
+    krb5_creds *reply_creds;    /* Creds from TGS reply */
+    krb5_error_code reply_code; /* Error status from TGS reply */
+    krb5_data *caller_out;      /* Caller's out parameter */
+    krb5_data *caller_realm;    /* Caller's realm parameter */
+    unsigned int *caller_flags; /* Caller's flags parameter */
+};
+
+/* Convert ticket flags to necessary KDC options */
+#define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
+
+static krb5_error_code
+begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx);
+
+/*
+ * Fill in the caller out, realm, and flags output variables.  out is filled in
+ * with ctx->previous_request, which the caller should set, and realm is filled
+ * in with the realm of ctx->cur_tgt.
+ */
+static krb5_error_code
+set_caller_request(krb5_context context, krb5_tkt_creds_context ctx)
 {
-    krb5_error_code retval;
-    krb5_creds mcreds, *ncreds, **tgts, **tgts_iter;
-    krb5_flags fields;
-    krb5_boolean not_ktype = FALSE;
-    int kdcopt = 0;
+    krb5_error_code code;
+    const krb5_data *req = &ctx->previous_request;
+    const krb5_data *realm = &ctx->cur_tgt->server->data[1];
+    krb5_data out_copy = empty_data(), realm_copy = empty_data();
+
+    code = krb5int_copy_data_contents(context, req, &out_copy);
+    if (code != 0)
+        goto cleanup;
+    code = krb5int_copy_data_contents(context, realm, &realm_copy);
+    if (code != 0)
+        goto cleanup;
+
+    *ctx->caller_out = out_copy;
+    *ctx->caller_realm = realm_copy;
+    *ctx->caller_flags = KRB5_TKT_CREDS_CONTINUE;
+    return 0;
+
+cleanup:
+    krb5_free_data_contents(context, &out_copy);
+    krb5_free_data_contents(context, &realm_copy);
+    return code;
+}
+
+/* Simple wrapper around krb5_cc_retrieve_cred which allocates the result
+ * container. */
+static krb5_error_code
+cache_get(krb5_context context, krb5_ccache ccache, krb5_flags flags,
+          krb5_creds *in_creds, krb5_creds **out_creds)
+{
+    krb5_error_code code;
+    krb5_creds *creds;
 
     *out_creds = NULL;
 
+    creds = malloc(sizeof(*creds));
+    if (creds == NULL)
+        return ENOMEM;
+
+    code = krb5_cc_retrieve_cred(context, ccache, flags, in_creds, creds);
+    if (code != 0) {
+        free(creds);
+        return code;
+    }
+
+    *out_creds = creds;
+    return 0;
+}
+
+/*
+ * Set up the request given by ctx->tgs_in_creds, using ctx->cur_tgt.  KDC
+ * options for the requests are determined by ctx->cur_tgt->ticket_flags and
+ * extra_options.
+ */
+static krb5_error_code
+make_request(krb5_context context, krb5_tkt_creds_context ctx,
+             int extra_options)
+{
+    krb5_error_code code;
+    krb5_data request = empty_data();
+
+    ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags);
+
+    /* XXX This check belongs in gc_via_tgt.c or nowhere. */
+    if (!krb5_c_valid_enctype(ctx->cur_tgt->keyblock.enctype))
+        return KRB5_PROG_ETYPE_NOSUPP;
+
+    code = krb5int_make_tgs_request(context, ctx->cur_tgt, ctx->kdcopt,
+                                    ctx->cur_tgt->addresses, NULL,
+                                    ctx->tgs_in_creds, NULL, NULL, &request,
+                                    &ctx->timestamp, &ctx->nonce,
+                                    &ctx->subkey);
+    if (code != 0)
+        return code;
+
+    krb5_free_data_contents(context, &ctx->previous_request);
+    ctx->previous_request = request;
+    return set_caller_request(context, ctx);
+}
+
+/* Set up a request for a TGT for realm, using ctx->cur_tgt. */
+static krb5_error_code
+make_request_for_tgt(krb5_context context, krb5_tkt_creds_context ctx,
+                     const krb5_data *realm)
+{
+    krb5_error_code code;
+
+    /* Construct the principal krbtgt/<realm>@<cur-tgt-realm>. */
+    krb5_free_principal(context, ctx->tgt_princ);
+    ctx->tgt_princ = NULL;
+    code = krb5int_tgtname(context, realm, &ctx->cur_tgt->server->realm,
+                           &ctx->tgt_princ);
+    if (code != 0)
+        return code;
+
+    /* Construct input creds using ctx->tgt_in_creds as a container. */
+    memset(&ctx->tgt_in_creds, 0, sizeof(ctx->tgt_in_creds));
+    ctx->tgt_in_creds.client = ctx->client;
+    ctx->tgt_in_creds.server = ctx->tgt_princ;
+
+    /* Make a request for the above creds with no extra options. */
+    ctx->tgs_in_creds = &ctx->tgt_in_creds;
+    code = make_request(context, ctx, 0);
+    return code;
+}
+
+/* Set up a request for the desired service principal, using ctx->cur_tgt.
+ * Optionally allow the answer to be a referral. */
+static krb5_error_code
+make_request_for_service(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_boolean referral)
+{
+    krb5_error_code code;
+    int extra_options;
+
+    /* Include the caller-specified KDC options in service requests. */
+    extra_options = ctx->req_kdcopt;
+
+    /* Automatically set the enc-tkt-in-skey flag for user-to-user requests. */
+    if (ctx->in_creds->second_ticket.length != 0 &&
+        (extra_options & KDC_OPT_CNAME_IN_ADDL_TKT) == 0)
+        extra_options |= KDC_OPT_ENC_TKT_IN_SKEY;
+
+    /* Set the canonicalize flag for referral requests. */
+    if (referral)
+        extra_options |= KDC_OPT_CANONICALIZE;
+
     /*
-     * See if we already have the ticket cached. To do this usefully
-     * for constrained delegation, we would need to look inside
-     * second_ticket, which we can't do.
+     * Use the profile enctypes for referral requests, since we might get back
+     * a TGT.  We'll ask again with context enctypes if we get the actual
+     * service ticket and it's not consistent with the context enctypes.
      */
-    if ((options & KRB5_GC_CONSTRAINED_DELEGATION) == 0) {
-        retval = krb5int_construct_matching_creds(context, options, in_creds,
-                                                  &mcreds, &fields);
+    if (referral)
+        context->use_conf_ktypes = TRUE;
+    ctx->tgs_in_creds = ctx->in_creds;
+    code = make_request(context, ctx, extra_options);
+    if (referral)
+        context->use_conf_ktypes = FALSE;
+    return code;
+}
+
+/* Decode and decrypt a TGS reply, and set the reply_code or reply_creds field
+ * of ctx with the result.  Also handle too-big errors. */
+static krb5_error_code
+get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_data *reply)
+{
+    krb5_error_code code;
+
+    krb5_free_creds(context, ctx->reply_creds);
+    ctx->reply_creds = NULL;
+    code = krb5int_process_tgs_reply(context, reply, ctx->cur_tgt, ctx->kdcopt,
+                                     ctx->cur_tgt->addresses, NULL,
+                                     ctx->tgs_in_creds, ctx->timestamp,
+                                     ctx->nonce, ctx->subkey, NULL, NULL,
+                                     &ctx->reply_creds);
+    if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) {
+        /* Instruct the caller to re-send the request with TCP. */
+        code = set_caller_request(context, ctx);
+        if (code != 0)
+            return code;
+        return KRB5KRB_ERR_RESPONSE_TOO_BIG;
+    }
 
-        if (retval)
-            return retval;
+    /* Depending on our state, we may or may not be able to handle an error.
+     * For now, store it in the context and return success. */
+    ctx->reply_code = code;
+    return 0;
+}
+
+/* Add realm to ctx->realms_seen so that we can avoid revisiting it later. */
+static krb5_error_code
+remember_realm(krb5_context context, krb5_tkt_creds_context ctx,
+               const krb5_data *realm)
+{
+    size_t len = 0;
+    krb5_data *new_list;
 
-        ncreds = malloc(sizeof(krb5_creds));
-        if (!ncreds)
-            return ENOMEM;
+    if (ctx->realms_seen != NULL) {
+        for (len = 0; ctx->realms_seen[len].data != NULL; len++);
+    }
+    new_list = realloc(ctx->realms_seen, (len + 2) * sizeof(krb5_data));
+    if (new_list == NULL)
+        return ENOMEM;
+    ctx->realms_seen = new_list;
+    new_list[len] = empty_data();
+    new_list[len + 1] = empty_data();
+    return krb5int_copy_data_contents(context, realm, &new_list[len]);
+}
 
-        memset(ncreds, 0, sizeof(krb5_creds));
-        ncreds->magic = KV5M_CREDS;
+/* Return TRUE if realm appears to ctx->realms_seen. */
+static krb5_boolean
+seen_realm_before(krb5_context context, krb5_tkt_creds_context ctx,
+                  const krb5_data *realm)
+{
+    size_t i;
 
-        retval = krb5_cc_retrieve_cred(context, ccache, fields, &mcreds,
-                                       ncreds);
-        if (retval == 0) {
-            *out_creds = ncreds;
-            return 0;
+    if (ctx->realms_seen != NULL) {
+        for (i = 0; ctx->realms_seen[i].data != NULL; i++) {
+            if (data_eq(ctx->realms_seen[i], *realm))
+                return TRUE;
         }
-        free(ncreds);
-        ncreds = NULL;
-        if ((retval != KRB5_CC_NOTFOUND && retval != KRB5_CC_NOT_KTYPE)
-            || options & KRB5_GC_CACHED)
-            return retval;
-        not_ktype = (retval == KRB5_CC_NOT_KTYPE);
-    } else if (options & KRB5_GC_CACHED)
-        return KRB5_CC_NOTFOUND;
+    }
+    return FALSE;
+}
+
+/***** STATE_COMPLETE *****/
+
+/* Check and cache the desired credential when we receive it.  Expects the
+ * received credential to be in ctx->reply_creds. */
+static krb5_error_code
+complete(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+
+    /* Note the authdata we asked for in the output creds. */
+    ctx->reply_creds->authdata = ctx->authdata;
+    ctx->authdata = NULL;
+
+    /* Cache the credential if desired. */
+    if (!(ctx->req_options & KRB5_GC_NO_STORE)) {
+        code = krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
+        if (code != 0)
+            return code;
+    }
+
+    /* If we were doing constrained delegation, make sure we got a forwardable
+     * ticket, or it won't work. */
+    if ((ctx->req_options & KRB5_GC_CONSTRAINED_DELEGATION)
+        && (ctx->reply_creds->ticket_flags & TKT_FLG_FORWARDABLE) == 0)
+        return KRB5_TKT_NOT_FORWARDABLE;
+
+    ctx->state = STATE_COMPLETE;
+    return 0;
+}
+
+/***** STATE_NON_REFERRAL *****/
+
+/* Process the response to a non-referral request. */
+static krb5_error_code
+step_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    /* No fallbacks if we didn't get a successful reply. */
+    if (ctx->reply_code)
+        return ctx->reply_code;
+
+    return complete(context, ctx);
+}
+
+/* Make a non-referrals request for the desired service ticket. */
+static krb5_error_code
+begin_non_referral(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    ctx->state = STATE_NON_REFERRAL;
+    return make_request_for_service(context, ctx, FALSE);
+}
+
+/***** STATE_REFERRALS *****/
+
+/*
+ * Possibly retry a request in the fallback realm after a referral request
+ * failure in the local realm.  Expects ctx->reply_code to be set to the error
+ * from a referral request.
+ */
+static krb5_error_code
+try_fallback_realm(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    char **hrealms;
+
+    /* Only fall back if our error was from the first referral request. */
+    if (ctx->referral_count > 1)
+        return ctx->reply_code;
+
+    /* Only fall back if the original request used the referral realm. */
+    if (!krb5_is_referral_realm(&ctx->req_server->realm))
+        return ctx->reply_code;
+
+    if (ctx->server->length < 2) {
+        /* We need a type/host format principal to find a fallback realm. */
+        return KRB5_ERR_HOST_REALM_UNKNOWN;
+    }
+
+    /* We expect this to give exactly one answer (XXX clean up interface). */
+    code = krb5_get_fallback_host_realm(context, &ctx->server->data[1],
+                                        &hrealms);
+    if (code != 0)
+        return code;
+
+    /* Give up if the fallback realm isn't any different. */
+    if (data_eq_string(ctx->server->realm, hrealms[0]))
+        return ctx->reply_code;
+
+    /* Rewrite server->realm to be the fallback realm. */
+    krb5_free_data_contents(context, &ctx->server->realm);
+    ctx->server->realm = string2data(hrealms[0]);
+    free(hrealms);
+
+    /* Obtain a TGT for the new service realm. */
+    ctx->getting_tgt_for = STATE_NON_REFERRAL;
+    return begin_get_tgt(context, ctx);
+}
+
+/* Return true if context contains app-provided TGS enctypes and enctype is not
+ * one of them. */
+static krb5_boolean
+wrong_enctype(krb5_context context, krb5_enctype enctype)
+{
+    size_t i;
+
+    if (context->tgs_etypes == NULL)
+        return FALSE;
+    for (i = 0; context->tgs_etypes[i] != 0; i++) {
+        if (enctype == context->tgs_etypes[i])
+            return FALSE;
+    }
+    return TRUE;
+}
+
+/* Advance the referral request loop. */
+static krb5_error_code
+step_referrals(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    const krb5_data *referral_realm;
+
+    /* Possibly retry with the fallback realm on error. */
+    if (ctx->reply_code != 0)
+        return try_fallback_realm(context, ctx);
+
+    if (krb5_principal_compare(context, ctx->reply_creds->server,
+                               ctx->server)) {
+        /* We got the ticket we asked for... but we didn't necessarily ask for
+         * it with the right enctypes.  Try a non-referral request if so. */
+        if (wrong_enctype(context, ctx->reply_creds->keyblock.enctype))
+            return begin_non_referral(context, ctx);
+
+        /* Note the authdata we asked for in the output creds. */
+        return complete(context, ctx);
+    }
+
+    /* Old versions of Active Directory can rewrite the server name instead of
+     * returning a referral.  Try a non-referral query if we see this. */
+    if (!IS_TGS_PRINC(context, ctx->reply_creds->server))
+        return begin_non_referral(context, ctx);
+
+    if (ctx->referral_count == 1) {
+        /* Cache the referral TGT only if it's from the local realm.
+         * Make sure to note the associated authdata, if any. */
+        code = krb5_copy_authdata(context, ctx->authdata,
+                                  &ctx->reply_creds->authdata);
+        if (code != 0)
+            return code;
+        code = krb5_cc_store_cred(context, ctx->ccache, ctx->reply_creds);
+        if (code != 0)
+            return code;
+
+        /* The authdata in this TGT will be copied into subsequent TGTs or the
+         * final credentials, so we don't need to request it again. */
+        krb5_free_authdata(context, ctx->in_creds->authdata);
+        ctx->in_creds->authdata = NULL;
+    }
+
+    /* Give up if we've gotten too many referral TGTs. */
+    if (ctx->referral_count++ >= KRB5_REFERRAL_MAXHOPS)
+        return KRB5_KDC_UNREACH;
+
+    /* Check for referral loops. */
+    referral_realm = &ctx->reply_creds->server->data[1];
+    if (seen_realm_before(context, ctx, referral_realm))
+        return KRB5_KDC_UNREACH;
+    code = remember_realm(context, ctx, referral_realm);
+    if (code != 0)
+        return code;
+
+    /* Use the referral TGT for the next request. */
+    krb5_free_creds(context, ctx->cur_tgt);
+    ctx->cur_tgt = ctx->reply_creds;
+    ctx->reply_creds = NULL;
+
+    /* Rewrite the server realm to be the referral realm. */
+    krb5_free_data_contents(context, &ctx->server->realm);
+    code = krb5int_copy_data_contents(context, referral_realm,
+                                      &ctx->server->realm);
+    if (code != 0)
+        return code;
+
+    /* Generate the next referral request. */
+    return make_request_for_service(context, ctx, TRUE);
+}
+
+/*
+ * Begin the referrals request loop.  Expects ctx->cur_tgt to be a TGT for
+ * ctx->realm->server.
+ */
+static krb5_error_code
+begin_referrals(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    ctx->state = STATE_REFERRALS;
+    ctx->referral_count = 1;
+
+    /* Empty out the realms-seen list for loop checking. */
+    krb5int_free_data_list(context, ctx->realms_seen);
+    ctx->realms_seen = NULL;
+
+    /* Generate the first referral request. */
+    return make_request_for_service(context, ctx, TRUE);
+}
+
+/***** STATE_GET_TGT_OFFPATH *****/
+
+/*
+ * Foreign TGT acquisition can happen either before the referrals loop, if the
+ * service principal had an explicitly specified foreign realm, or after it
+ * fails, if we wind up using the fallback realm.  end_get_tgt() advances to
+ * the appropriate state depending on which we were doing.
+ */
+static krb5_error_code
+end_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    if (ctx->getting_tgt_for == STATE_REFERRALS)
+        return begin_referrals(context, ctx);
+    else
+        return begin_non_referral(context, ctx);
+}
+
+/*
+ * We enter STATE_GET_TGT_OFFPATH from STATE_GET_TGT if we receive, from one of
+ * the KDCs in the expected path, a TGT for a realm not in the path.  This may
+ * happen if the KDC has a different idea of the expected path than we do.  If
+ * it happens, we repeatedly ask the KDC of the TGT we have for a destination
+ * realm TGT, until we get it, fail, or give up.
+ */
+
+/* Advance the process of chasing off-path TGTs. */
+static krb5_error_code
+step_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    const krb5_data *tgt_realm;
+
+    /* We have no fallback if the last request failed, so just give up. */
+    if (ctx->reply_code != 0)
+        return ctx->reply_code;
+
+    /* Verify that we got a TGT. */
+    if (!IS_TGS_PRINC(context, ctx->reply_creds->server))
+        return KRB5_KDCREP_MODIFIED;
+
+    /* Use this tgt for the next request. */
+    krb5_free_creds(context, ctx->cur_tgt);
+    ctx->cur_tgt = ctx->reply_creds;
+    ctx->reply_creds = NULL;
+
+    /* Check if we've seen this realm before, and remember it. */
+    tgt_realm = &ctx->cur_tgt->server->data[1];
+    if (seen_realm_before(context, ctx, tgt_realm))
+        return KRB5_KDC_UNREACH;
+    code = remember_realm(context, ctx, tgt_realm);
+    if (code != 0)
+        return code;
+
+    if (data_eq(*tgt_realm, ctx->server->realm)) {
+        /* We received the server realm TGT we asked for. */
+        return end_get_tgt(context, ctx);
+    } else if (ctx->offpath_count++ >= KRB5_REFERRAL_MAXHOPS) {
+        /* Time to give up. */
+        return KRB5_KDCREP_MODIFIED;
+    }
 
+    return make_request_for_tgt(context, ctx, &ctx->server->realm);
+}
+
+/* Begin chasing off-path referrals, starting from ctx->cur_tgt. */
+static krb5_error_code
+begin_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    ctx->state = STATE_GET_TGT_OFFPATH;
+    ctx->offpath_count = 1;
+    return make_request_for_tgt(context, ctx, &ctx->server->realm);
+}
+
+/***** STATE_GET_TGT *****/
+
+/*
+ * To obtain a foreign TGT, we first construct a path of realms R1..Rn between
+ * the local realm and the target realm, using krb5_walk_realm_tree().  Usually
+ * this path is based on the domain hierarchy, but it may be altered by
+ * configuration.
+ *
+ * We begin with cur_realm set to the local realm (R1) and next_realm set to
+ * the target realm (Rn).  At each step, we check to see if we have a cached
+ * TGT for next_realm; if not, we ask cur_realm to give us a TGT for
+ * next_realm.  If that fails, we decrement next_realm until we get a
+ * successful answer or reach cur_realm--in which case we've gotten as far as
+ * we can, and have to give up.  If we do get back a TGT, it may or may not be
+ * for the realm we asked for, so we search for it in the path.  The realm of
+ * the TGT we get back becomes cur_realm, and next_realm is reset to the target
+ * realm.  Overall, this is an O(n^2) process in the length of the path, but
+ * the path length will generally be short and the process will usually end
+ * much faster than the worst case.
+ *
+ * In some cases we may get back a TGT for a realm not in the path.  In that
+ * case we enter STATE_GET_TGT_OFFPATH.
+ */
+
+/*
+ * Point *TGT at an allocated credentials structure containing a TGT for realm
+ * retrieved from ctx->ccache.  If we are retrieving a foreign TGT, accept any
+ * issuing realm (i.e. match only the service principal name).  If the TGT is
+ * not found in the cache, return successfully but set *tgt to NULL.
+ */
+static krb5_error_code
+get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx,
+               const krb5_data *realm, krb5_creds **tgt)
+{
+    krb5_creds mcreds;
+    krb5_error_code code;
+    krb5_principal tgtname = NULL;
+    krb5_flags flags;
+    krb5_boolean local_realm = data_eq(*realm, ctx->client->realm);
+
+    *tgt = NULL;
+
+    /* Construct the principal krbtgt/<realm>@<client realm>.  The realm
+     * won't matter unless we're getting the local TGT. */
+    code = krb5int_tgtname(context, realm, &ctx->client->realm, &tgtname);
+    if (code != 0)
+        goto cleanup;
+
+    /* Match the TGT realm only if we're getting the local TGT. */
+    flags = KRB5_TC_SUPPORTED_KTYPES;
+    if (local_realm)
+        flags |= KRB5_TC_MATCH_SRV_NAMEONLY;
+
+    /* Construct a matching cred for the ccache query. */
+    memset(&mcreds, 0, sizeof(mcreds));
+    mcreds.client = ctx->client;
+    mcreds.server = tgtname;
+
+    /* Fetch the TGT credential. */
+    context->use_conf_ktypes = TRUE;
+    code = cache_get(context, ctx->ccache, flags, &mcreds, tgt);
+    context->use_conf_ktypes = FALSE;
+
+    /* Handle not-found errors.  Make a note if we couldn't find a local TGT
+     * because of enctypes. */
+    if (local_realm && code == KRB5_CC_NOT_KTYPE)
+        ctx->cache_code = KRB5_CC_NOT_KTYPE;
+    if (code != 0 && code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
+        goto cleanup;
+    code = 0;
+
+cleanup:
+    krb5_free_principal(context, tgtname);
+    return code;
+}
+
+/* Initialize the realm path fields for getting a TGT for
+ * ctx->server->realm. */
+static krb5_error_code
+init_realm_path(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    krb5_principal *tgt_princ_list = NULL;
+    krb5_data *realm_path;
+    size_t nrealms, i;
+
+    /* Construct a list of TGT principals from client to server.  We will throw
+     * this away after grabbing the remote realms from each principal. */
+    code = krb5_walk_realm_tree(context, &ctx->client->realm,
+                                &ctx->server->realm,
+                                &tgt_princ_list, KRB5_REALM_BRANCH_CHAR);
+    if (code != 0)
+        return code;
+
+    /* Count the number of principals and allocate the realm path. */
+    for (nrealms = 0; tgt_princ_list[nrealms]; nrealms++);
+    assert(nrealms > 1);
+    realm_path = k5alloc((nrealms + 1) * sizeof(*realm_path), &code);
+    if (realm_path == NULL)
+        goto cleanup;
+
+    /* Steal the remote realm field from each TGT principal. */
+    for (i = 0; i < nrealms; i++) {
+        assert(tgt_princ_list[i]->length == 2);
+        realm_path[i] = tgt_princ_list[i]->data[1];
+        tgt_princ_list[i]->data[1].data = NULL;
+    }
+    realm_path[nrealms] = empty_data();
+
+    /* Initialize the realm path fields in ctx. */
+    krb5int_free_data_list(context, ctx->realm_path);
+    ctx->realm_path = realm_path;
+    ctx->last_realm = realm_path + nrealms - 1;
+    ctx->cur_realm = realm_path;
+    ctx->next_realm = ctx->last_realm;
+    realm_path = NULL;
+
+cleanup:
+    krb5_free_realm_tree(context, tgt_princ_list);
+    return 0;
+}
+
+/* Find realm within the portion of ctx->realm_path following
+ * ctx->cur_realm.  Return NULL if it is not found. */
+static const krb5_data *
+find_realm_in_path(krb5_context context, krb5_tkt_creds_context ctx,
+                   const krb5_data *realm)
+{
+    const krb5_data *r;
+
+    for (r = ctx->cur_realm + 1; r->data != NULL; r++) {
+        if (data_eq(*r, *realm))
+            return r;
+    }
+    return NULL;
+}
+
+/*
+ * Generate the next request in the path traversal.  If a cached TGT for the
+ * target realm appeared in the ccache since we started the TGT acquisition
+ * process, this function may invoke end_get_tgt().
+ */
+static krb5_error_code
+get_tgt_request(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    krb5_creds *cached_tgt;
+
+    while (1) {
+        /* Check if we have a cached TGT for the target realm. */
+        code = get_cached_tgt(context, ctx, ctx->next_realm, &cached_tgt);
+        if (code != 0)
+            return code;
+        if (cached_tgt != NULL) {
+            /* Advance the current realm and keep going. */
+            krb5_free_creds(context, ctx->cur_tgt);
+            ctx->cur_tgt = cached_tgt;
+            if (ctx->next_realm == ctx->last_realm)
+                return end_get_tgt(context, ctx);
+            ctx->cur_realm = ctx->next_realm;
+            ctx->next_realm = ctx->last_realm;
+            continue;
+        }
+
+        return make_request_for_tgt(context, ctx, ctx->next_realm);
+    }
+}
+
+/* Process a TGS reply and advance the path traversal to get a foreign TGT. */
+static krb5_error_code
+step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    const krb5_data *tgt_realm, *path_realm;
+
+    if (ctx->reply_code != 0) {
+        /* The last request failed.  Try the next-closest realm to
+         * ctx->cur_realm. */
+        ctx->next_realm--;
+        if (ctx->next_realm == ctx->cur_realm) {
+            /* We've tried all the realms we could and couldn't progress beyond
+             * ctx->cur_realm, so it's time to give up. */
+            return ctx->reply_code;
+        }
+    } else {
+        /* Verify that we got a TGT. */
+        if (!IS_TGS_PRINC(context, ctx->reply_creds->server))
+            return KRB5_KDCREP_MODIFIED;
+
+        /* Use this tgt for the next request regardless of what it is. */
+        krb5_free_creds(context, ctx->cur_tgt);
+        ctx->cur_tgt = ctx->reply_creds;
+        ctx->reply_creds = NULL;
+
+        /* Remember that we saw this realm. */
+        tgt_realm = &ctx->cur_tgt->server->data[1];
+        code = remember_realm(context, ctx, tgt_realm);
+        if (code != 0)
+            return code;
+
+        /* See where we wound up on the path (or off it). */
+        path_realm = find_realm_in_path(context, ctx, tgt_realm);
+        if (path_realm != NULL) {
+            /* We got a realm on the expected path, so we can cache it. */
+            code = krb5_cc_store_cred(context, ctx->ccache, ctx->cur_tgt);
+            if (code != 0)
+                return code;
+            if (path_realm == ctx->last_realm) {
+                /* We received a TGT for the target realm. */
+                return end_get_tgt(context, ctx);
+            } else if (path_realm != NULL) {
+                /* We still have further to go; advance the traversal. */
+                ctx->cur_realm = path_realm;
+                ctx->next_realm = ctx->last_realm;
+            }
+        } else if (data_eq(*tgt_realm, ctx->client->realm)) {
+            /* We were referred back to the local realm, which is bad. */
+            return KRB5_KDCREP_MODIFIED;
+        } else {
+            /* We went off the path; start the off-path chase. */
+            return begin_get_tgt_offpath(context, ctx);
+        }
+    }
+
+    /* Generate the next request in the path traversal. */
+    return get_tgt_request(context, ctx);
+}
+
+/*
+ * Begin the process of getting a foreign TGT, either for the explicitly
+ * specified server realm or for the fallback realm.  Expects that
+ * ctx->server->realm is the realm of the desired TGT, and that
+ * ctx->getting_tgt_for is the state we should advance to after we have the
+ * desired TGT.
+ */
+static krb5_error_code
+begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    krb5_creds *cached_tgt;
+
+    ctx->state = STATE_GET_TGT;
+
+    /* See if we have a cached TGT for the server realm. */
+    code = get_cached_tgt(context, ctx, &ctx->server->realm, &cached_tgt);
+    if (code != 0)
+        return code;
+    if (cached_tgt != NULL) {
+        krb5_free_creds(context, ctx->cur_tgt);
+        ctx->cur_tgt = cached_tgt;
+        return end_get_tgt(context, ctx);
+    }
+
+    /* Initialize the realm path. */
+    code = init_realm_path(context, ctx);
+    if (code != 0)
+        return code;
+
+    /* Start with the local tgt. */
+    krb5_free_creds(context, ctx->cur_tgt);
+    ctx->cur_tgt = NULL;
+    code = get_cached_tgt(context, ctx, &ctx->client->realm, &ctx->cur_tgt);
+    if (code != 0)
+        return code;
+    if (ctx->cur_tgt == NULL)
+        return ctx->cache_code;
+
+    /* Empty out the realms-seen list for loop checking. */
+    krb5int_free_data_list(context, ctx->realms_seen);
+    ctx->realms_seen = NULL;
+
+    /* Generate the first request. */
+    return get_tgt_request(context, ctx);
+}
+
+/***** STATE_BEGIN *****/
+
+/*
+ * Look for the desired credentials in the cache, if possible.  If we find
+ * them, put them in ctx->reply_creds and advance the state to STATE_COMPLETE.
+ * Return successfully even if creds are not found, unless the caller only
+ * wanted cached creds.
+ */
+static krb5_error_code
+check_cache(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    krb5_creds mcreds;
+    krb5_flags fields;
+
+    /* For constrained delegation, the expected result is in second_ticket, so
+     * we can't really do a cache check here. */
+    if (ctx->req_options & KRB5_GC_CONSTRAINED_DELEGATION)
+        return (ctx->req_options & KRB5_GC_CACHED) ? KRB5_CC_NOTFOUND : 0;
+
+    /* Perform the cache lookup. */
+    code = krb5int_construct_matching_creds(context, ctx->req_options,
+                                            ctx->in_creds, &mcreds, &fields);
+    if (code)
+        return code;
+    code = cache_get(context, ctx->ccache, fields, &mcreds, &ctx->reply_creds);
+    if (code == 0) {
+        ctx->state = STATE_COMPLETE;
+        return 0;
+    }
+
+    /* Stop on unexpected cache errors. */
+    if (code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE)
+        return code;
+
+    /* Stop if the caller only wanted cached creds. */
+    if (ctx->req_options & KRB5_GC_CACHED)
+        return code;
+
+    /* Remember whether the cache lookup failed due to enctypes or not. */
+    ctx->cache_code = code;
+    return 0;
+}
+
+/* Decide where to begin the acquisition process. */
+static krb5_error_code
+begin(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+
+    code = check_cache(context, ctx);
+    if (code != 0 || ctx->state == STATE_COMPLETE)
+        return code;
+
+    /* If the server realm is unspecified, start with the client realm. */
+    if (krb5_is_referral_realm(&ctx->server->realm)) {
+        krb5_free_data_contents(context, &ctx->server->realm);
+        code = krb5int_copy_data_contents(context, &ctx->client->realm,
+                                          &ctx->server->realm);
+        if (code != 0)
+            return code;
+    }
+
+    /* Obtain a TGT for the service realm. */
+    ctx->getting_tgt_for = STATE_REFERRALS;
+    return begin_get_tgt(context, ctx);
+}
+
+/***** API functions *****/
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache,
+                    krb5_creds *in_creds, krb5_flags options,
+                    krb5_tkt_creds_context *pctx)
+{
+    krb5_error_code code;
+    krb5_tkt_creds_context ctx = NULL;
+
+    ctx = k5alloc(sizeof(*ctx), &code);
+    if (ctx == NULL)
+        goto cleanup;
+
+    ctx->req_options = options;
+    ctx->req_kdcopt = 0;
     if (options & KRB5_GC_CANONICALIZE)
-        kdcopt |= KDC_OPT_CANONICALIZE;
+        ctx->req_kdcopt |= KDC_OPT_CANONICALIZE;
     if (options & KRB5_GC_FORWARDABLE)
-        kdcopt |= KDC_OPT_FORWARDABLE;
+        ctx->req_kdcopt |= KDC_OPT_FORWARDABLE;
     if (options & KRB5_GC_NO_TRANSIT_CHECK)
-        kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
+        ctx->req_kdcopt |= KDC_OPT_DISABLE_TRANSITED_CHECK;
     if (options & KRB5_GC_CONSTRAINED_DELEGATION) {
-        if (options & KRB5_GC_USER_USER)
-            return EINVAL;
-        kdcopt |= KDC_OPT_FORWARDABLE | KDC_OPT_CNAME_IN_ADDL_TKT;
+        if (options & KRB5_GC_USER_USER) {
+            code = EINVAL;
+            goto cleanup;
+        }
+        ctx->req_kdcopt |= KDC_OPT_FORWARDABLE | KDC_OPT_CNAME_IN_ADDL_TKT;
     }
 
-    retval = krb5_get_cred_from_kdc_opt(context, ccache, in_creds,
-                                        &ncreds, &tgts, kdcopt);
-    if (tgts) {
-        /* Attempt to cache intermediate ticket-granting tickets. */
-        for (tgts_iter = tgts; *tgts_iter; tgts_iter++)
-            (void) krb5_cc_store_cred(context, ccache, *tgts_iter);
-        krb5_free_tgt_creds(context, tgts);
+    ctx->state = STATE_BEGIN;
+    ctx->cache_code = KRB5_CC_NOTFOUND;
+
+    code = krb5_copy_creds(context, in_creds, &ctx->in_creds);
+    if (code != 0)
+        goto cleanup;
+    ctx->client = ctx->in_creds->client;
+    ctx->server = ctx->in_creds->server;
+    code = krb5_copy_principal(context, ctx->server, &ctx->req_server);
+    if (code != 0)
+        goto cleanup;
+    code = krb5_cc_dup(context, ccache, &ctx->ccache);
+    if (code != 0)
+        goto cleanup;
+    code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata);
+    if (code != 0)
+        goto cleanup;
+
+    *pctx = ctx;
+    ctx = NULL;
+
+cleanup:
+    krb5_tkt_creds_free(context, ctx);
+    return code;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_creds *creds)
+{
+    if (ctx->state != STATE_COMPLETE)
+        return KRB5_NO_TKT_SUPPLIED;
+    return krb5int_copy_creds_contents(context, ctx->reply_creds, creds);
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx,
+                         krb5_ticket_times *times)
+{
+    if (ctx->state != STATE_COMPLETE)
+        return KRB5_NO_TKT_SUPPLIED;
+    *times = ctx->reply_creds->times;
+    return 0;
+}
+
+void KRB5_CALLCONV
+krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    if (ctx == NULL)
+        return;
+    krb5_free_creds(context, ctx->in_creds);
+    krb5_cc_close(context, ctx->ccache);
+    krb5_free_principal(context, ctx->req_server);
+    krb5_free_authdata(context, ctx->authdata);
+    krb5_free_creds(context, ctx->cur_tgt);
+    krb5int_free_data_list(context, ctx->realms_seen);
+    krb5_free_principal(context, ctx->tgt_princ);
+    krb5_free_keyblock(context, ctx->subkey);
+    krb5_free_data_contents(context, &ctx->previous_request);
+    krb5int_free_data_list(context, ctx->realm_path);
+    krb5_free_creds(context, ctx->reply_creds);
+    free(ctx);
+}
+
+krb5_error_code
+krb5_tkt_creds_get(krb5_context context, krb5_tkt_creds_context ctx)
+{
+    krb5_error_code code;
+    krb5_data request = empty_data(), reply = empty_data();
+    krb5_data realm = empty_data();
+    unsigned int flags = 0;
+    int tcp_only = 0, use_master;
+
+    for (;;) {
+        /* Get the next request and realm.  Turn on TCP if necessary. */
+        code = krb5_tkt_creds_step(context, ctx, &reply, &request, &realm,
+                                   &flags);
+        if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only)
+            tcp_only = 1;
+        else if (code != 0 || !(flags & KRB5_TKT_CREDS_CONTINUE))
+            break;
+        krb5_free_data_contents(context, &reply);
+
+        /* Send it to a KDC for the appropriate realm. */
+        use_master = 0;
+        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);
     }
 
-    /*
-     * Translate KRB5_CC_NOTFOUND if we previously got
-     * KRB5_CC_NOT_KTYPE from krb5_cc_retrieve_cred(), in order to
-     * handle the case where there is no TGT in the ccache and the
-     * input enctype didn't match.  This handling is necessary because
-     * some callers, such as GSSAPI, iterate through enctypes and
-     * KRB5_CC_NOTFOUND passed through from the
-     * krb5_get_cred_from_kdc() is semantically incorrect, since the
-     * actual failure was the non-existence of a ticket of the correct
-     * enctype rather than the missing TGT.
-     */
-    if ((retval == KRB5_CC_NOTFOUND || retval == KRB5_CC_NOT_KTYPE)
-        && not_ktype)
-        return KRB5_CC_NOT_KTYPE;
-    else if (retval)
-        return retval;
-
-    if ((options & KRB5_GC_CONSTRAINED_DELEGATION)
-        && (ncreds->ticket_flags & TKT_FLG_FORWARDABLE) == 0) {
-        /* This ticket won't work for constrained delegation. */
-        krb5_free_creds(context, ncreds);
-        return KRB5_TKT_NOT_FORWARDABLE;
+    krb5_free_data_contents(context, &request);
+    krb5_free_data_contents(context, &reply);
+    krb5_free_data_contents(context, &realm);
+    return code;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_tkt_creds_step(krb5_context context, krb5_tkt_creds_context ctx,
+                    krb5_data *in, krb5_data *out, krb5_data *realm,
+                    unsigned int *flags)
+{
+    krb5_error_code code;
+    krb5_boolean no_input = (in == NULL || in->length == 0);
+
+    *out = empty_data();
+    *realm = empty_data();
+    *flags = 0;
+
+    /* We should receive an empty input on the first step only, and should not
+     * get called after completion. */
+    if (no_input != (ctx->state == STATE_BEGIN) ||
+        ctx->state == STATE_COMPLETE)
+        return EINVAL;
+
+    ctx->caller_out = out;
+    ctx->caller_realm = realm;
+    ctx->caller_flags = flags;
+
+    if (!no_input) {
+        /* Convert the input token into a credential and store it in ctx. */
+        code = get_creds_from_tgs_reply(context, ctx, in);
+        if (code != 0)
+            return code;
     }
 
-    /* Attempt to cache the returned ticket. */
-    if (!(options & KRB5_GC_NO_STORE))
-        (void) krb5_cc_store_cred(context, ccache, ncreds);
+    if (ctx->state == STATE_BEGIN)
+        return begin(context, ctx);
+    else if (ctx->state == STATE_GET_TGT)
+        return step_get_tgt(context, ctx);
+    else if (ctx->state == STATE_GET_TGT_OFFPATH)
+        return step_get_tgt_offpath(context, ctx);
+    else if (ctx->state == STATE_REFERRALS)
+        return step_referrals(context, ctx);
+    else if (ctx->state == STATE_NON_REFERRAL)
+        return step_non_referral(context, ctx);
+    else
+        return EINVAL;
+}
+
+krb5_error_code KRB5_CALLCONV
+krb5_get_credentials(krb5_context context, krb5_flags options,
+                     krb5_ccache ccache, krb5_creds *in_creds,
+                     krb5_creds **out_creds)
+{
+    krb5_error_code code;
+    krb5_creds *ncreds = NULL;
+    krb5_tkt_creds_context ctx = NULL;
+
+    *out_creds = NULL;
+
+    /* Allocate a container. */
+    ncreds = k5alloc(sizeof(*ncreds), &code);
+    if (ncreds == NULL)
+        goto cleanup;
+
+    /* Make and execute a krb5_tkt_creds context to get the credential. */
+    code = krb5_tkt_creds_init(context, ccache, in_creds, options, &ctx);
+    if (code != 0)
+        goto cleanup;
+    code = krb5_tkt_creds_get(context, ctx);
+    if (code != 0)
+        goto cleanup;
+    code = krb5_tkt_creds_get_creds(context, ctx, ncreds);
+    if (code != 0)
+        goto cleanup;
 
     *out_creds = ncreds;
-    return 0;
+    ncreds = NULL;
+
+cleanup:
+    krb5_free_creds(context, ncreds);
+    krb5_tkt_creds_free(context, ctx);
+    return code;
 }
diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h
index ac30a52..3d42bc1 100644
--- a/src/lib/krb5/krb/int-proto.h
+++ b/src/lib/krb5/krb/int-proto.h
@@ -59,11 +59,6 @@ krb5_preauth_supply_preauth_data(krb5_context context,
                                  const char *value);
 
 krb5_error_code
-krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
-                           krb5_creds *in_cred, krb5_creds **out_cred,
-                           krb5_creds ***tgts, int kdcopt);
-
-krb5_error_code
 krb5int_construct_matching_creds(krb5_context context, krb5_flags options,
                                  krb5_creds *in_creds, krb5_creds *mcreds,
                                  krb5_flags *fields);
diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports
index c3af209..cab6e9b 100644
--- a/src/lib/krb5/libkrb5.exports
+++ b/src/lib/krb5/libkrb5.exports
@@ -313,9 +313,6 @@ krb5_gen_portaddr
 krb5_gen_replay_name
 krb5_generate_seq_number
 krb5_generate_subkey
-krb5_get_cred_from_kdc
-krb5_get_cred_from_kdc_renew
-krb5_get_cred_from_kdc_validate
 krb5_get_cred_via_tkt
 krb5_get_credentials
 krb5_get_credentials_for_proxy
@@ -550,6 +547,11 @@ krb5_string_to_salttype
 krb5_string_to_timestamp
 krb5_sync_disk_file
 krb5int_tgtname
+krb5_tkt_creds_free
+krb5_tkt_creds_get_creds
+krb5_tkt_creds_get_times
+krb5_tkt_creds_init
+krb5_tkt_creds_step
 krb5_timeofday
 krb5_timestamp_to_sfstring
 krb5_timestamp_to_string
-- 
2.6.6

