From: Greg Hudson Date: Thu, 8 Apr 2010 03:36:58 +0000 (+0000) Subject: Merge changes from /users/lhoward/iakerb-refonly X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=748919bb73e3a7ac821a5f68ae7700c2292f53dc;p=thirdparty%2Fkrb5.git Merge changes from /users/lhoward/iakerb-refonly git-svn-id: svn://anonsvn.mit.edu/krb5/branches/iakerb@23872 dc483132-0cff-0310-8789-dd5450dbe970 --- diff --git a/src/include/k5-int.h b/src/include/k5-int.h index d5be8afac0..0236594ce7 100644 --- a/src/include/k5-int.h +++ b/src/include/k5-int.h @@ -2601,15 +2601,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/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index a47bebe758..2fad9e9d9d 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -48,7 +48,6 @@ STLIBOBJS= \ fast.o \ fwd_tgt.o \ gc_frm_kdc.o \ - gc_frm_kdc_step.o \ gc_via_tkt.o \ gen_seqnum.o \ gen_subkey.o \ @@ -148,7 +147,6 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)fast.$(OBJEXT) \ $(OUTPRE)fwd_tgt.$(OBJEXT) \ $(OUTPRE)gc_frm_kdc.$(OBJEXT) \ - $(OUTPRE)gc_frm_kdc_step.$(OBJEXT) \ $(OUTPRE)gc_via_tkt.$(OBJEXT) \ $(OUTPRE)gen_seqnum.$(OBJEXT) \ $(OUTPRE)gen_subkey.$(OBJEXT) \ @@ -249,7 +247,6 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/fast.c \ $(srcdir)/fwd_tgt.c \ $(srcdir)/gc_frm_kdc.c \ - $(srcdir)/gc_frm_kdc_step.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 index 9733f7cc3f..c96fde37c3 100644 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ b/src/lib/krb5/krb/gc_frm_kdc.c @@ -1,10 +1,9 @@ /* -*- 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. + * lib/krb5/krb/gc_frm_kdc.c + * + * Copyright (C) 2010 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. @@ -26,1373 +25,1012 @@ * 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. + * krb5_tkt_creds_step() and related functions: + * + * Get credentials from some KDC somewhere, possibly getting (and caching) + * cross-realm TGTs along the way, and possibly following referrals to other + * realms. This is an asynchronous API; it is used by the synchronous API + * krb5_get_credentials(). */ #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. + * 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. * - * CUR_TGT and NXT_TGT can each point either into CC_TGTS or into - * KDC_TGTS. + * The process is divided up into states which govern how the next input token + * should be interpreted. Each state has a "begin_" 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. * - * 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. + * 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). * - * 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 + * 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. */ -#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) +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 */ +}; -#endif +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) */ + int req_kdcopt; /* Caller-requested 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 */ + + /* 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) -/* - * 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) +static krb5_error_code +begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx); /* - * 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. + * 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. */ -#define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES) +static krb5_error_code +set_caller_request(krb5_context context, krb5_tkt_creds_context ctx) +{ + 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(); -/* - * Prototypes of helper functions - */ -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); + 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; -/* - * 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) -{ + *ctx->caller_out = out_copy; + *ctx->caller_realm = realm_copy; + *ctx->caller_flags = 1; + return 0; - 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]; +cleanup: + krb5_free_data_contents(context, &out_copy); + krb5_free_data_contents(context, &realm_copy); + return code; } /* - * 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. + * 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 void -shift_cc_tgts(struct tr_state *ts) +static krb5_error_code +get_cached_tgt(krb5_context context, krb5_tkt_creds_context ctx, + const krb5_data *realm, krb5_creds **tgt) { - unsigned int i; - struct cc_tgts *rb; + krb5_creds mcreds, *creds = NULL; + krb5_error_code code; + krb5_principal tgtname = NULL; + krb5_flags flags; - rb = &ts->cc_tgts; - i = rb->cur = rb->nxt; - rb->dirty[i] = 1; - ts->cur_cc_tgt = ts->nxt_cc_tgt; + *tgt = NULL; - i = (i + 1) % NCC_TGTS; + /* 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; - 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; - } -} + /* Match the TGT realm only if we're getting the local TGT. */ + flags = KRB5_TC_SUPPORTED_KTYPES; + if (!data_eq(*realm, ctx->client->realm)) + flags |= KRB5_TC_MATCH_SRV_NAMEONLY; -/* - * 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; - } + /* Allocate a structure for the resulting creds. */ + creds = k5alloc(sizeof(*creds), &code); + if (creds == NULL) + goto cleanup; + + /* Construct a matching cred for the ccache query. */ + memset(&mcreds, 0, sizeof(mcreds)); + mcreds.client = ctx->client; + mcreds.server = tgtname; + + /* Fetch the TGT credential, handling not-found errors. */ + context->use_conf_ktypes = TRUE; + code = krb5_cc_retrieve_cred(context, ctx->ccache, flags, &mcreds, + creds); + context->use_conf_ktypes = FALSE; + if (code != 0 && code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) + goto cleanup; + if (code == 0) { + *tgt = creds; + creds = NULL; } + code = 0; + +cleanup: + krb5_free_principal(context, tgtname); + free(creds); + return code; } /* - * Debug support + * 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. */ -#ifdef DEBUG_GC_FRM_KDC -static void -tr_dbg(struct tr_state *ts, const char *prog) +static krb5_error_code +make_request(krb5_context context, krb5_tkt_creds_context ctx, + int extra_options) { - 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); -} + krb5_error_code code; + krb5_data request = empty_data(); -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)); -} + ctx->kdcopt = extra_options | FLAGS2OPTS(ctx->cur_tgt->ticket_flags); -static void -tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ) -{ - char *str; + /* 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; - if (krb5_unparse_name(ts->ctx, princ, &str)) - return; - fprintf(stderr, "%s: %s\n", prog, str); - krb5_free_unparsed_name(ts->ctx, str); + 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); } -#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(). - */ -krb5_error_code -krb5int_tgt_mcred(krb5_context ctx, krb5_principal client, - krb5_principal dst, krb5_principal src, - krb5_creds *mcreds) +/* 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 retval; + 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; +} - retval = 0; - memset(mcreds, 0, sizeof(*mcreds)); +/* 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; - retval = krb5_copy_principal(ctx, client, &mcreds->client); - if (retval) - goto cleanup; + /* Include the caller-specified KDC options in service requests. */ + extra_options = ctx->kdcopt; - retval = krb5int_tgtname(ctx, krb5_princ_realm(ctx, dst), - krb5_princ_realm(ctx, src), &mcreds->server); - if (retval) - goto cleanup; + /* 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; -cleanup: - if (retval) - krb5_free_cred_contents(ctx, mcreds); + /* Set the canonicalize flag for referral requests. */ + if (referral) + extra_options |= KDC_OPT_CANONICALIZE; - return retval; + /* + * 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 (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; } -/* - * init_rtree() - * - * Populate REALM_LIST with the output of krb5_walk_realm_tree(). - */ +/* 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 -init_rtree(struct tr_state *ts, - krb5_principal client, krb5_principal server) +get_creds_from_tgs_reply(krb5_context context, krb5_tkt_creds_context ctx, + krb5_data *reply) { - 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]); + 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; } - 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; + /* 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; } -/* - * retr_local_tgt() - * - * Prime CUR_TGT with the cached TGT of the client's local realm. - */ +/* Add realm to ctx->realms_seen so that we can avoid revisiting it later. */ static krb5_error_code -retr_local_tgt(struct tr_state *ts, krb5_principal client) +remember_realm(krb5_context context, krb5_tkt_creds_context ctx, + const krb5_data *realm) { - krb5_error_code retval; - krb5_creds tgtq; - - memset(&tgtq, 0, sizeof(tgtq)); - retval = krb5int_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; + size_t len = 0; + krb5_data *new_list; + + if (ctx->realms_seen != NULL) { + for (len = 0; ctx->realms_seen[len].data != NULL; len++); } - return retval; + 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]); } -/* - * 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) +/* 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) { - 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; + size_t i; + + 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; + } } - TR_DBG_RET(ts, "try_ccache", retval); - return retval; + return FALSE; +} + +/***** 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; + + /* Note the authdata we asked for in the output creds. */ + ctx->reply_creds->authdata = ctx->authdata; + ctx->authdata = NULL; + ctx->state = STATE_COMPLETE; + return 0; +} + +/* 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 *****/ + /* - * 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. + * 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 -find_nxt_realm(struct tr_state *ts) +try_fallback_realm(krb5_context context, krb5_tkt_creds_context ctx) { - krb5_data *r1, *r2; - krb5_principal *kdcptr; + krb5_error_code code; + char **hrealms; - 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; + /* Only fall back if our error was from the first referral request. */ + if (ctx->referral_count > 1) + return ctx->reply_code; - r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1); + /* Only fall back if the original request used the referral realm. */ + if (!krb5_is_referral_realm(&ctx->req_server->realm)) + return ctx->reply_code; - for (kdcptr = ts->cur_realm + 1; *kdcptr != NULL; kdcptr++) { + if (ctx->server->length < 2) { + /* We need a type/host format principal to find a fallback realm. */ + return KRB5_ERR_HOST_REALM_UNKNOWN; + } - r2 = krb5_princ_component(ts->ctx, *kdcptr, 1); + /* 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; - if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) { - break; - } + /* 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; } - if (*kdcptr != NULL) { - ts->nxt_realm = kdcptr; - TR_DBG_RET(ts, "find_nxt_realm", 0); + 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. */ + ctx->reply_creds->authdata = ctx->authdata; + ctx->authdata = NULL; + ctx->state = STATE_COMPLETE; 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; + /* 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; } - /* - * 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; + /* 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); } /* - * try_kdc() - * - * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_REALM if - * successful. + * Begin the referrals request loop. Expects ctx->cur_tgt to be a TGT for + * ctx->realm->server. */ static krb5_error_code -try_kdc(struct tr_state *ts, krb5_creds *tgtq) +begin_referrals(krb5_context context, krb5_tkt_creds_context ctx) { - krb5_error_code retval; - krb5_creds ltgtq; + ctx->state = STATE_REFERRALS; + ctx->referral_count = 1; - 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; + /* Empty out the realms-seen list for loop checking. */ + krb5int_free_data_list(context, ctx->realms_seen); + ctx->realms_seen = NULL; - 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; + /* Generate the first referral request. */ + return make_request_for_service(context, ctx, TRUE); } +/***** STATE_GET_TGT_OFFPATH *****/ + /* - * 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. + * 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 -kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds) +end_get_tgt(krb5_context context, krb5_tkt_creds_context ctx) { - krb5_error_code retval; - krb5_data *rdst, *rsrc; + if (ctx->getting_tgt_for == STATE_REFERRALS) + return begin_referrals(context, ctx); + else + return begin_non_referral(context, ctx); +} - retval = 0; - memset(mcreds, 0, sizeof(*mcreds)); +/* + * 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. + */ - 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; +/* 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; - retval = krb5int_tgtname(ts->ctx, rdst, rsrc, &mcreds->server); - if (retval) - goto cleanup; + /* We have no fallback if the last request failed, so just give up. */ + if (ctx->reply_code != 0) + return ctx->reply_code; -cleanup: - if (retval) - krb5_free_cred_contents(ts->ctx, mcreds); + /* Verify that we got a TGT. */ + if (!IS_TGS_PRINC(context, ctx->reply_creds->server)) + return KRB5_KDCREP_MODIFIED; - return retval; + /* 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); } -/* - * 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. - */ +/* Begin chasing off-path referrals, starting from ctx->cur_tgt. */ static krb5_error_code -next_closest_tgt(struct tr_state *ts, krb5_principal client) +begin_get_tgt_offpath(krb5_context context, krb5_tkt_creds_context ctx) { - 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; + ctx->state = STATE_GET_TGT_OFFPATH; + ctx->offpath_count = 1; + return make_request_for_tgt(context, ctx, &ctx->server->realm); } +/***** STATE_GET_TGT *****/ + /* - * 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 | - * +----+----+----+----+----+ + * 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. * - * do_traversal() updates CUR_REALM: + * 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. * - * 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. + * 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. */ + +/* Initialize the realm path fields for getting a TGT for + * ctx->server->realm. */ 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) +init_realm_path(krb5_context context, krb5_tkt_creds_context ctx) { - 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) + 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; - retval = retr_local_tgt(ts, client); - if (retval) - 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(); - 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) { + /* 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; - retval = next_closest_tgt(ts, client); - if (retval) - goto cleanup; +cleanup: + krb5_free_realm_tree(context, tgt_princ_list); + return 0; +} - if (ts->offpath_tgt != NULL) { - retval = chase_offpath(ts, client, server); - if (retval) - goto cleanup; - break; - } - assert(ts->cur_realm != ts->nxt_realm); - } +/* 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; - 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; + for (r = ctx->cur_realm + 1; r->data != NULL; r++) { + if (data_eq(*r, *realm)) + return r; } - -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; + return NULL; } /* - * 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. + * 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 -chase_offpath(struct tr_state *ts, - krb5_principal client, krb5_principal server) +get_tgt_request(krb5_context context, krb5_tkt_creds_context ctx) { - 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; + 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; } - 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; + return make_request_for_tgt(context, ctx, ctx->next_realm); } - 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. - */ +/* Process a TGS reply and advance the path traversal to get a foreign TGT. */ static krb5_error_code -offpath_loopchk(struct tr_state *ts, - krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount) +step_get_tgt(krb5_context context, krb5_tkt_creds_context ctx) { - 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)) + 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; - } - 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)) + + /* 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); + } } - return 0; + + /* Generate the next request in the path traversal. */ + return get_tgt_request(context, ctx); } /* - * 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. + * 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. */ - -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) +static krb5_error_code +begin_get_tgt(krb5_context context, krb5_tkt_creds_context ctx) { - 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; - } + 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); } - 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 = krb5int_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; - } + /* 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 KRB5_CC_NOTFOUND; + + /* 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); +} - /* - * 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. - */ +/***** STATE_BEGIN *****/ - /* - * 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; - } +static krb5_error_code +begin(krb5_context context, krb5_tkt_creds_context ctx) +{ + krb5_error_code 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; } - DUMP_PRINC("gc_from_kdc client at fallback", client); - DUMP_PRINC("gc_from_kdc server at fallback", server); + /* Obtain a TGT for the service realm. */ + ctx->getting_tgt_for = STATE_REFERRALS; + return begin_get_tgt(context, ctx); +} - /* - * At this point referrals have been tried and have failed. Go - * back to the server principal as originally issued and try the - * conventional path. - */ +/***** API functions *****/ - /* - * 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; - } - } +krb5_error_code KRB5_CALLCONV +krb5_tkt_creds_init(krb5_context context, krb5_ccache ccache, + krb5_creds *in_creds, int kdcopt, + krb5_tkt_creds_context *pctx) +{ + krb5_error_code code; + krb5_tkt_creds_context ctx = NULL; - DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite", - server); + ctx = k5alloc(sizeof(*ctx), &code); + if (ctx == NULL) + goto cleanup; - /* - * Get a TGT for the target realm. - */ + ctx->state = STATE_BEGIN; - krb5_free_cred_contents(context, &tgtq); - retval = krb5int_tgt_mcred(context, client, server, client, &tgtq); - if (retval) + code = krb5_copy_creds(context, in_creds, &ctx->in_creds); + if (code != 0) 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) + 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; - 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; + /* XXX Make an alias for now; use krb5_cc_dup later. */ + ctx->ccache = ccache; + ctx->req_kdcopt = kdcopt; + code = krb5_copy_authdata(context, in_creds->authdata, &ctx->authdata); + if (code != 0) 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); + + *pctx = ctx; + ctx = NULL; 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. - */ + krb5_tkt_creds_free(context, ctx); + return code; +} - 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 - } - } +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); +} - /* Free referral TGTs list. */ - for (i=0;istate != STATE_COMPLETE) + return KRB5_NO_TKT_SUPPLIED; + if (ccache == NULL) + ccache = ctx->ccache; + return krb5_cc_store_cred(context, ccache, ctx->reply_creds); } -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) +krb5_error_code KRB5_CALLCONV +krb5_tkt_creds_get_times(krb5_context context, krb5_tkt_creds_context ctx, + krb5_ticket_times *times) { - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - 0); + if (ctx->state != STATE_COMPLETE) + return KRB5_NO_TKT_SUPPLIED; + *times = ctx->reply_creds->times; + return 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) +void KRB5_CALLCONV +krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx) { - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - KDC_OPT_VALIDATE); + if (ctx == NULL) + return; + krb5_free_creds(context, ctx->in_creds); + 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_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts) +krb5_tkt_creds_get(krb5_context context, krb5_tkt_creds_context ctx) { - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - KDC_OPT_RENEW); + 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 & 1) == 0) + 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); + } + + 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; + } + + 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; } diff --git a/src/lib/krb5/krb/gc_frm_kdc_step.c b/src/lib/krb5/krb/gc_frm_kdc_step.c deleted file mode 100644 index 1aece10678..0000000000 --- a/src/lib/krb5/krb/gc_frm_kdc_step.c +++ /dev/null @@ -1,1034 +0,0 @@ -/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ -/* - * Copyright (c) 1990-2009 by the Massachusetts Institute of Technology. - * Copyright (c) 1994 CyberSAFE Corporation - * Copyright (c) 1993 Open Computing Security Group - * 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_tkt_creds_step() and related functions: - * - * Get credentials from some KDC somewhere, possibly accumulating TGTs - * along the way. This is asychronous version of the API in gc_frm_kdc.c. - * It requires that the KDC support cross-realm referrals. - */ - -#include "k5-int.h" -#include -#include "int-proto.h" - -/* - * 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_" 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) */ - int req_kdcopt; /* Caller-requested 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 */ - - /* 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 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 = 1; - return 0; - -cleanup: - krb5_free_data_contents(context, &out_copy); - krb5_free_data_contents(context, &realm_copy); - return code; -} - -/* - * 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, *creds = NULL; - krb5_error_code code; - krb5_principal tgtname = NULL; - krb5_flags flags; - - *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 (!data_eq(*realm, ctx->client->realm)) - flags |= KRB5_TC_MATCH_SRV_NAMEONLY; - - /* Allocate a structure for the resulting creds. */ - creds = k5alloc(sizeof(*creds), &code); - if (creds == NULL) - goto cleanup; - - /* Construct a matching cred for the ccache query. */ - memset(&mcreds, 0, sizeof(mcreds)); - mcreds.client = ctx->client; - mcreds.server = tgtname; - - /* Fetch the TGT credential, handling not-found errors. */ - context->use_conf_ktypes = TRUE; - code = krb5_cc_retrieve_cred(context, ctx->ccache, flags, &mcreds, - creds); - context->use_conf_ktypes = FALSE; - if (code != 0 && code != KRB5_CC_NOTFOUND && code != KRB5_CC_NOT_KTYPE) - goto cleanup; - if (code == 0) { - *tgt = creds; - creds = NULL; - } - code = 0; - -cleanup: - krb5_free_principal(context, tgtname); - free(creds); - return code; -} - -/* - * 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->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; - - /* - * 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 (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; - } - - /* 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; - - 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]); -} - -/* 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; - - 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; - } - } - return FALSE; -} - -/***** 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; - - /* Note the authdata we asked for in the output creds. */ - ctx->reply_creds->authdata = ctx->authdata; - ctx->authdata = NULL; - ctx->state = STATE_COMPLETE; - return 0; -} - -/* 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. */ - ctx->reply_creds->authdata = ctx->authdata; - ctx->authdata = NULL; - ctx->state = STATE_COMPLETE; - return 0; - } - - /* 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. - */ - -/* 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 KRB5_CC_NOTFOUND; - - /* 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 *****/ - -static krb5_error_code -begin(krb5_context context, krb5_tkt_creds_context ctx) -{ - krb5_error_code 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, int kdcopt, - 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->state = STATE_BEGIN; - - 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; - /* XXX Make an alias for now; use krb5_cc_dup later. */ - ctx->ccache = ccache; - ctx->req_kdcopt = kdcopt; - 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); -} - -/* Store credentials in credentials cache. If ccache is NULL, the - * credentials cache associated with the context is used. */ -krb5_error_code KRB5_CALLCONV -krb5_tkt_creds_store_creds(krb5_context context, krb5_tkt_creds_context ctx, - krb5_ccache ccache) -{ - if (ctx->state != STATE_COMPLETE) - return KRB5_NO_TKT_SUPPLIED; - if (ccache == NULL) - ccache = ctx->ccache; - return krb5_cc_store_cred(context, ccache, ctx->reply_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_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 & 1) == 0) - 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); - } - - 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; - } - - 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; -} diff --git a/src/lib/krb5/krb/int-proto.h b/src/lib/krb5/krb/int-proto.h index b3d54e8be4..495e17d8d8 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 6d7722422f..010ac56287 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -319,9 +319,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