From: Greg Hudson Date: Wed, 14 Apr 2010 14:36:32 +0000 (+0000) Subject: Introduce new krb5_tkt_creds API X-Git-Tag: krb5-1.9-beta1~285 X-Git-Url: http://git.tremily.us/?a=commitdiff_plain;h=4472eed040cf442a504f24158f80242d6fbd94c2;p=krb5.git 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 --- diff --git a/src/include/k5-int.h b/src/include/k5-int.h index 8b72b06da..fde8e6660 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -2574,15 +2574,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 740390c9a..945d424d2 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 e25ba669a..4384c5e55 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -47,7 +47,6 @@ STLIBOBJS= \ etype_list.o \ fast.o \ fwd_tgt.o \ - gc_frm_kdc.o \ gc_via_tkt.o \ gen_seqnum.o \ gen_subkey.o \ @@ -147,7 +146,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) \ @@ -248,7 +246,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 c9bb9f3d3..000000000 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ /dev/null @@ -1,1400 +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 -#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 *realm_list; - unsigned int nkdcs; - krb5_principal *cur_realm; - krb5_principal *nxt_realm; - krb5_principal *lst_realm; - 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_realm(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_realm_str, *nxt_realm_str; - - cur_tgt_str = cur_realm_str = nxt_realm_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_realm, &cur_realm_str); - if (retval) goto cleanup; - retval = krb5_unparse_name(ts->ctx, *ts->nxt_realm, &nxt_realm_str); - if (retval) goto cleanup; - fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str); - fprintf(stderr, "%s: cur_realm %s\n", prog, cur_realm_str); - fprintf(stderr, "%s: nxt_realm %s\n", prog, nxt_realm_str); -cleanup: - if (cur_tgt_str) - krb5_free_unparsed_name(ts->ctx, cur_tgt_str); - if (cur_realm_str) - krb5_free_unparsed_name(ts->ctx, cur_realm_str); - if (nxt_realm_str) - krb5_free_unparsed_name(ts->ctx, nxt_realm_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 REALM_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->realm_list = NULL; - retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client), - krb5_princ_realm(ts->ctx, server), - &ts->realm_list, KRB5_REALM_BRANCH_CHAR); - if (retval) - return retval; - - for (ts->nkdcs = 0; ts->realm_list[ts->nkdcs]; ts->nkdcs++) { - assert(krb5_princ_size(ts->ctx, ts->realm_list[ts->nkdcs]) == 2); - TR_DBG_RTREE(ts, "init_rtree", ts->realm_list[ts->nkdcs]); - } - assert(ts->nkdcs > 1); - ts->lst_realm = ts->realm_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_realm() - * - * A NXT_TGT gotten from an intermediate KDC might actually be a - * referral. Search REALM_LIST forward starting from CUR_REALM, 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_REALM's remote realm, not local realm, because, among - * other reasons, we can get a referral to the final realm; e.g., - * given - * - * REALM_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_REALM to one entry forward of the match. - */ -static krb5_error_code -find_nxt_realm(struct tr_state *ts) -{ - krb5_data *r1, *r2; - krb5_principal *kdcptr; - - TR_DBG(ts, "find_nxt_realm"); - 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_realm + 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_realm = kdcptr; - TR_DBG_RET(ts, "find_nxt_realm", 0); - return 0; - } - - r2 = krb5_princ_component(ts->ctx, ts->realm_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_realm: 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_realm == ts->realm_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_realm", 0); - return 0; -} - -/* - * try_kdc() - * - * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_REALM 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, - <gtq, &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_realm(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_REALM)@local_realm(CUR_REALM) 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_realm, 1); - rsrc = krb5_princ_component(ts->ctx, *ts->cur_realm, 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_REALM - * 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_realm = ts->lst_realm; - ts->nxt_realm > ts->cur_realm; - ts->nxt_realm--) { - - 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_realm != ts->realm_list || - ts->nxt_realm != ts->lst_realm) { - 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_realm(), 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 principal names, not realm names. We only use the - * remote parts of the TGT principal names (i.e. the second principal - * name component), so to us the list of principal names is logically - * a list of realms. - * - * The do_traversal loop calls next_closest_tgt() to find the next - * closest TGT to the destination realm. next_closest_tgt() updates - * NXT_REALM for the following iteration of the do_traversal() loop. - * - * At the beginning of any given iteration of the do_traversal() loop, - * CUR_REALM's remote realm is the remote realm of CUR_TGT->SERVER. The - * local realms of CUR_REALM and CUR_TGT->SERVER may not match due to - * short-circuit paths provided by intermediate KDCs, e.g., CUR_REALM - * might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B. - * - * For example, given REALM_LIST of - * - * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3, - * krbtgt/R5@R4 - * - * The next_closest_tgt() loop moves NXT_REALM to the left starting from - * R5, stopping before it reaches CUR_REALM. When next_closest_tgt() - * returns, the do_traversal() loop updates CUR_REALM to be NXT_REALM, 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_REALM: - * - * 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_REALM, it may not perform a - * simple increment from CUR_REALM, 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_realm = ts->realm_list, ts->nxt_realm = NULL; - ts->cur_realm != NULL && ts->cur_realm < ts->lst_realm; - ts->cur_realm = ts->nxt_realm, 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_realm != ts->nxt_realm); - } - - 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->realm_list != NULL) - krb5_free_realm_tree(ctx, ts->realm_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;iserver); - 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 / 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" function to set up + * the context fields related to the state, a "step_" function to + * process a reply and update the related context fields, and possibly a + * "_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_ 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/@. */ + 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/@. 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 f7126e3f0..babfeb0a2 100644 --- a/src/lib/krb5/krb/int-proto.h +++ b/src/lib/krb5/krb/int-proto.h @@ -58,11 +58,6 @@ krb5_preauth_supply_preauth_data(krb5_context context, const char *attr, 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, diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 1a1ae4515..58e4ddac7 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