diff options
author | Luke Howard <lukeh@padl.com> | 2009-11-17 10:50:37 +0000 |
---|---|---|
committer | Luke Howard <lukeh@padl.com> | 2009-11-17 10:50:37 +0000 |
commit | eaaa48c853b8f11d9a7064074b3db27a44f64d6d (patch) | |
tree | 4fa2fd767e6283daa66fad8944bf7c0ce18d3ee3 | |
parent | e99f16dc3580d4008b5c24a3fcfe5850a0ebea97 (diff) | |
download | krb5-eaaa48c853b8f11d9a7064074b3db27a44f64d6d.zip krb5-eaaa48c853b8f11d9a7064074b3db27a44f64d6d.tar.gz krb5-eaaa48c853b8f11d9a7064074b3db27a44f64d6d.tar.bz2 |
lite IAKERB supports referrals only
git-svn-id: svn://anonsvn.mit.edu/krb5/users/lhoward/iakerb-refonly@23262 dc483132-0cff-0310-8789-dd5450dbe970
-rw-r--r-- | src/lib/krb5/krb/Makefile.in | 3 | ||||
-rw-r--r-- | src/lib/krb5/krb/gc_frm_kdc.c | 1899 | ||||
-rw-r--r-- | src/lib/krb5/krb/gc_frm_kdc_step.c | 1509 | ||||
-rw-r--r-- | src/lib/krb5/libkrb5.exports | 1 |
4 files changed, 758 insertions, 2654 deletions
diff --git a/src/lib/krb5/krb/Makefile.in b/src/lib/krb5/krb/Makefile.in index 3746746..843d7ea 100644 --- a/src/lib/krb5/krb/Makefile.in +++ b/src/lib/krb5/krb/Makefile.in @@ -45,6 +45,7 @@ STLIBOBJS= \ free_rtree.o \ fwd_tgt.o \ gc_frm_kdc.o \ + gc_frm_kdc_step.o \ gc_via_tkt.o \ gen_seqnum.o \ gen_subkey.o \ @@ -135,6 +136,7 @@ OBJS= $(OUTPRE)addr_comp.$(OBJEXT) \ $(OUTPRE)free_rtree.$(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) \ @@ -226,6 +228,7 @@ SRCS= $(srcdir)/addr_comp.c \ $(srcdir)/free_rtree.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 b611c51..581d89d 100644 --- a/src/lib/krb5/krb/gc_frm_kdc.c +++ b/src/lib/krb5/krb/gc_frm_kdc.c @@ -1,8 +1,9 @@ /* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */ /* - * Copyright (c) 1990-2009 by the Massachusetts Institute of Technology. + * Copyright (c) 1994,2003,2005,2007 by the Massachusetts Institute of Technology. * Copyright (c) 1994 CyberSAFE Corporation * Copyright (c) 1993 Open Computing Security Group + * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology. * All Rights Reserved. * * Export of this software from the United States of America may @@ -31,17 +32,17 @@ * along the way. */ -#define DEBUG_GC_FRM_KDC 1 -#define DEBUG_REFERRALS 1 - #include "k5-int.h" #include <stdio.h> #include "int-proto.h" +struct tr_state; + /* * Ring buffer abstraction for TGTs returned from a ccache; avoids * lots of excess copying. */ + #define NCC_TGTS 2 struct cc_tgts { krb5_creds cred[NCC_TGTS]; @@ -49,6 +50,19 @@ struct cc_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. * @@ -65,6 +79,8 @@ struct cc_tgts { * allocates a krb5_creds for return. */ struct tr_state { + krb5_context ctx; + krb5_ccache ccache; krb5_principal *kdc_list; unsigned int nkdcs; krb5_principal *cur_kdc; @@ -80,84 +96,24 @@ struct tr_state { krb5_creds *offpath_tgt; }; -enum krb5_tkt_creds_state { - TKT_CREDS_INITIAL_TGT, /* TGT to client realm */ - TKT_CREDS_REFERRAL_TGT, /* Trying referrals with canon flag */ - TKT_CREDS_REFERRAL_TGT_NOCANON, /* Trying referrals without canon */ - TKT_CREDS_FALLBACK_INITIAL_TGT, /* Trying fallback TGT */ - TKT_CREDS_FALLBACK_FINAL_TKT, /* Trying fallback service ticket */ - TKT_CREDS_COMPLETE /* Final state */ -}; - -#ifdef DEBUG_GC_FRM_KDC -static char *tkt_creds_dbg_state[] = { - "INITIAL_TGT", - "REFERRAL_TGT", - "REFERRAL_TGT_NOCANON", - "FALLBACK_INITIAL_TGT", - "FINAL_TKT", - "COMPLETE" -}; -#endif /* DEBUG_GC_FRM_KDC */ - -/* - * Asynchronous API request/response state - */ -struct _krb5_tkt_creds_context { - enum krb5_tkt_creds_state state; - krb5_ccache ccache; - krb5_creds in_cred; - krb5_creds tgtq; - krb5_data *realm; - krb5_principal client; - krb5_principal server; - krb5_principal supplied_server; - krb5_creds *out_cred; - krb5_creds **tgts; - int kdcopt; - unsigned int referral_count; - krb5_creds cc_tgt; - krb5_creds *tgtptr; - krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS]; - krb5_boolean use_conf_ktypes; - struct tr_state ts; - krb5_timestamp timestamp; - krb5_keyblock *subkey; - krb5_data encoded_previous_request; - int is_referral_realm; -}; - -/* 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(krb5_context, krb5_tkt_creds_context); -static void shift_cc_tgts(krb5_context, krb5_tkt_creds_context); -static void clean_cc_tgts(krb5_context, krb5_tkt_creds_context); - /* * Debug support */ #ifdef DEBUG_GC_FRM_KDC -#define TR_DBG(context, ctx, prog) tr_dbg(context, ctx, prog) -#define TR_DBG_RET(context, ctx, prog, ret) tr_dbg_ret(context, ctx, prog, ret) -#define TR_DBG_RTREE(context, ctx, prog, princ) tr_dbg_rtree(context, ctx, prog, princ) +#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(krb5_context, krb5_tkt_creds_context, const char *); -static void tr_dbg_ret(krb5_context, krb5_tkt_creds_context, const char *, krb5_error_code); -static void tr_dbg_rtree(krb5_context, krb5_tkt_creds_context, const char *, krb5_principal); +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(context, ctx, prog) -#define TR_DBG_RET(context, ctx, prog, ret) -#define TR_DBG_RTREE(context, ctx, prog, princ) +#define TR_DBG(ts, prog) +#define TR_DBG_RET(ts, prog, ret) +#define TR_DBG_RTREE(ts, prog, princ) #endif /* !DEBUG_GC_FRM_KDC */ @@ -167,28 +123,6 @@ static void tr_dbg_rtree(krb5_context, krb5_tkt_creds_context, const char *, krb #define DFPRINTF(x) fprintf x #define DUMP_PRINC(x, y) krb5int_dbgref_dump_principal((x), (y)) -void -krb5int_dbgref_dump_principal(char *msg, krb5_principal princ) -{ - krb5_context context = NULL; - krb5_error_code code; - char *s = NULL; - - code = krb5_init_context(&context); - if (code != 0) - goto cleanup; - - code = krb5_unparse_name(context, princ, &s); - if (code != 0) - goto cleanup; - - fprintf(stderr, "%s: %s\n", msg, s); - -cleanup: - krb5_free_unparsed_name(context, s); - krb5_free_context(context); -} - #else #define DPRINTF(x) @@ -220,137 +154,23 @@ cleanup: */ static krb5_error_code tgt_mcred(krb5_context, krb5_principal, krb5_principal, krb5_principal, krb5_creds *); -static krb5_error_code retr_local_tgt(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code try_ccache(krb5_context context, - krb5_tkt_creds_context, krb5_creds *); -static krb5_error_code find_nxt_kdc(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code try_kdc_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code try_kdc_reply(krb5_context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code kdc_mcred(krb5_context context, - krb5_tkt_creds_context, krb5_principal, +static krb5_error_code retr_local_tgt(struct tr_state *, krb5_principal); +static krb5_error_code try_ccache(struct tr_state *, krb5_creds *); +static krb5_error_code find_nxt_kdc(struct tr_state *); +static krb5_error_code try_kdc(struct tr_state *, krb5_creds *); +static krb5_error_code kdc_mcred(struct tr_state *, krb5_principal, krb5_creds *mcreds); -static krb5_error_code next_closest_tgt_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code next_closest_tgt_reply(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code init_rtree(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code do_traversal_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *req); -static krb5_error_code do_traversal_reply(krb5_context context, - krb5_tkt_creds_context, - krb5_data *req); - -static krb5_error_code chase_offpath_request(krb5_context, - krb5_tkt_creds_context, - krb5_data *data); -static krb5_error_code chase_offpath_reply(krb5_context, - krb5_tkt_creds_context, - krb5_data *data); -static krb5_error_code offpath_loopchk(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_creds *tgt, - krb5_creds *reftgts[], - unsigned int rcount); - -#define TR_STATE(_ctx) (&(_ctx)->ts) - -/* - * tkt_make_tgs_request() - * - * wrapper around krb5_make_tgs_request() that updates realm and - * performs some additional validation - */ -static krb5_error_code -tkt_make_tgs_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_creds *tgt, - krb5_flags kdcopt, - krb5_creds *in_cred, - krb5_data *req) -{ - krb5_error_code code; - - assert(tgt != NULL); - - assert(req != NULL); - assert(req->length == 0); - assert(req->data == NULL); - - assert(in_cred != NULL); - assert(in_cred->server != NULL); - - ctx->realm = &in_cred->server->realm; - - /* These flags are always included */ - kdcopt |= FLAGS2OPTS(tgt->ticket_flags); - - if ((kdcopt & KDC_OPT_ENC_TKT_IN_SKEY) == 0) - in_cred->is_skey = FALSE; - - if (!krb5_c_valid_enctype(tgt->keyblock.enctype)) - return KRB5_PROG_ETYPE_NOSUPP; - - code = krb5_make_tgs_request(context, tgt, kdcopt, - tgt->addresses, NULL, - in_cred, NULL, NULL, - req, &ctx->timestamp, &ctx->subkey); - return code; -} - -/* - * tkt_process_tgs_reply() - * - * wrapper around krb5_process_tgs_reply() that uses context - * information and performs some additional validation - */ -static krb5_error_code -tkt_process_tgs_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep, - krb5_creds *tgt, - krb5_flags kdcopt, - krb5_creds *in_cred, - krb5_creds **out_cred) -{ - krb5_error_code code; - - /* These flags are always included */ - kdcopt |= FLAGS2OPTS(tgt->ticket_flags); - - if ((kdcopt & KDC_OPT_ENC_TKT_IN_SKEY) == 0) - in_cred->is_skey = FALSE; - - code = krb5_process_tgs_reply(context, - rep, - tgt, - kdcopt, - tgt->addresses, - NULL, - in_cred, - ctx->timestamp, - ctx->subkey, - NULL, - NULL, - out_cred); - -#ifdef DEBUG_GC_FRM_KDC - if (code != 0) - fprintf(stderr, "tkt_process_tgs_reply: %s\n", - krb5_get_error_message(context, code)); -#endif - - return code; -} +static krb5_error_code next_closest_tgt(struct tr_state *, krb5_principal); +static krb5_error_code init_rtree(struct tr_state *, + krb5_principal, krb5_principal); +static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache, + krb5_principal client, krb5_principal server, + krb5_creds *out_cc_tgt, krb5_creds **out_tgt, + krb5_creds ***out_kdc_tgts, int *tgtptr_isoffpath); +static krb5_error_code chase_offpath(struct tr_state *, krb5_principal, + krb5_principal); +static krb5_error_code offpath_loopchk(struct tr_state *ts, + krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount); /* * init_cc_tgts() @@ -359,9 +179,8 @@ tkt_process_tgs_reply(krb5_context context, * CC_TGTS, CC_TGT_DIRTY arrays prior to calling. */ static void -init_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) +init_cc_tgts(struct tr_state *ts) { - struct tr_state *ts = TR_STATE(ctx); ts->cc_tgts.cur = 0; ts->cc_tgts.nxt = 1; @@ -377,11 +196,10 @@ init_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) * the new NXT_CC_TGT. */ static void -shift_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) +shift_cc_tgts(struct tr_state *ts) { unsigned int i; struct cc_tgts *rb; - struct tr_state *ts = TR_STATE(ctx); rb = &ts->cc_tgts; i = rb->cur = rb->nxt; @@ -393,7 +211,7 @@ shift_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) rb->nxt = i; ts->nxt_cc_tgt = &rb->cred[i]; if (rb->dirty[i]) { - krb5_free_cred_contents(context, &rb->cred[i]); + krb5_free_cred_contents(ts->ctx, &rb->cred[i]); rb->dirty[i] = 0; } } @@ -404,16 +222,15 @@ shift_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) * Free CC_TGTS which were dirty, then mark them clean. */ static void -clean_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) +clean_cc_tgts(struct tr_state *ts) { unsigned int i; struct cc_tgts *rb; - struct tr_state *ts = TR_STATE(ctx); rb = &ts->cc_tgts; for (i = 0; i < NCC_TGTS; i++) { if (rb->dirty[i]) { - krb5_free_cred_contents(context, &rb->cred[i]); + krb5_free_cred_contents(ts->ctx, &rb->cred[i]); rb->dirty[i] = 0; } } @@ -424,49 +241,46 @@ clean_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) */ #ifdef DEBUG_GC_FRM_KDC static void -tr_dbg(krb5_context context, krb5_tkt_creds_context ctx, const char *prog) +tr_dbg(struct tr_state *ts, const char *prog) { krb5_error_code retval; char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str; - struct tr_state *ts = TR_STATE(ctx); cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL; - retval = krb5_unparse_name(context, ts->cur_tgt->server, &cur_tgt_str); + retval = krb5_unparse_name(ts->ctx, ts->cur_tgt->server, &cur_tgt_str); if (retval) goto cleanup; - retval = krb5_unparse_name(context, *ts->cur_kdc, &cur_kdc_str); + retval = krb5_unparse_name(ts->ctx, *ts->cur_kdc, &cur_kdc_str); if (retval) goto cleanup; - retval = krb5_unparse_name(context, *ts->nxt_kdc, &nxt_kdc_str); + retval = krb5_unparse_name(ts->ctx, *ts->nxt_kdc, &nxt_kdc_str); if (retval) goto cleanup; fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str); fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str); fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str); cleanup: if (cur_tgt_str) - krb5_free_unparsed_name(context, cur_tgt_str); + krb5_free_unparsed_name(ts->ctx, cur_tgt_str); if (cur_kdc_str) - krb5_free_unparsed_name(context, cur_kdc_str); + krb5_free_unparsed_name(ts->ctx, cur_kdc_str); if (nxt_kdc_str) - krb5_free_unparsed_name(context, nxt_kdc_str); + krb5_free_unparsed_name(ts->ctx, nxt_kdc_str); } static void -tr_dbg_ret(krb5_context context, krb5_tkt_creds_context ctx, - const char *prog, krb5_error_code ret) +tr_dbg_ret(struct tr_state *ts, const char *prog, krb5_error_code ret) { fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret, error_message(ret)); } static void -tr_dbg_rtree(krb5_context context, krb5_tkt_creds_context ctx, - const char *prog, krb5_principal princ) +tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ) { char *str; - if (krb5_unparse_name(context, princ, &str)) + if (krb5_unparse_name(ts->ctx, princ, &str)) return; fprintf(stderr, "%s: %s\n", prog, str); - krb5_free_unparsed_name(context, str); + krb5_free_unparsed_name(ts->ctx, str); } #endif /* DEBUG_GC_FRM_KDC */ @@ -482,26 +296,27 @@ tr_dbg_rtree(krb5_context context, krb5_tkt_creds_context ctx, * consistency with krb5_tgtname(). */ static krb5_error_code -tgt_mcred(krb5_context context, krb5_principal client, +tgt_mcred(krb5_context ctx, krb5_principal client, krb5_principal dst, krb5_principal src, krb5_creds *mcreds) { krb5_error_code retval; + retval = 0; memset(mcreds, 0, sizeof(*mcreds)); - retval = krb5_copy_principal(context, client, &mcreds->client); - if (retval != 0) + retval = krb5_copy_principal(ctx, client, &mcreds->client); + if (retval) goto cleanup; - retval = krb5_tgtname(context, krb5_princ_realm(context, dst), - krb5_princ_realm(context, src), &mcreds->server); - if (retval != 0) + retval = krb5_tgtname(ctx, krb5_princ_realm(ctx, dst), + krb5_princ_realm(ctx, src), &mcreds->server); + if (retval) goto cleanup; cleanup: - if (retval != 0) - krb5_free_cred_contents(context, mcreds); + if (retval) + krb5_free_cred_contents(ctx, mcreds); return retval; } @@ -512,40 +327,28 @@ cleanup: * Populate KDC_LIST with the output of krb5_walk_realm_tree(). */ static krb5_error_code -init_rtree(krb5_context context, krb5_tkt_creds_context ctx) +init_rtree(struct tr_state *ts, + krb5_principal client, krb5_principal server) { krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - if (ts->kdc_list != NULL) { - krb5_free_realm_tree(context, ts->kdc_list); - ts->kdc_list = NULL; - } - retval = krb5_walk_realm_tree(context, krb5_princ_realm(context, - ctx->client), - krb5_princ_realm(context, ctx->server), + ts->kdc_list = NULL; + retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client), + krb5_princ_realm(ts->ctx, server), &ts->kdc_list, KRB5_REALM_BRANCH_CHAR); if (retval) return retval; for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) { - assert(krb5_princ_size(context, ts->kdc_list[ts->nkdcs]) == 2); - TR_DBG_RTREE(context, ctx, "init_rtree", ts->kdc_list[ts->nkdcs]); + assert(krb5_princ_size(ts->ctx, ts->kdc_list[ts->nkdcs]) == 2); + TR_DBG_RTREE(ts, "init_rtree", ts->kdc_list[ts->nkdcs]); } assert(ts->nkdcs > 1); ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1; - ts->kdc_tgts = realloc(ts->kdc_tgts, (ts->nkdcs + 1) * sizeof(krb5_creds)); + ts->kdc_tgts = calloc(ts->nkdcs + 1, sizeof(krb5_creds)); if (ts->kdc_tgts == NULL) return ENOMEM; - memset(ts->kdc_tgts, 0, (ts->nkdcs + 1) * sizeof(krb5_creds)); - - if (ts->offpath_tgt != NULL) { - assert(ctx->tgtptr != ts->offpath_tgt); - krb5_free_creds(context, ts->offpath_tgt); - ts->offpath_tgt = NULL; - } return 0; } @@ -556,24 +359,23 @@ init_rtree(krb5_context context, krb5_tkt_creds_context ctx) * Prime CUR_TGT with the cached TGT of the client's local realm. */ static krb5_error_code -retr_local_tgt(krb5_context context, krb5_tkt_creds_context ctx) +retr_local_tgt(struct tr_state *ts, krb5_principal client) { krb5_error_code retval; krb5_creds tgtq; - struct tr_state *ts = TR_STATE(ctx); memset(&tgtq, 0, sizeof(tgtq)); - retval = tgt_mcred(context, ctx->client, ctx->client, ctx->client, &tgtq); + retval = tgt_mcred(ts->ctx, client, client, client, &tgtq); if (retval) return retval; /* Match realm, unlike other ccache retrievals here. */ - retval = krb5_cc_retrieve_cred(context, ctx->ccache, + retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache, KRB5_TC_SUPPORTED_KTYPES, &tgtq, ts->nxt_cc_tgt); - krb5_free_cred_contents(context, &tgtq); + krb5_free_cred_contents(ts->ctx, &tgtq); if (!retval) { - shift_cc_tgts(context, ctx); + shift_cc_tgts(ts); ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt; } return retval; @@ -586,19 +388,18 @@ retr_local_tgt(krb5_context context, krb5_tkt_creds_context ctx) * it if successful. */ static krb5_error_code -try_ccache(krb5_context context, krb5_tkt_creds_context ctx, krb5_creds *tgtq) +try_ccache(struct tr_state *ts, krb5_creds *tgtq) { krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - TR_DBG(context, ctx, "try_ccache"); - retval = krb5_cc_retrieve_cred(context, ctx->ccache, RETR_FLAGS, + 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(context, ctx); + shift_cc_tgts(ts); ts->nxt_tgt = ts->cur_cc_tgt; } - TR_DBG_RET(context, ctx, "try_ccache", retval); + TR_DBG_RET(ts, "try_ccache", retval); return retval; } @@ -627,23 +428,22 @@ try_ccache(krb5_context context, krb5_tkt_creds_context ctx, krb5_creds *tgtq) * NXT_KDC to one entry forward of the match. */ static krb5_error_code -find_nxt_kdc(krb5_context context, krb5_tkt_creds_context ctx) +find_nxt_kdc(struct tr_state *ts) { krb5_data *r1, *r2; krb5_principal *kdcptr; - struct tr_state *ts = TR_STATE(ctx); - TR_DBG(context, ctx, "find_nxt_kdc"); + TR_DBG(ts, "find_nxt_kdc"); assert(ts->ntgts > 0); assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); - if (krb5_princ_size(context, ts->nxt_tgt->server) != 2) + if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2) return KRB5_KDCREP_MODIFIED; - r1 = krb5_princ_component(context, ts->nxt_tgt->server, 1); + r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1); for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) { - r2 = krb5_princ_component(context, *kdcptr, 1); + r2 = krb5_princ_component(ts->ctx, *kdcptr, 1); if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) { break; @@ -651,15 +451,15 @@ find_nxt_kdc(krb5_context context, krb5_tkt_creds_context ctx) } if (*kdcptr != NULL) { ts->nxt_kdc = kdcptr; - TR_DBG_RET(context, ctx, "find_nxt_kdc", 0); + TR_DBG_RET(ts, "find_nxt_kdc", 0); return 0; } - r2 = krb5_princ_component(context, ts->kdc_list[0], 1); + r2 = krb5_princ_component(ts->ctx, ts->kdc_list[0], 1); if (r1 != NULL && r2 != NULL && r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) { - TR_DBG_RET(context, ctx, "find_nxt_kdc: looped back to local", + TR_DBG_RET(ts, "find_nxt_kdc: looped back to local", KRB5_KDCREP_MODIFIED); return KRB5_KDCREP_MODIFIED; } @@ -682,54 +482,43 @@ find_nxt_kdc(krb5_context context, krb5_tkt_creds_context ctx) */ ts->kdc_tgts[--ts->ntgts] = NULL; ts->nxt_tgt = ts->cur_tgt; - TR_DBG_RET(context, ctx, "find_nxt_kdc", 0); + TR_DBG_RET(ts, "find_nxt_kdc", 0); return 0; } /* - * try_kdc_request()/try_kdc_reply() + * try_kdc() * * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_KDC if * successful. */ static krb5_error_code -try_kdc_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) +try_kdc(struct tr_state *ts, krb5_creds *tgtq) { krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - TR_DBG(context, ctx, "try_kdc_request"); - - retval = tkt_make_tgs_request(context, ctx, ts->cur_tgt, 0, - &ctx->tgtq, req); - - TR_DBG_RET(context, ctx, "try_kdc_request", retval); - - return retval; -} + krb5_creds ltgtq; -static krb5_error_code -try_kdc_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); + 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; - TR_DBG(context, ctx, "try_kdc_reply"); - retval = tkt_process_tgs_reply(context, ctx, rep, ts->cur_tgt, 0, - &ctx->tgtq, &ts->kdc_tgts[ts->ntgts++]); - if (retval != 0) { + 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(context, ctx, "try_kdc_reply", retval); + TR_DBG_RET(ts, "try_kdc", retval); return retval; } - ts->nxt_tgt = ts->kdc_tgts[ts->ntgts - 1]; - retval = find_nxt_kdc(context, ctx); - TR_DBG_RET(context, ctx, "try_kdc_reply", retval); + ts->nxt_tgt = ts->kdc_tgts[ts->ntgts-1]; + retval = find_nxt_kdc(ts); + TR_DBG_RET(ts, "try_kdc", retval); return retval; } @@ -744,126 +533,80 @@ try_kdc_reply(krb5_context context, * cleans MCREDS on failure. */ static krb5_error_code -kdc_mcred(krb5_context context, krb5_tkt_creds_context ctx, - krb5_principal client, krb5_creds *mcreds) +kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds) { krb5_error_code retval; krb5_data *rdst, *rsrc; - struct tr_state *ts = TR_STATE(ctx); + retval = 0; memset(mcreds, 0, sizeof(*mcreds)); - rdst = krb5_princ_component(context, *ts->nxt_kdc, 1); - rsrc = krb5_princ_component(context, *ts->cur_kdc, 1); - - retval = krb5_copy_principal(context, client, &mcreds->client); - if (retval != 0) + rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1); + rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1); + retval = krb5_copy_principal(ts->ctx, client, &mcreds->client); + if (retval) goto cleanup; - retval = krb5_tgtname(context, rdst, rsrc, &mcreds->server); - if (retval != 0) + retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcreds->server); + if (retval) goto cleanup; cleanup: - if (retval != 0) - krb5_free_cred_contents(context, mcreds); + if (retval) + krb5_free_cred_contents(ts->ctx, mcreds); return retval; } -static void -tkt_creds_set_state(krb5_context context, - krb5_tkt_creds_context ctx, - enum krb5_tkt_creds_state newstate, - int freeReferrals, - const char *msg) -{ - unsigned int i; - - assert(newstate >= TKT_CREDS_INITIAL_TGT); - assert(newstate <= TKT_CREDS_COMPLETE); - -#ifdef DEBUG_GC_FRM_KDC - fprintf(stderr, "tkt_creds_set_state %s->%s %s referrals\n", - tkt_creds_dbg_state[ctx->state], - tkt_creds_dbg_state[newstate], - freeReferrals ? "free" : "do not free"); -#endif /* DEBUG_GC_FRM_KDC */ - - if (freeReferrals) { - for (i = 0; i < ctx->referral_count; i++) { - assert(ctx->tgtptr != ctx->referral_tgts[i]); - krb5_free_creds(context, ctx->referral_tgts[i]); - ctx->referral_tgts[i] = NULL; - } - } - - ctx->state = newstate; -} - /* - * next_closest_tgt_request()/next_closest_tgt_reply() + * next_closest_tgt() * * Using CUR_TGT, attempt to get the cross-realm TGT having its remote * realm closest to the target principal's. Update NXT_TGT, NXT_KDC * accordingly. */ static krb5_error_code -next_closest_tgt_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) +next_closest_tgt(struct tr_state *ts, krb5_principal client) { krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - assert(ctx->tgtq.server == NULL); - - if (ts->nxt_kdc == NULL) { - /* Initial state */ - ts->nxt_kdc = ts->lst_kdc; - } else if (ts->nxt_kdc < ts->cur_kdc) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "next_closest_tgt_request"); - return 0; - } + krb5_creds tgtq; - assert(ts->nxt_kdc >= ts->cur_kdc); + retval = 0; + memset(&tgtq, 0, sizeof(tgtq)); - retval = kdc_mcred(context, ctx, ctx->client, &ctx->tgtq); - if (retval != 0) - return retval; + for (ts->nxt_kdc = ts->lst_kdc; + ts->nxt_kdc > ts->cur_kdc; + ts->nxt_kdc--) { - /* Don't waste time retrying ccache for direct path. */ - if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) { - retval = try_ccache(context, ctx, &ctx->tgtq); - if (retval == 0) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "next_closest_tgt_request"); - return 0; - } else if (HARD_CC_ERR(retval)) { - return retval; + krb5_free_cred_contents(ts->ctx, &tgtq); + retval = kdc_mcred(ts, client, &tgtq); + if (retval) + goto cleanup; + /* Don't waste time retrying ccache for direct path. */ + if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) { + retval = try_ccache(ts, &tgtq); + if (!retval) + break; + if (HARD_CC_ERR(retval)) + goto cleanup; + } + /* Not in the ccache, so talk to a KDC. */ + retval = try_kdc(ts, &tgtq); + if (!retval) { + break; } + /* + * In case of errors in try_kdc() or find_nxt_kdc(), continue + * looping through the KDC list. + */ } - - /* Not in the ccache, so talk to a KDC. */ - return try_kdc_request(context, ctx, req); -} - -static krb5_error_code -next_closest_tgt_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - struct tr_state *ts = TR_STATE(ctx); - - code = try_kdc_reply(context, ctx, rep); - if (code != 0) - ts->nxt_kdc--; - - return 0; + /* + * 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; } /* @@ -949,7 +692,7 @@ next_closest_tgt_reply(krb5_context context, * 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 to occur + * path. Typically, short-circuit paths will cause execution occur * faster than this worst-case scenario. * * When next_closest_tgt() updates NXT_KDC, it may not perform a @@ -957,106 +700,79 @@ next_closest_tgt_reply(krb5_context context, * short-circuit pieces of the transit path. */ static krb5_error_code -do_traversal_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) +do_traversal(krb5_context ctx, + krb5_ccache ccache, + krb5_principal client, + krb5_principal server, + krb5_creds *out_cc_tgt, + krb5_creds **out_tgt, + krb5_creds ***out_kdc_tgts, + int *tgtptr_isoffpath) { - krb5_error_code retval = 0; - struct tr_state *ts = TR_STATE(ctx); - - if (ctx->tgtptr == NULL) { - /* Initial state */ - clean_cc_tgts(context, ctx); - init_cc_tgts(context, ctx); - - retval = init_rtree(context, ctx); - if (retval != 0) - goto cleanup; + krb5_error_code retval; + struct tr_state state, *ts; - retval = retr_local_tgt(context, ctx); - if (retval != 0) - goto cleanup; + *out_tgt = NULL; + *out_kdc_tgts = NULL; + ts = &state; + memset(ts, 0, sizeof(*ts)); + ts->ctx = ctx; + ts->ccache = ccache; + init_cc_tgts(ts); - ts->cur_kdc = ts->kdc_list; - ts->nxt_kdc = NULL; - } - - if (ts->cur_kdc == NULL || ts->cur_kdc >= ts->lst_kdc) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "do_traversal_request"); + retval = init_rtree(ts, client, server); + if (retval) goto cleanup; - } - - if (ts->offpath_tgt == NULL) - retval = next_closest_tgt_request(context, ctx, req); - else - retval = chase_offpath_request(context, ctx, req); - -cleanup: - return retval; -} -static krb5_error_code -do_traversal_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - int offpath = 0; - - assert(ctx->out_cred == NULL); + retval = retr_local_tgt(ts, client); + if (retval) + goto cleanup; - if (ts->offpath_tgt != NULL) { - retval = chase_offpath_reply(context, ctx, rep); - if (retval != 0) - return retval; + for (ts->cur_kdc = ts->kdc_list, ts->nxt_kdc = NULL; + ts->cur_kdc != NULL && ts->cur_kdc < ts->lst_kdc; + ts->cur_kdc = ts->nxt_kdc, ts->cur_tgt = ts->nxt_tgt) { - /* termination condition */ - offpath = 1; - } else { - retval = next_closest_tgt_reply(context, ctx, rep); - if (retval != 0) - return retval; - } + retval = next_closest_tgt(ts, client); + if (retval) + goto cleanup; - if (offpath == 0) { -#if 0 + if (ts->offpath_tgt != NULL) { + retval = chase_offpath(ts, client, server); + if (retval) + goto cleanup; + break; + } assert(ts->cur_kdc != ts->nxt_kdc); -#endif - ts->cur_kdc = ts->nxt_kdc; - ts->cur_tgt = ts->nxt_tgt; } if (NXT_TGT_IS_CACHED(ts)) { assert(ts->offpath_tgt == NULL); - krb5_free_cred_contents(context, &ctx->cc_tgt); - ctx->cc_tgt = *ts->cur_cc_tgt; - ctx->tgtptr = ts->cur_tgt; + *out_cc_tgt = *ts->cur_cc_tgt; + *out_tgt = out_cc_tgt; MARK_CUR_CC_TGT_CLEAN(ts); } else if (ts->offpath_tgt != NULL){ - ctx->tgtptr = ts->offpath_tgt; + *out_tgt = ts->offpath_tgt; } else { /* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */ - ctx->tgtptr = ts->nxt_tgt; - } - - if (ts->cur_kdc == ts->lst_kdc || offpath) { - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "do_traversal_reply"); - if (ts->ntgts != 0) { - krb5_free_tgt_creds(context, ctx->tgts); - ctx->tgts = ts->kdc_tgts; - ts->kdc_tgts = NULL; - } + *out_tgt = ts->nxt_tgt; } +cleanup: + clean_cc_tgts(ts); + if (ts->kdc_list != NULL) + krb5_free_realm_tree(ctx, ts->kdc_list); + if (ts->ntgts == 0) { + *out_kdc_tgts = NULL; + if (ts->kdc_tgts != NULL) + free(ts->kdc_tgts); + } else + *out_kdc_tgts = ts->kdc_tgts; + *tgtptr_isoffpath = (ts->offpath_tgt != NULL); return retval; } /* - * chase_offpath_request()/chase_offpath_reply() + * chase_offpath() * * Chase off-path TGT referrals. * @@ -1069,100 +785,75 @@ do_traversal_reply(krb5_context context, * TS->OFFPATH_TGT. */ static krb5_error_code -chase_offpath_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) +chase_offpath(struct tr_state *ts, + krb5_principal client, krb5_principal server) { krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - krb5_data *rdst = krb5_princ_realm(context, ctx->server); - krb5_data *rsrc; - - if (ctx->referral_count == 0) { - /* Initial state */ - ts->cur_tgt = ts->offpath_tgt; - } else if (ctx->referral_count >= KRB5_REFERRAL_MAXHOPS) { - /* termination condition */ - retval = KRB5_KDCREP_MODIFIED; - goto cleanup; + 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 = krb5_tgtname(ts->ctx, rdst, rsrc, &mcred.server); + if (retval) + goto cleanup; + mcred.client = client; + retval = krb5_get_cred_via_tkt(ts->ctx, cur_tgt, + FLAGS2OPTS(cur_tgt->ticket_flags), + cur_tgt->addresses, &mcred, &nxt_tgt); + mcred.client = NULL; + krb5_free_principal(ts->ctx, mcred.server); + mcred.server = NULL; + if (retval) + goto cleanup; + if (!IS_TGS_PRINC(ts->ctx, nxt_tgt->server)) { + retval = KRB5_KDCREP_MODIFIED; + goto cleanup; + } + r1 = krb5_princ_component(ts->ctx, nxt_tgt->server, 1); + if (rdst->length == r1->length && + !memcmp(rdst->data, r1->data, rdst->length)) { + retval = 0; + goto cleanup; + } + retval = offpath_loopchk(ts, nxt_tgt, reftgts, rcount); + if (retval) + goto cleanup; + reftgts[rcount] = nxt_tgt; + cur_tgt = nxt_tgt; + nxt_tgt = NULL; } - - assert(ctx->tgtptr != NULL); - assert(ctx->tgtptr->server != NULL); - - rsrc = krb5_princ_component(context, ctx->tgtptr->server, 1); - - assert(ctx->tgtq.server == NULL); - - retval = krb5_tgtname(context, rdst, rsrc, &ctx->tgtq.server); - if (retval != 0) - goto cleanup; - - retval = krb5_copy_principal(context, ctx->client, &ctx->tgtq.client); - if (retval != 0) - goto cleanup; - - retval = tkt_make_tgs_request(context, ctx, ts->cur_tgt, 0, - &ctx->tgtq, req); - if (retval != 0) - goto cleanup; + /* Max hop count exceeded. */ + retval = KRB5_KDCREP_MODIFIED; cleanup: - return retval; -} - -static krb5_error_code -chase_offpath_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - krb5_creds *nxt_tgt = NULL; - krb5_data *rsrc; - krb5_data *rdst = krb5_princ_realm(context, ctx->server); - krb5_data *r1; - - rsrc = krb5_princ_component(context, ctx->tgtptr->server, 1); - - retval = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, 0, - &ctx->tgtq, &nxt_tgt); - if (retval != 0) - goto cleanup; - if (!IS_TGS_PRINC(context, nxt_tgt->server)) { - retval = KRB5_KDCREP_MODIFIED; - goto cleanup; - } - r1 = krb5_princ_component(context, nxt_tgt->server, 1); - if (rdst->length == r1->length && - !memcmp(rdst->data, r1->data, rdst->length)) { - retval = 0; - goto cleanup; + if (mcred.server != NULL) { + krb5_free_principal(ts->ctx, mcred.server); } - retval = offpath_loopchk(context, ctx, nxt_tgt, ctx->referral_tgts, - ctx->referral_count); - if (retval) - goto cleanup; - assert(ctx->referral_count < KRB5_REFERRAL_MAXHOPS - 1); - ctx->referral_tgts[ctx->referral_count++] = nxt_tgt; - ctx->tgtptr = nxt_tgt; - nxt_tgt = NULL; - -cleanup: /* * 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(context, ts->offpath_tgt); - ts->offpath_tgt = NULL; + krb5_free_creds(ts->ctx, ts->offpath_tgt); } + ts->offpath_tgt = NULL; if (nxt_tgt != NULL) { - if (retval != 0) - krb5_free_creds(context, nxt_tgt); + if (retval) + krb5_free_creds(ts->ctx, nxt_tgt); else ts->offpath_tgt = nxt_tgt; } + for (i = 0; i < rcount; i++) { + krb5_free_creds(ts->ctx, reftgts[i]); + } return retval; } @@ -1173,22 +864,21 @@ cleanup: * on-path. */ static krb5_error_code -offpath_loopchk(krb5_context context, krb5_tkt_creds_context ctx, +offpath_loopchk(struct tr_state *ts, krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount) { krb5_data *r1, *r2; unsigned int i; - struct tr_state *ts = TR_STATE(ctx); - r1 = krb5_princ_component(context, tgt->server, 1); + r1 = krb5_princ_component(ts->ctx, tgt->server, 1); for (i = 0; i < rcount; i++) { - r2 = krb5_princ_component(context, reftgts[i]->server, 1); + r2 = krb5_princ_component(ts->ctx, reftgts[i]->server, 1); if (r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) return KRB5_KDCREP_MODIFIED; } for (i = 0; i < ts->ntgts; i++) { - r2 = krb5_princ_component(context, ts->kdc_tgts[i]->server, 1); + r2 = krb5_princ_component(ts->ctx, ts->kdc_tgts[i]->server, 1); if (r1->length == r2->length && !memcmp(r1->data, r2->data, r1->length)) return KRB5_KDCREP_MODIFIED; @@ -1197,423 +887,331 @@ offpath_loopchk(krb5_context context, krb5_tkt_creds_context ctx, } /* - * Asynchronous API + * krb5_get_cred_from_kdc_opt() + * krb5_get_cred_from_kdc() + * krb5_get_cred_from_kdc_validate() + * krb5_get_cred_from_kdc_renew() + * + * Retrieve credentials for client IN_CRED->CLIENT, server + * IN_CRED->SERVER, ticket flags IN_CRED->TICKET_FLAGS, possibly + * second_ticket if needed. + * + * Request credentials from the KDC for the server's realm. Point + * TGTS to an allocated array of pointers to krb5_creds, containing + * any intermediate credentials obtained in the process of contacting + * the server's KDC; if no intermediate credentials were obtained, + * TGTS is a null pointer. Return intermediate credentials if + * intermediate KDCs provided credentials, even if no useful end + * ticket results. + * + * Caller must free TGTS, regardless of whether this function returns + * success. + * + * This function does NOT cache the intermediate TGTs. + * + * Do not call this routine if desired credentials are already cached. + * + * On success, OUT_CRED contains the desired credentials; the caller + * must free them. + * + * Beware memory management issues if you have modifications in mind. + * With the addition of referral support, it is now the case that *tgts, + * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to + * the same credential at different times. + * + * Returns errors, system errors. */ -krb5_error_code KRB5_CALLCONV -krb5_tkt_creds_init(krb5_context context, - krb5_ccache ccache, - krb5_creds *creds, - int kdcopt, - krb5_tkt_creds_context *pctx) + +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 code; - krb5_tkt_creds_context ctx = NULL; + 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; - if (creds->client == NULL || creds->server == NULL) { - code = EINVAL; - goto cleanup; + /* + * 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; + } } - ctx = k5alloc(sizeof(*ctx), &code); - if (code != 0) - goto cleanup; - - code = krb5int_copy_creds_contents(context, creds, &ctx->in_cred); - if (code != 0) - goto cleanup; - - ctx->ccache = ccache; /* XXX */ + supplied_server = in_cred->server; + in_cred->server=server; + supplied_authdata = in_cred->authdata; - ctx->use_conf_ktypes = context->use_conf_ktypes; - ctx->client = ctx->in_cred.client; - ctx->server = ctx->in_cred.server; + 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)); - code = krb5_copy_principal(context, ctx->server, &ctx->supplied_server); - if (code != 0) - goto cleanup; + tgtptr = NULL; + *tgts = NULL; + *out_cred=NULL; + old_use_conf_ktypes = context->use_conf_ktypes; - ctx->is_referral_realm = krb5_is_referral_realm(&ctx->server->realm); - if (ctx->is_referral_realm) { + /* Copy client realm to server if no hint. */ + if (krb5_is_referral_realm(&server->realm)) { /* Use the client realm. */ - DPRINTF(("krb5_tkt_creds_init: no server realm supplied, " - "using client realm\n")); - krb5_free_data_contents(context, &ctx->server->realm); - code = krb5int_copy_data_contents_add0(context, - &ctx->client->realm, - &ctx->server->realm); - if (code != 0) + 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; - } - - /* The rest must be done by krb5_tkt_creds_step() */ - - *pctx = ctx; - -cleanup: - if (code != 0) - 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 != TKT_CREDS_COMPLETE) - return EINVAL; - return krb5int_copy_creds_contents(context, ctx->out_cred, creds); -} - -void KRB5_CALLCONV -krb5_tkt_creds_free(krb5_context context, - krb5_tkt_creds_context ctx) -{ - struct tr_state *ts; - int i; - - if (ctx == NULL) - return; - - krb5_free_principal(context, ctx->supplied_server); - krb5_free_cred_contents(context, &ctx->in_cred); - krb5_free_cred_contents(context, &ctx->tgtq); - krb5_free_creds(context, ctx->out_cred); - krb5_free_tgt_creds(context, ctx->tgts); - krb5_free_data_contents(context, &ctx->encoded_previous_request); - krb5_free_keyblock(context, ctx->subkey); - - /* Free referral TGTs list. */ - for (i = 0; i < KRB5_REFERRAL_MAXHOPS; i++) { - if (ctx->referral_tgts[i] != NULL) { - krb5_free_creds(context, ctx->referral_tgts[i]); - ctx->referral_tgts[i] = NULL; } + memcpy(server->realm.data, client->realm.data, client->realm.length); + server->realm.length = client->realm.length; + server->realm.data[server->realm.length] = 0; } + /* + * Retreive initial TGT to match the specified server, either for the + * local realm in the default (referral) case or for the remote + * realm if we're starting someplace non-local. + */ + retval = tgt_mcred(context, client, server, client, &tgtq); + if (retval) + goto cleanup; - clean_cc_tgts(context, ctx); - ts = TR_STATE(ctx); - if (ts->offpath_tgt != NULL) - krb5_free_creds(context, ts->offpath_tgt); - if (ts->kdc_list != NULL) - krb5_free_realm_tree(context, ts->kdc_list); - if (ts->kdc_tgts != NULL) - free(ts->kdc_tgts); - - free(ctx); -} - -krb5_error_code KRB5_CALLCONV -krb5_tkt_creds_get(krb5_context context, - krb5_tkt_creds_context ctx) -{ - krb5_error_code code; - krb5_data request; - krb5_data reply; - krb5_data realm; - unsigned int flags = 0; - int tcp_only = 0; - int use_master = 0; - - request.length = 0; - request.data = NULL; - reply.length = 0; - reply.data = NULL; - realm.length = 0; - realm.data = NULL; - - if (ctx->state == TKT_CREDS_COMPLETE) { - return 0; - } - - for (;;) { - code = krb5_tkt_creds_step(context, - ctx, - &reply, - &request, - &realm, - &flags); - if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) - tcp_only = 1; - else if (code != 0 || (flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE)) - break; - - krb5_free_data_contents(context, &reply); - - code = krb5_sendto_kdc(context, &request, &realm, - &reply, &use_master, tcp_only); - if (code != 0) - break; - - krb5_free_data_contents(context, &request); - krb5_free_data_contents(context, &realm); - } - - krb5_free_data_contents(context, &request); - krb5_free_data_contents(context, &reply); - krb5_free_data_contents(context, &realm); - - return code; -} - -/* - * Retrieve 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. - */ -static krb5_error_code -tkt_creds_request_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - - DUMP_PRINC("tkt_creds_request_initial_tgt: server", ctx->server); - - assert(ctx->tgtq.server == NULL); - - if (ctx->tgtptr == NULL) { - code = tgt_mcred(context, ctx->client, ctx->server, - ctx->client, &ctx->tgtq); - if (code != 0) - return code; - - /* Fast path: is it in the ccache? */ - krb5_free_cred_contents(context, &ctx->cc_tgt); - code = krb5_cc_retrieve_cred(context, ctx->ccache, RETR_FLAGS, - &ctx->tgtq, &ctx->cc_tgt); - if (code == 0) { - tkt_creds_set_state(context, ctx, ctx->state + 1, 0, - "tkt_creds_request_initial_tgt"); - ctx->tgtptr = &ctx->cc_tgt; - return 0; - } else if (HARD_CC_ERR(code)) { - return code; - } - krb5_free_cred_contents(context, &ctx->tgtq); + /* 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); } - - code = do_traversal_request(context, ctx, req); - - return code; -} - -static krb5_error_code -tkt_creds_reply_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = do_traversal_reply(context, ctx, rep); - if (code != 0) { - DPRINTF(("tkt_creds_reply_initial_tgt: failed to find initial TGT for referral\n")); + if (retval) { + DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n")); + goto cleanup; } - return code; -} + DUMP_PRINC("gc_from_kdc: server as requested", supplied_server); -/* - * Determine KDC options to use based on context options - */ -static krb5_flags -tkt_creds_kdcopt(krb5_tkt_creds_context ctx) -{ - krb5_flags kdcopt = ctx->kdcopt; - - if (ctx->state == TKT_CREDS_REFERRAL_TGT) - kdcopt |= KDC_OPT_CANONICALIZE; - - if (ctx->in_cred.second_ticket.length != 0 && + if (in_cred->second_ticket.length != 0 && (kdcopt & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) { kdcopt |= KDC_OPT_ENC_TKT_IN_SKEY; } - return kdcopt; -} - -static krb5_error_code -tkt_creds_request_referral_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - /* * Try requesting a service ticket from our local KDC with referrals * turned on. If the first referral succeeds, follow a referral-only * path, otherwise fall back to old-style assumptions. */ + /* * Save TGTPTR because we rewrite it in the referral loop, and * we might need to explicitly free it later. */ - if (ctx->referral_count >= KRB5_REFERRAL_MAXHOPS) { - code = KRB5_GET_IN_TKT_LOOP; /* XXX */ - goto cleanup; - } - - code = tkt_make_tgs_request(context, ctx, ctx->tgtptr, - tkt_creds_kdcopt(ctx), - &ctx->in_cred, req); - if (code != 0) - goto cleanup; - -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_reply_referral_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - unsigned int i; - - assert(ctx->subkey); - - code = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, - tkt_creds_kdcopt(ctx), &ctx->in_cred, - &ctx->out_cred); - if (code != 0) { - if (ctx->referral_count == 0) { - /* Fall through to non-referral case */ - tkt_creds_set_state(context, ctx, TKT_CREDS_FALLBACK_INITIAL_TGT, - 1, "tkt_creds_reply_referral_tgt"); - ctx->tgtptr = NULL; - code = 0; - } - goto cleanup; - } - - /* - * Referral request succeeded; let's see what it is - */ - if (krb5_principal_compare(context, ctx->server, ctx->out_cred->server)) { - DPRINTF(("gc_from_kdc: request generated ticket " - "for requested server principal\n")); - DUMP_PRINC("gc_from_kdc final referred reply", - ctx->server); - /* - * Check if the return enctype is one that we requested if - * needed. - */ - if (ctx->use_conf_ktypes || !context->tgs_etypes) { - tkt_creds_set_state(context, ctx, TKT_CREDS_COMPLETE, 0, - "tkt_creds_reply_referral_tgt"); + 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; } - for (i = 0; context->tgs_etypes[i]; i++) { - if (ctx->out_cred->keyblock.enctype == context->tgs_etypes[i]) { - /* Found an allowable etype, so we're done */ - tkt_creds_set_state(context, ctx, TKT_CREDS_COMPLETE, 0, - "tkt_creds_reply_referral_tgt"); + /* 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; + } } - } - context->use_conf_ktypes = ctx->use_conf_ktypes; - } else if (IS_TGS_PRINC(context, ctx->out_cred->server)) { - krb5_data *r1, *r2; - - DPRINTF(("gc_from_kdc: request generated referral tgt\n")); - DUMP_PRINC("gc_from_kdc credential received", - ctx->out_cred->server); - - assert(ctx->referral_count < KRB5_REFERRAL_MAXHOPS); - if (ctx->referral_count == 0) { - assert(ctx->tgtptr->server != NULL); - r1 = &ctx->tgtptr->server->data[1]; - } else { - assert(ctx->referral_tgts[ctx->referral_count - 1] != NULL); - r1 = &ctx->referral_tgts[ctx->referral_count -1 ]->server->data[1]; - } + /* + * 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. + */ - r2 = &ctx->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, ctx->out_cred); - ctx->out_cred = NULL; + /* 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; } - /* Check for referral routing loop. */ - for (i = 0; i < ctx->referral_count; i++) { - if (krb5_principal_compare(context, - ctx->out_cred->server, - ctx->referral_tgts[i]->server)) { - DFPRINTF((stderr, - "krb5_get_cred_from_kdc_opt: " - "referral routing loop - " - "got referral back to hop #%d\n", i)); - code = KRB5_KDC_UNREACH; - goto cleanup; + else if (IS_TGS_PRINC(context, (*out_cred)->server)) { + krb5_data *r1, *r2; + + DPRINTF(("gc_from_kdc: request generated referral tgt\n")); + DUMP_PRINC("gc_from_kdc credential received", + (*out_cred)->server); + + if (referral_count == 0) + r1 = &tgtptr->server->data[1]; + else + r1 = &referral_tgts[referral_count-1]->server->data[1]; + + r2 = &(*out_cred)->server->data[1]; + if (data_eq(*r1, *r2)) { + DPRINTF(("gc_from_kdc: referred back to " + "previous realm; fall back\n")); + krb5_free_creds(context, *out_cred); + *out_cred = NULL; + break; + } + /* Check for referral routing loop. */ + for (i=0;i<referral_count;i++) { +#if 0 + DUMP_PRINC("gc_from_kdc: loop compare #1", + (*out_cred)->server); + DUMP_PRINC("gc_from_kdc: loop compare #2", + referral_tgts[i]->server); +#endif + if (krb5_principal_compare(context, + (*out_cred)->server, + referral_tgts[i]->server)) { + DFPRINTF((stderr, + "krb5_get_cred_from_kdc_opt: " + "referral routing loop - " + "got referral back to hop #%d\n", i)); + retval=KRB5_KDC_UNREACH; + goto cleanup; + } + } + /* Point current tgt pointer at newly-received TGT. */ + if (tgtptr == &cc_tgt) + krb5_free_cred_contents(context, tgtptr); + tgtptr=*out_cred; + /* Save requested auth data with TGT in case it ends up stored */ + if (supplied_authdata != NULL) { + /* Ensure we note TGT contains authorization data */ + retval = krb5_copy_authdata(context, + supplied_authdata, + &(*out_cred)->authdata); + if (retval) + goto cleanup; } + /* Save pointer to tgt in referral_tgts. */ + referral_tgts[referral_count]=*out_cred; + *out_cred = NULL; + /* Copy krbtgt realm to server principal. */ + krb5_free_data_contents(context, &server->realm); + retval = krb5int_copy_data_contents(context, + &tgtptr->server->data[1], + &server->realm); + if (retval) + goto cleanup; + /* Don't ask for KDC to add auth data multiple times */ + in_cred->authdata = NULL; + /* + * Future work: rewrite server principal per any + * supplied padata. + */ + } else { + /* Not a TGT; punt to fallback. */ + krb5_free_creds(context, *out_cred); + *out_cred = NULL; + break; } - /* Point current tgt pointer at newly-received TGT. */ - ctx->tgtptr = ctx->out_cred; - - /* avoid copying authdata multiple times */ - ctx->out_cred->authdata = ctx->in_cred.authdata; - ctx->in_cred.authdata = NULL; - - /* Save pointer to tgt in referral_tgts */ - ctx->referral_tgts[ctx->referral_count++] = ctx->out_cred; - ctx->out_cred = NULL; - - /* Copy krbtgt realm to server principal */ - krb5_free_data_contents(context, &ctx->server->realm); - code = krb5int_copy_data_contents(context, - &ctx->tgtptr->server->data[1], - &ctx->server->realm); - if (code != 0) - goto cleanup; - - /* Future work: rewrite SPN in padata */ - tkt_creds_set_state(context, ctx, TKT_CREDS_REFERRAL_TGT, 0, - "tkt_creds_reply_referral_tgt"); - } else { - /* Not a TGT; punt to fallback */ - krb5_free_creds(context, ctx->out_cred); - ctx->out_cred = NULL; - tkt_creds_set_state(context, ctx, TKT_CREDS_FALLBACK_INITIAL_TGT, 1, - "tkt_creds_reply_referral_tgt"); - ctx->tgtptr = NULL; } -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_request_fallback_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - char **hrealms; - - DUMP_PRINC("gc_from_kdc client at fallback", ctx->client); - DUMP_PRINC("gc_from_kdc server at fallback", ctx->server); + DUMP_PRINC("gc_from_kdc client at fallback", client); + DUMP_PRINC("gc_from_kdc server at fallback", server); /* * At this point referrals have been tried and have failed. Go * back to the server principal as originally issued and try the * conventional path. */ - if (ctx->is_referral_realm) { - if (ctx->server->length >= 2) { - code = krb5_get_fallback_host_realm(context, - &ctx->server->data[1], + + /* + * 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 (code != 0) - goto cleanup; - krb5_free_data_contents(context, &ctx->server->realm); - ctx->server->realm.data = hrealms[0]; - ctx->server->realm.length = strlen(hrealms[0]); + 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 { + } + else { /* * Problem case: Realm tagged for referral but apparently not * in a <type>/<host> format that @@ -1621,74 +1219,99 @@ tkt_creds_request_fallback_initial_tgt(krb5_context context, */ DPRINTF(("gc_from_kdc: referral specified " "but no fallback realm avaiable!\n")); - code = KRB5_ERR_HOST_REALM_UNKNOWN; + retval = KRB5_ERR_HOST_REALM_UNKNOWN; goto cleanup; } } DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite", - ctx->server); + server); /* * Get a TGT for the target realm. */ - code = tkt_creds_request_initial_tgt(context, ctx, req); - -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_reply_fallback_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = tkt_creds_reply_initial_tgt(context, ctx, rep); - - return code; -} - -static krb5_error_code -tkt_creds_request_fallback_final_tkt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - - code = tkt_make_tgs_request(context, ctx, ctx->tgtptr, - tkt_creds_kdcopt(ctx), - &ctx->in_cred, req); - return code; -} - -static krb5_error_code -tkt_creds_reply_fallback_final_tkt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, - tkt_creds_kdcopt(ctx), &ctx->in_cred, - &ctx->out_cred); - - return code; -} + krb5_free_cred_contents(context, &tgtq); + retval = tgt_mcred(context, client, server, client, &tgtq); + if (retval) + goto cleanup; -static krb5_error_code -tkt_creds_request_complete(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code = 0; + /* Fast path: Is it in the ccache? */ + /* Free tgtptr data if reused from above. */ + if (tgtptr == &cc_tgt) + krb5_free_cred_contents(context, tgtptr); + tgtptr = NULL; + /* Free saved TGT in OTGTPTR if it was off-path. */ + if (tgtptr_isoffpath) + krb5_free_creds(context, otgtptr); + otgtptr = NULL; + /* Free TGTS if previously filled by do_traversal() */ + if (*tgts != NULL) { + for (i = 0; (*tgts)[i] != NULL; i++) { + krb5_free_creds(context, (*tgts)[i]); + } + free(*tgts); + *tgts = NULL; + } + context->use_conf_ktypes = 1; + retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS, + &tgtq, &cc_tgt); + if (!retval) { + tgtptr = &cc_tgt; + } else if (!HARD_CC_ERR(retval)) { + tgtptr_isoffpath = 0; + retval = do_traversal(context, ccache, client, server, + &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath); + } + if (retval) + goto cleanup; + otgtptr = tgtptr; - DUMP_PRINC("gc_from_kdc: final server after reversion", ctx->server); + /* + * Finally have TGT for target realm! Try using it to get creds. + */ - assert(ctx->out_cred); + if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) { + retval = KRB5_PROG_ETYPE_NOSUPP; + goto cleanup; + } + context->use_conf_ktypes = old_use_conf_ktypes; + retval = krb5_get_cred_via_tkt(context, tgtptr, + FLAGS2OPTS(tgtptr->ticket_flags) | + kdcopt, + tgtptr->addresses, in_cred, out_cred); +cleanup: + krb5_free_cred_contents(context, &tgtq); + if (tgtptr == &cc_tgt) + krb5_free_cred_contents(context, tgtptr); + if (tgtptr_isoffpath) + krb5_free_creds(context, otgtptr); + context->use_conf_ktypes = old_use_conf_ktypes; + /* Drop the original principal back into in_cred so that it's cached + in the expected format. */ + DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup", + server); + krb5_free_principal(context, server); + in_cred->server = supplied_server; + in_cred->authdata = supplied_authdata; + if (*out_cred && !retval) { + /* Success: free server, swap supplied server back in. */ + krb5_free_principal (context, (*out_cred)->server); + (*out_cred)->server = out_supplied_server; + assert((*out_cred)->authdata == NULL); + (*out_cred)->authdata = out_supplied_authdata; + } + else { + /* + * Failure: free out_supplied_server. Don't free out_cred here + * since it's either null or a referral TGT that we free below, + * and we may need it to return. + */ + krb5_free_principal(context, out_supplied_server); + krb5_free_authdata(context, out_supplied_authdata); + } + DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server); /* * Deal with ccache TGT management: If tgts has been set from * initial non-referral TGT discovery, leave it alone. Otherwise, if @@ -1697,245 +1320,53 @@ tkt_creds_request_complete(krb5_context context, * local KDC.) This is part of cleanup because useful received TGTs * should be cached even if the main request resulted in failure. */ - if (ctx->tgts == NULL && ctx->referral_tgts[0] != NULL) { - /* Allocate returnable TGT list. */ - ctx->tgts = calloc(2, sizeof (krb5_creds *)); - if (ctx->tgts == NULL) { - code = ENOMEM; - goto cleanup; + 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 } - code = krb5_copy_creds(context, ctx->referral_tgts[0], &ctx->tgts[0]); - if (code != 0) - goto cleanup; } - krb5_free_principal(context, ctx->out_cred->server); - ctx->out_cred->server = ctx->supplied_server; - ctx->supplied_server = NULL; - - assert(ctx->out_cred->authdata == NULL); - code = krb5_copy_authdata(context, ctx->in_cred.authdata, - &ctx->out_cred->authdata); - if (code != 0) - goto cleanup; - -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_step_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - enum krb5_tkt_creds_state state; - - context->use_conf_ktypes = 1; - - do { - krb5_free_cred_contents(context, &ctx->tgtq); - - krb5_free_keyblock(context, ctx->subkey); - ctx->subkey = NULL; - - switch ((state = ctx->state)) { - case TKT_CREDS_INITIAL_TGT: - code = tkt_creds_request_initial_tgt(context, ctx, req); - break; - case TKT_CREDS_REFERRAL_TGT: - case TKT_CREDS_REFERRAL_TGT_NOCANON: - code = tkt_creds_request_referral_tgt(context, ctx, req); - break; - case TKT_CREDS_FALLBACK_INITIAL_TGT: - code = tkt_creds_request_fallback_initial_tgt(context, ctx, req); - break; - case TKT_CREDS_FALLBACK_FINAL_TKT: - code = tkt_creds_request_fallback_final_tkt(context, ctx, req); - break; - case TKT_CREDS_COMPLETE: - code = tkt_creds_request_complete(context, ctx, req); - break; - default: - assert(0 && "tkt_creds_step_request invalid state"); - break; - } - /* If there has been a state transition, then try again. */ - } while (code == 0 && ctx->state != state); - - context->use_conf_ktypes = ctx->use_conf_ktypes; - - return code; -} - -static krb5_error_code -tkt_creds_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - context->use_conf_ktypes = 1; - - { - krb5_free_creds(context, ctx->out_cred); - ctx->out_cred = NULL; - - assert(ctx->subkey); - - switch (ctx->state) { - case TKT_CREDS_INITIAL_TGT: - code = tkt_creds_reply_initial_tgt(context, ctx, rep); - break; - case TKT_CREDS_REFERRAL_TGT: - case TKT_CREDS_REFERRAL_TGT_NOCANON: - code = tkt_creds_reply_referral_tgt(context, ctx, rep); - break; - case TKT_CREDS_FALLBACK_INITIAL_TGT: - code = tkt_creds_reply_fallback_initial_tgt(context, ctx, rep); - break; - case TKT_CREDS_FALLBACK_FINAL_TKT: - code = tkt_creds_reply_fallback_final_tkt(context, ctx, rep); - break; - case TKT_CREDS_COMPLETE: - default: - assert(0 && "tkt_creds_reply invalid state"); - break; + /* Free referral TGTs list. */ + for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) { + if(referral_tgts[i]) { + krb5_free_creds(context, referral_tgts[i]); } } - - context->use_conf_ktypes = ctx->use_conf_ktypes; - - 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, code2; - - *flags = 0; - - out->data = NULL; - out->length = 0; - - realm->data = NULL; - realm->length = 0; - - if (in != NULL && in->length != 0) { - code = tkt_creds_reply(context, ctx, in); - if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG) { - code2 = krb5int_copy_data_contents(context, - &ctx->encoded_previous_request, - out); - if (code2 != 0) - code = code2; - goto copy_realm; - } - if (code != 0) - goto cleanup; - } - - code = tkt_creds_step_request(context, ctx, out); - if (code != 0) - goto cleanup; - - if (ctx->state == TKT_CREDS_COMPLETE) { - *flags = 1; - goto cleanup; - } - - assert(out->length != 0); - - code = krb5int_copy_data_contents(context, - out, - &ctx->encoded_previous_request); - if (code != 0) - goto cleanup; - -copy_realm: - assert(ctx->realm && ctx->realm->length); - - code2 = krb5int_copy_data_contents(context, ctx->realm, realm); - if (code2 != 0) { - code = code2; - goto cleanup; - } - -cleanup: - return code; -} - -/* - * 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_cred all may point to - * the same credential at different times. - * - * Returns errors, system errors. - */ - -krb5_error_code -krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts, int kdcopt) -{ - krb5_error_code code; - krb5_tkt_creds_context ctx = NULL; - - code = krb5_tkt_creds_init(context, ccache, in_cred, kdcopt, &ctx); - if (code != 0) - goto cleanup; - - code = krb5_tkt_creds_get(context, ctx); - if (code != 0) - goto cleanup; - - *out_cred = ctx->out_cred; - ctx->out_cred = NULL; - -cleanup: - if (ctx != NULL) { - *tgts = ctx->tgts; - ctx->tgts = NULL; - } - - krb5_tkt_creds_free(context, ctx); - - return code; + DPRINTF(("gc_from_kdc finishing with %s\n", + retval ? error_message(retval) : "no error")); + return retval; } krb5_error_code diff --git a/src/lib/krb5/krb/gc_frm_kdc_step.c b/src/lib/krb5/krb/gc_frm_kdc_step.c index b611c51..84a60ef 100644 --- a/src/lib/krb5/krb/gc_frm_kdc_step.c +++ b/src/lib/krb5/krb/gc_frm_kdc_step.c @@ -39,79 +39,16 @@ #include "int-proto.h" /* - * 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; -}; - -/* - * State struct for do_traversal() and helpers. - * - * CUR_TGT and NXT_TGT can each point either into CC_TGTS or into - * KDC_TGTS. - * - * CUR_TGT is the "working" TGT, which will be used to obtain new - * TGTs. NXT_TGT will be CUR_TGT for the next iteration of the loop. - * - * Part of the baroqueness of this setup is to deal with annoying - * differences between krb5_cc_retrieve_cred() and - * krb5_get_cred_via_tkt(); krb5_cc_retrieve_cred() fills in a - * caller-allocated krb5_creds, while krb5_get_cred_via_tkt() - * allocates a krb5_creds for return. - */ -struct tr_state { - krb5_principal *kdc_list; - unsigned int nkdcs; - krb5_principal *cur_kdc; - krb5_principal *nxt_kdc; - krb5_principal *lst_kdc; - krb5_creds *cur_tgt; - krb5_creds *nxt_tgt; - krb5_creds **kdc_tgts; - struct cc_tgts cc_tgts; - krb5_creds *cur_cc_tgt; - krb5_creds *nxt_cc_tgt; - unsigned int ntgts; - krb5_creds *offpath_tgt; -}; - -enum krb5_tkt_creds_state { - TKT_CREDS_INITIAL_TGT, /* TGT to client realm */ - TKT_CREDS_REFERRAL_TGT, /* Trying referrals with canon flag */ - TKT_CREDS_REFERRAL_TGT_NOCANON, /* Trying referrals without canon */ - TKT_CREDS_FALLBACK_INITIAL_TGT, /* Trying fallback TGT */ - TKT_CREDS_FALLBACK_FINAL_TKT, /* Trying fallback service ticket */ - TKT_CREDS_COMPLETE /* Final state */ -}; - -#ifdef DEBUG_GC_FRM_KDC -static char *tkt_creds_dbg_state[] = { - "INITIAL_TGT", - "REFERRAL_TGT", - "REFERRAL_TGT_NOCANON", - "FALLBACK_INITIAL_TGT", - "FINAL_TKT", - "COMPLETE" -}; -#endif /* DEBUG_GC_FRM_KDC */ - -/* * Asynchronous API request/response state */ struct _krb5_tkt_creds_context { - enum krb5_tkt_creds_state state; krb5_ccache ccache; krb5_creds in_cred; krb5_creds tgtq; krb5_data *realm; krb5_principal client; krb5_principal server; - krb5_principal supplied_server; + krb5_principal req_server; krb5_creds *out_cred; krb5_creds **tgts; int kdcopt; @@ -120,47 +57,13 @@ struct _krb5_tkt_creds_context { krb5_creds *tgtptr; krb5_creds *referral_tgts[KRB5_REFERRAL_MAXHOPS]; krb5_boolean use_conf_ktypes; - struct tr_state ts; krb5_timestamp timestamp; krb5_keyblock *subkey; krb5_data encoded_previous_request; int is_referral_realm; + int complete; }; -/* 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(krb5_context, krb5_tkt_creds_context); -static void shift_cc_tgts(krb5_context, krb5_tkt_creds_context); -static void clean_cc_tgts(krb5_context, krb5_tkt_creds_context); - -/* - * Debug support - */ -#ifdef DEBUG_GC_FRM_KDC - -#define TR_DBG(context, ctx, prog) tr_dbg(context, ctx, prog) -#define TR_DBG_RET(context, ctx, prog, ret) tr_dbg_ret(context, ctx, prog, ret) -#define TR_DBG_RTREE(context, ctx, prog, princ) tr_dbg_rtree(context, ctx, prog, princ) - -static void tr_dbg(krb5_context, krb5_tkt_creds_context, const char *); -static void tr_dbg_ret(krb5_context, krb5_tkt_creds_context, const char *, krb5_error_code); -static void tr_dbg_rtree(krb5_context, krb5_tkt_creds_context, const char *, krb5_principal); - -#else - -#define TR_DBG(context, ctx, prog) -#define TR_DBG_RET(context, ctx, prog, ret) -#define TR_DBG_RTREE(context, ctx, prog, princ) - -#endif /* !DEBUG_GC_FRM_KDC */ - #ifdef DEBUG_REFERRALS #define DPRINTF(x) printf x @@ -201,13 +104,6 @@ cleanup: #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK) /* - * Certain krb5_cc_retrieve_cred() errors are soft errors when looking - * for a cross-realm TGT. - */ -#define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND && \ - (r) != KRB5_CC_NOT_KTYPE) - -/* * Flags for ccache lookups of cross-realm TGTs. * * A cross-realm TGT may be issued by some other intermediate realm's @@ -220,49 +116,6 @@ cleanup: */ static krb5_error_code tgt_mcred(krb5_context, krb5_principal, krb5_principal, krb5_principal, krb5_creds *); -static krb5_error_code retr_local_tgt(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code try_ccache(krb5_context context, - krb5_tkt_creds_context, krb5_creds *); -static krb5_error_code find_nxt_kdc(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code try_kdc_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code try_kdc_reply(krb5_context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code kdc_mcred(krb5_context context, - krb5_tkt_creds_context, krb5_principal, - krb5_creds *mcreds); -static krb5_error_code next_closest_tgt_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code next_closest_tgt_reply(krb5_context context, - krb5_tkt_creds_context, - krb5_data *); -static krb5_error_code init_rtree(krb5_context context, - krb5_tkt_creds_context); -static krb5_error_code do_traversal_request(krb5_context context, - krb5_tkt_creds_context, - krb5_data *req); -static krb5_error_code do_traversal_reply(krb5_context context, - krb5_tkt_creds_context, - krb5_data *req); - -static krb5_error_code chase_offpath_request(krb5_context, - krb5_tkt_creds_context, - krb5_data *data); -static krb5_error_code chase_offpath_reply(krb5_context, - krb5_tkt_creds_context, - krb5_data *data); -static krb5_error_code offpath_loopchk(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_creds *tgt, - krb5_creds *reftgts[], - unsigned int rcount); - -#define TR_STATE(_ctx) (&(_ctx)->ts) /* * tkt_make_tgs_request() @@ -353,124 +206,6 @@ tkt_process_tgs_reply(krb5_context context, } /* - * 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(krb5_context context, krb5_tkt_creds_context ctx) -{ - struct tr_state *ts = TR_STATE(ctx); - - ts->cc_tgts.cur = 0; - ts->cc_tgts.nxt = 1; - ts->cur_cc_tgt = &ts->cc_tgts.cred[0]; - ts->nxt_cc_tgt = &ts->cc_tgts.cred[1]; -} - -/* - * shift_cc_tgts() - * - * Given a fresh assignment to NXT_CC_TGT, mark NXT_CC_TGT as dirty, - * and shift indices so old NXT_CC_TGT becomes new CUR_CC_TGT. Clean - * the new NXT_CC_TGT. - */ -static void -shift_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) -{ - unsigned int i; - struct cc_tgts *rb; - struct tr_state *ts = TR_STATE(ctx); - - rb = &ts->cc_tgts; - i = rb->cur = rb->nxt; - rb->dirty[i] = 1; - ts->cur_cc_tgt = ts->nxt_cc_tgt; - - i = (i + 1) % NCC_TGTS; - - rb->nxt = i; - ts->nxt_cc_tgt = &rb->cred[i]; - if (rb->dirty[i]) { - krb5_free_cred_contents(context, &rb->cred[i]); - rb->dirty[i] = 0; - } -} - -/* - * clean_cc_tgts() - * - * Free CC_TGTS which were dirty, then mark them clean. - */ -static void -clean_cc_tgts(krb5_context context, krb5_tkt_creds_context ctx) -{ - unsigned int i; - struct cc_tgts *rb; - struct tr_state *ts = TR_STATE(ctx); - - rb = &ts->cc_tgts; - for (i = 0; i < NCC_TGTS; i++) { - if (rb->dirty[i]) { - krb5_free_cred_contents(context, &rb->cred[i]); - rb->dirty[i] = 0; - } - } -} - -/* - * Debug support - */ -#ifdef DEBUG_GC_FRM_KDC -static void -tr_dbg(krb5_context context, krb5_tkt_creds_context ctx, const char *prog) -{ - krb5_error_code retval; - char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str; - struct tr_state *ts = TR_STATE(ctx); - - cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL; - retval = krb5_unparse_name(context, ts->cur_tgt->server, &cur_tgt_str); - if (retval) goto cleanup; - retval = krb5_unparse_name(context, *ts->cur_kdc, &cur_kdc_str); - if (retval) goto cleanup; - retval = krb5_unparse_name(context, *ts->nxt_kdc, &nxt_kdc_str); - if (retval) goto cleanup; - fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str); - fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str); - fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str); -cleanup: - if (cur_tgt_str) - krb5_free_unparsed_name(context, cur_tgt_str); - if (cur_kdc_str) - krb5_free_unparsed_name(context, cur_kdc_str); - if (nxt_kdc_str) - krb5_free_unparsed_name(context, nxt_kdc_str); -} - -static void -tr_dbg_ret(krb5_context context, krb5_tkt_creds_context ctx, - const char *prog, krb5_error_code ret) -{ - fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret, - error_message(ret)); -} - -static void -tr_dbg_rtree(krb5_context context, krb5_tkt_creds_context ctx, - const char *prog, krb5_principal princ) -{ - char *str; - - if (krb5_unparse_name(context, princ, &str)) - return; - fprintf(stderr, "%s: %s\n", prog, str); - krb5_free_unparsed_name(context, str); -} -#endif /* DEBUG_GC_FRM_KDC */ - -/* * tgt_mcred() * * Return MCREDS for use as a match criterion. @@ -507,696 +242,6 @@ cleanup: } /* - * init_rtree() - * - * Populate KDC_LIST with the output of krb5_walk_realm_tree(). - */ -static krb5_error_code -init_rtree(krb5_context context, krb5_tkt_creds_context ctx) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - if (ts->kdc_list != NULL) { - krb5_free_realm_tree(context, ts->kdc_list); - ts->kdc_list = NULL; - } - - retval = krb5_walk_realm_tree(context, krb5_princ_realm(context, - ctx->client), - krb5_princ_realm(context, ctx->server), - &ts->kdc_list, KRB5_REALM_BRANCH_CHAR); - if (retval) - return retval; - - for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) { - assert(krb5_princ_size(context, ts->kdc_list[ts->nkdcs]) == 2); - TR_DBG_RTREE(context, ctx, "init_rtree", ts->kdc_list[ts->nkdcs]); - } - assert(ts->nkdcs > 1); - ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1; - - ts->kdc_tgts = realloc(ts->kdc_tgts, (ts->nkdcs + 1) * sizeof(krb5_creds)); - if (ts->kdc_tgts == NULL) - return ENOMEM; - memset(ts->kdc_tgts, 0, (ts->nkdcs + 1) * sizeof(krb5_creds)); - - if (ts->offpath_tgt != NULL) { - assert(ctx->tgtptr != ts->offpath_tgt); - krb5_free_creds(context, ts->offpath_tgt); - ts->offpath_tgt = NULL; - } - - return 0; -} - -/* - * retr_local_tgt() - * - * Prime CUR_TGT with the cached TGT of the client's local realm. - */ -static krb5_error_code -retr_local_tgt(krb5_context context, krb5_tkt_creds_context ctx) -{ - krb5_error_code retval; - krb5_creds tgtq; - struct tr_state *ts = TR_STATE(ctx); - - memset(&tgtq, 0, sizeof(tgtq)); - retval = tgt_mcred(context, ctx->client, ctx->client, ctx->client, &tgtq); - if (retval) - return retval; - - /* Match realm, unlike other ccache retrievals here. */ - retval = krb5_cc_retrieve_cred(context, ctx->ccache, - KRB5_TC_SUPPORTED_KTYPES, - &tgtq, ts->nxt_cc_tgt); - krb5_free_cred_contents(context, &tgtq); - if (!retval) { - shift_cc_tgts(context, ctx); - ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt; - } - return retval; -} - -/* - * try_ccache() - * - * Attempt to retrieve desired NXT_TGT from ccache. Point NXT_TGT to - * it if successful. - */ -static krb5_error_code -try_ccache(krb5_context context, krb5_tkt_creds_context ctx, krb5_creds *tgtq) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - TR_DBG(context, ctx, "try_ccache"); - retval = krb5_cc_retrieve_cred(context, ctx->ccache, RETR_FLAGS, - tgtq, ts->nxt_cc_tgt); - if (!retval) { - shift_cc_tgts(context, ctx); - ts->nxt_tgt = ts->cur_cc_tgt; - } - TR_DBG_RET(context, ctx, "try_ccache", retval); - return retval; -} - -/* - * find_nxt_kdc() - * - * A NXT_TGT gotten from an intermediate KDC might actually be a - * referral. Search KDC_LIST forward starting from CUR_KDC, looking - * for the KDC with the same remote realm as NXT_TGT. If we don't - * find it, the intermediate KDC is leading us off the transit path. - * - * Match on CUR_KDC's remote realm, not local realm, because, among - * other reasons, we can get a referral to the final realm; e.g., - * given - * - * KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, - * krbtgt/R4@R3, NULL } - * CUR_TGT->SERVER == krbtgt/R2@R1 - * NXT_TGT->SERVER == krbtgt/R4@R2 - * - * i.e., we got a ticket issued by R2 with remote realm R4, we want to - * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT - * with R3 as its local realm. - * - * Set up for next iteration of do_traversal() loop by pointing - * NXT_KDC to one entry forward of the match. - */ -static krb5_error_code -find_nxt_kdc(krb5_context context, krb5_tkt_creds_context ctx) -{ - krb5_data *r1, *r2; - krb5_principal *kdcptr; - struct tr_state *ts = TR_STATE(ctx); - - TR_DBG(context, ctx, "find_nxt_kdc"); - assert(ts->ntgts > 0); - assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); - if (krb5_princ_size(context, ts->nxt_tgt->server) != 2) - return KRB5_KDCREP_MODIFIED; - - r1 = krb5_princ_component(context, ts->nxt_tgt->server, 1); - - for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) { - - r2 = krb5_princ_component(context, *kdcptr, 1); - - if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) { - break; - } - } - if (*kdcptr != NULL) { - ts->nxt_kdc = kdcptr; - TR_DBG_RET(context, ctx, "find_nxt_kdc", 0); - return 0; - } - - r2 = krb5_princ_component(context, ts->kdc_list[0], 1); - if (r1 != NULL && r2 != NULL && - r1->length == r2->length && - !memcmp(r1->data, r2->data, r1->length)) { - TR_DBG_RET(context, ctx, "find_nxt_kdc: looped back to local", - KRB5_KDCREP_MODIFIED); - return KRB5_KDCREP_MODIFIED; - } - - /* - * Realm is not in our list; we probably got an unexpected realm - * referral. - */ - ts->offpath_tgt = ts->nxt_tgt; - if (ts->cur_kdc == ts->kdc_list) { - /* - * Local KDC referred us off path; trust it for caching - * purposes. - */ - return 0; - } - /* - * Unlink the off-path TGT from KDC_TGTS but don't free it, - * because we should return it. - */ - ts->kdc_tgts[--ts->ntgts] = NULL; - ts->nxt_tgt = ts->cur_tgt; - TR_DBG_RET(context, ctx, "find_nxt_kdc", 0); - return 0; -} - -/* - * try_kdc_request()/try_kdc_reply() - * - * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_KDC if - * successful. - */ -static krb5_error_code -try_kdc_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - TR_DBG(context, ctx, "try_kdc_request"); - - retval = tkt_make_tgs_request(context, ctx, ts->cur_tgt, 0, - &ctx->tgtq, req); - - TR_DBG_RET(context, ctx, "try_kdc_request", retval); - - return retval; -} - -static krb5_error_code -try_kdc_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - TR_DBG(context, ctx, "try_kdc_reply"); - retval = tkt_process_tgs_reply(context, ctx, rep, ts->cur_tgt, 0, - &ctx->tgtq, &ts->kdc_tgts[ts->ntgts++]); - if (retval != 0) { - ts->ntgts--; - ts->nxt_tgt = ts->cur_tgt; - TR_DBG_RET(context, ctx, "try_kdc_reply", retval); - return retval; - } - ts->nxt_tgt = ts->kdc_tgts[ts->ntgts - 1]; - retval = find_nxt_kdc(context, ctx); - TR_DBG_RET(context, ctx, "try_kdc_reply", retval); - return retval; -} - -/* - * kdc_mcred() - * - * Return MCREDS for use as a match criterion. - * - * Resulting credential has CLIENT as the client principal, and - * krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server - * principal. Zeroes MCREDS first, does not allocate MCREDS, and - * cleans MCREDS on failure. - */ -static krb5_error_code -kdc_mcred(krb5_context context, krb5_tkt_creds_context ctx, - krb5_principal client, krb5_creds *mcreds) -{ - krb5_error_code retval; - krb5_data *rdst, *rsrc; - struct tr_state *ts = TR_STATE(ctx); - - memset(mcreds, 0, sizeof(*mcreds)); - - rdst = krb5_princ_component(context, *ts->nxt_kdc, 1); - rsrc = krb5_princ_component(context, *ts->cur_kdc, 1); - - retval = krb5_copy_principal(context, client, &mcreds->client); - if (retval != 0) - goto cleanup; - - retval = krb5_tgtname(context, rdst, rsrc, &mcreds->server); - if (retval != 0) - goto cleanup; - -cleanup: - if (retval != 0) - krb5_free_cred_contents(context, mcreds); - - return retval; -} - -static void -tkt_creds_set_state(krb5_context context, - krb5_tkt_creds_context ctx, - enum krb5_tkt_creds_state newstate, - int freeReferrals, - const char *msg) -{ - unsigned int i; - - assert(newstate >= TKT_CREDS_INITIAL_TGT); - assert(newstate <= TKT_CREDS_COMPLETE); - -#ifdef DEBUG_GC_FRM_KDC - fprintf(stderr, "tkt_creds_set_state %s->%s %s referrals\n", - tkt_creds_dbg_state[ctx->state], - tkt_creds_dbg_state[newstate], - freeReferrals ? "free" : "do not free"); -#endif /* DEBUG_GC_FRM_KDC */ - - if (freeReferrals) { - for (i = 0; i < ctx->referral_count; i++) { - assert(ctx->tgtptr != ctx->referral_tgts[i]); - krb5_free_creds(context, ctx->referral_tgts[i]); - ctx->referral_tgts[i] = NULL; - } - } - - ctx->state = newstate; -} - -/* - * next_closest_tgt_request()/next_closest_tgt_reply() - * - * Using CUR_TGT, attempt to get the cross-realm TGT having its remote - * realm closest to the target principal's. Update NXT_TGT, NXT_KDC - * accordingly. - */ -static krb5_error_code -next_closest_tgt_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - - assert(ctx->tgtq.server == NULL); - - if (ts->nxt_kdc == NULL) { - /* Initial state */ - ts->nxt_kdc = ts->lst_kdc; - } else if (ts->nxt_kdc < ts->cur_kdc) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "next_closest_tgt_request"); - return 0; - } - - assert(ts->nxt_kdc >= ts->cur_kdc); - - retval = kdc_mcred(context, ctx, ctx->client, &ctx->tgtq); - if (retval != 0) - return retval; - - /* Don't waste time retrying ccache for direct path. */ - if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) { - retval = try_ccache(context, ctx, &ctx->tgtq); - if (retval == 0) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "next_closest_tgt_request"); - return 0; - } else if (HARD_CC_ERR(retval)) { - return retval; - } - } - - /* Not in the ccache, so talk to a KDC. */ - return try_kdc_request(context, ctx, req); -} - -static krb5_error_code -next_closest_tgt_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - struct tr_state *ts = TR_STATE(ctx); - - code = try_kdc_reply(context, ctx, rep); - if (code != 0) - ts->nxt_kdc--; - - return 0; -} - -/* - * do_traversal() - * - * Find final TGT needed to get CLIENT a ticket for SERVER. Point - * OUT_TGT at the desired TGT, which may be an existing cached TGT - * (copied into OUT_CC_TGT) or one of the newly obtained TGTs - * (collected in OUT_KDC_TGTS). - * - * Get comfortable; this is somewhat complicated. - * - * Nomenclature: Cross-realm TGS principal names have the form: - * - * krbtgt/REMOTE@LOCAL - * - * krb5_walk_realm_tree() returns a list like: - * - * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, ... - * - * These are prinicpal names, not realm names. We only really use the - * remote parts of the TGT principal names. - * - * The do_traversal loop calls next_closest_tgt() to find the next - * closest TGT to the destination realm. next_closest_tgt() updates - * NXT_KDC for the following iteration of the do_traversal() loop. - * - * At the beginning of any given iteration of the do_traversal() loop, - * CUR_KDC's remote realm is the remote realm of CUR_TGT->SERVER. The - * local realms of CUR_KDC and CUR_TGT->SERVER may not match due to - * short-circuit paths provided by intermediate KDCs, e.g., CUR_KDC - * might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B. - * - * For example, given KDC_LIST of - * - * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3, - * krbtgt/R5@R4 - * - * The next_closest_tgt() loop moves NXT_KDC to the left starting from - * R5, stopping before it reaches CUR_KDC. When next_closest_tgt() - * returns, the do_traversal() loop updates CUR_KDC to be NXT_KDC, and - * calls next_closest_tgt() again. - * - * next_closest_tgt() at start of its loop: - * - * CUR NXT - * | | - * V V - * +----+----+----+----+----+ - * | R1 | R2 | R3 | R4 | R5 | - * +----+----+----+----+----+ - * - * next_closest_tgt() returns after finding a ticket for krbtgt/R3@R1: - * - * CUR NXT - * | | - * V V - * +----+----+----+----+----+ - * | R1 | R2 | R3 | R4 | R5 | - * +----+----+----+----+----+ - * - * do_traversal() updates CUR_KDC: - * - * NXT - * CUR - * | - * V - * +----+----+----+----+----+ - * | R1 | R2 | R3 | R4 | R5 | - * +----+----+----+----+----+ - * - * next_closest_tgt() at start of its loop: - * - * CUR NXT - * | | - * V V - * +----+----+----+----+----+ - * | R1 | R2 | R3 | R4 | R5 | - * +----+----+----+----+----+ - * - * etc. - * - * The algorithm executes in n*(n-1)/2 (the sum of integers from 1 to - * n-1) attempts in the worst case, i.e., each KDC only has a - * cross-realm ticket for the immediately following KDC in the transit - * path. Typically, short-circuit paths will cause execution to occur - * faster than this worst-case scenario. - * - * When next_closest_tgt() updates NXT_KDC, it may not perform a - * simple increment from CUR_KDC, in part because some KDC may - * short-circuit pieces of the transit path. - */ -static krb5_error_code -do_traversal_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code retval = 0; - struct tr_state *ts = TR_STATE(ctx); - - if (ctx->tgtptr == NULL) { - /* Initial state */ - clean_cc_tgts(context, ctx); - init_cc_tgts(context, ctx); - - retval = init_rtree(context, ctx); - if (retval != 0) - goto cleanup; - - retval = retr_local_tgt(context, ctx); - if (retval != 0) - goto cleanup; - - ts->cur_kdc = ts->kdc_list; - ts->nxt_kdc = NULL; - } - - if (ts->cur_kdc == NULL || ts->cur_kdc >= ts->lst_kdc) { - /* termination condition */ - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "do_traversal_request"); - goto cleanup; - } - - if (ts->offpath_tgt == NULL) - retval = next_closest_tgt_request(context, ctx, req); - else - retval = chase_offpath_request(context, ctx, req); - -cleanup: - return retval; -} - -static krb5_error_code -do_traversal_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - int offpath = 0; - - assert(ctx->out_cred == NULL); - - if (ts->offpath_tgt != NULL) { - retval = chase_offpath_reply(context, ctx, rep); - if (retval != 0) - return retval; - - /* termination condition */ - offpath = 1; - } else { - retval = next_closest_tgt_reply(context, ctx, rep); - if (retval != 0) - return retval; - } - - if (offpath == 0) { -#if 0 - assert(ts->cur_kdc != ts->nxt_kdc); -#endif - ts->cur_kdc = ts->nxt_kdc; - ts->cur_tgt = ts->nxt_tgt; - } - - if (NXT_TGT_IS_CACHED(ts)) { - assert(ts->offpath_tgt == NULL); - krb5_free_cred_contents(context, &ctx->cc_tgt); - ctx->cc_tgt = *ts->cur_cc_tgt; - ctx->tgtptr = ts->cur_tgt; - MARK_CUR_CC_TGT_CLEAN(ts); - } else if (ts->offpath_tgt != NULL){ - ctx->tgtptr = ts->offpath_tgt; - } else { - /* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */ - ctx->tgtptr = ts->nxt_tgt; - } - - if (ts->cur_kdc == ts->lst_kdc || offpath) { - tkt_creds_set_state(context, ctx, ctx->state + 1, 1, - "do_traversal_reply"); - if (ts->ntgts != 0) { - krb5_free_tgt_creds(context, ctx->tgts); - ctx->tgts = ts->kdc_tgts; - ts->kdc_tgts = NULL; - } - } - - return retval; -} - -/* - * chase_offpath_request()/chase_offpath_reply() - * - * Chase off-path TGT referrals. - * - * If we are traversing a trusted path (either hierarchically derived - * or explicit capath) and get a TGT pointing to a realm off this - * path, query the realm referenced by that off-path TGT. Repeat - * until we get to the destination realm or encounter an error. - * - * CUR_TGT is always either pointing into REFTGTS or is an alias for - * TS->OFFPATH_TGT. - */ -static krb5_error_code -chase_offpath_request(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - krb5_data *rdst = krb5_princ_realm(context, ctx->server); - krb5_data *rsrc; - - if (ctx->referral_count == 0) { - /* Initial state */ - ts->cur_tgt = ts->offpath_tgt; - } else if (ctx->referral_count >= KRB5_REFERRAL_MAXHOPS) { - /* termination condition */ - retval = KRB5_KDCREP_MODIFIED; - goto cleanup; - } - - assert(ctx->tgtptr != NULL); - assert(ctx->tgtptr->server != NULL); - - rsrc = krb5_princ_component(context, ctx->tgtptr->server, 1); - - assert(ctx->tgtq.server == NULL); - - retval = krb5_tgtname(context, rdst, rsrc, &ctx->tgtq.server); - if (retval != 0) - goto cleanup; - - retval = krb5_copy_principal(context, ctx->client, &ctx->tgtq.client); - if (retval != 0) - goto cleanup; - - retval = tkt_make_tgs_request(context, ctx, ts->cur_tgt, 0, - &ctx->tgtq, req); - if (retval != 0) - goto cleanup; - -cleanup: - return retval; -} - -static krb5_error_code -chase_offpath_reply(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code retval; - struct tr_state *ts = TR_STATE(ctx); - krb5_creds *nxt_tgt = NULL; - krb5_data *rsrc; - krb5_data *rdst = krb5_princ_realm(context, ctx->server); - krb5_data *r1; - - rsrc = krb5_princ_component(context, ctx->tgtptr->server, 1); - - retval = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, 0, - &ctx->tgtq, &nxt_tgt); - if (retval != 0) - goto cleanup; - if (!IS_TGS_PRINC(context, nxt_tgt->server)) { - retval = KRB5_KDCREP_MODIFIED; - goto cleanup; - } - r1 = krb5_princ_component(context, nxt_tgt->server, 1); - if (rdst->length == r1->length && - !memcmp(rdst->data, r1->data, rdst->length)) { - retval = 0; - goto cleanup; - } - retval = offpath_loopchk(context, ctx, nxt_tgt, ctx->referral_tgts, - ctx->referral_count); - if (retval) - goto cleanup; - assert(ctx->referral_count < KRB5_REFERRAL_MAXHOPS - 1); - ctx->referral_tgts[ctx->referral_count++] = nxt_tgt; - ctx->tgtptr = nxt_tgt; - nxt_tgt = NULL; - -cleanup: - /* - * 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(context, ts->offpath_tgt); - ts->offpath_tgt = NULL; - } - if (nxt_tgt != NULL) { - if (retval != 0) - krb5_free_creds(context, nxt_tgt); - else - ts->offpath_tgt = nxt_tgt; - } - return retval; -} - -/* - * offpath_loopchk() - * - * Check for loop back to previously-visited realms, both off-path and - * on-path. - */ -static krb5_error_code -offpath_loopchk(krb5_context context, krb5_tkt_creds_context ctx, - krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount) -{ - krb5_data *r1, *r2; - unsigned int i; - struct tr_state *ts = TR_STATE(ctx); - - r1 = krb5_princ_component(context, tgt->server, 1); - for (i = 0; i < rcount; i++) { - r2 = krb5_princ_component(context, reftgts[i]->server, 1); - if (r1->length == r2->length && - !memcmp(r1->data, r2->data, r1->length)) - return KRB5_KDCREP_MODIFIED; - } - for (i = 0; i < ts->ntgts; i++) { - r2 = krb5_princ_component(context, ts->kdc_tgts[i]->server, 1); - if (r1->length == r2->length && - !memcmp(r1->data, r2->data, r1->length)) - return KRB5_KDCREP_MODIFIED; - } - return 0; -} - -/* * Asynchronous API */ krb5_error_code KRB5_CALLCONV @@ -1209,10 +254,8 @@ krb5_tkt_creds_init(krb5_context context, krb5_error_code code; krb5_tkt_creds_context ctx = NULL; - if (creds->client == NULL || creds->server == NULL) { - code = EINVAL; - goto cleanup; - } + assert(creds->client != NULL); + assert(creds->server != NULL); ctx = k5alloc(sizeof(*ctx), &code); if (code != 0) @@ -1228,25 +271,24 @@ krb5_tkt_creds_init(krb5_context context, ctx->client = ctx->in_cred.client; ctx->server = ctx->in_cred.server; - code = krb5_copy_principal(context, ctx->server, &ctx->supplied_server); + code = krb5_copy_principal(context, ctx->server, &ctx->req_server); if (code != 0) goto cleanup; - ctx->is_referral_realm = krb5_is_referral_realm(&ctx->server->realm); - if (ctx->is_referral_realm) { - /* Use the client realm. */ - DPRINTF(("krb5_tkt_creds_init: no server realm supplied, " - "using client realm\n")); - krb5_free_data_contents(context, &ctx->server->realm); - code = krb5int_copy_data_contents_add0(context, - &ctx->client->realm, - &ctx->server->realm); - if (code != 0) - goto cleanup; - } + code = tgt_mcred(context, ctx->client, ctx->client, + ctx->client, &ctx->tgtq); + if (code != 0) + goto cleanup; - /* The rest must be done by krb5_tkt_creds_step() */ + code = krb5_cc_retrieve_cred(context, ctx->ccache, RETR_FLAGS, + &ctx->tgtq, &ctx->cc_tgt); + if (code != 0) + goto cleanup; + + ctx->tgtptr = &ctx->cc_tgt; + krb5_free_cred_contents(context, &ctx->tgtq); + /* The rest must be done by krb5_tkt_creds_step() */ *pctx = ctx; cleanup: @@ -1261,8 +303,9 @@ krb5_tkt_creds_get_creds(krb5_context context, krb5_tkt_creds_context ctx, krb5_creds *creds) { - if (ctx->state != TKT_CREDS_COMPLETE) + if (ctx->complete == 0) return EINVAL; + return krb5int_copy_creds_contents(context, ctx->out_cred, creds); } @@ -1270,13 +313,12 @@ void KRB5_CALLCONV krb5_tkt_creds_free(krb5_context context, krb5_tkt_creds_context ctx) { - struct tr_state *ts; int i; if (ctx == NULL) return; - krb5_free_principal(context, ctx->supplied_server); + krb5_free_principal(context, ctx->req_server); krb5_free_cred_contents(context, &ctx->in_cred); krb5_free_cred_contents(context, &ctx->tgtq); krb5_free_creds(context, ctx->out_cred); @@ -1292,128 +334,9 @@ krb5_tkt_creds_free(krb5_context context, } } - clean_cc_tgts(context, ctx); - ts = TR_STATE(ctx); - if (ts->offpath_tgt != NULL) - krb5_free_creds(context, ts->offpath_tgt); - if (ts->kdc_list != NULL) - krb5_free_realm_tree(context, ts->kdc_list); - if (ts->kdc_tgts != NULL) - free(ts->kdc_tgts); - free(ctx); } -krb5_error_code KRB5_CALLCONV -krb5_tkt_creds_get(krb5_context context, - krb5_tkt_creds_context ctx) -{ - krb5_error_code code; - krb5_data request; - krb5_data reply; - krb5_data realm; - unsigned int flags = 0; - int tcp_only = 0; - int use_master = 0; - - request.length = 0; - request.data = NULL; - reply.length = 0; - reply.data = NULL; - realm.length = 0; - realm.data = NULL; - - if (ctx->state == TKT_CREDS_COMPLETE) { - return 0; - } - - for (;;) { - code = krb5_tkt_creds_step(context, - ctx, - &reply, - &request, - &realm, - &flags); - if (code == KRB5KRB_ERR_RESPONSE_TOO_BIG && !tcp_only) - tcp_only = 1; - else if (code != 0 || (flags & KRB5_TKT_CREDS_STEP_FLAG_COMPLETE)) - break; - - krb5_free_data_contents(context, &reply); - - code = krb5_sendto_kdc(context, &request, &realm, - &reply, &use_master, tcp_only); - if (code != 0) - break; - - krb5_free_data_contents(context, &request); - krb5_free_data_contents(context, &realm); - } - - krb5_free_data_contents(context, &request); - krb5_free_data_contents(context, &reply); - krb5_free_data_contents(context, &realm); - - return code; -} - -/* - * Retrieve 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. - */ -static krb5_error_code -tkt_creds_request_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - - DUMP_PRINC("tkt_creds_request_initial_tgt: server", ctx->server); - - assert(ctx->tgtq.server == NULL); - - if (ctx->tgtptr == NULL) { - code = tgt_mcred(context, ctx->client, ctx->server, - ctx->client, &ctx->tgtq); - if (code != 0) - return code; - - /* Fast path: is it in the ccache? */ - krb5_free_cred_contents(context, &ctx->cc_tgt); - code = krb5_cc_retrieve_cred(context, ctx->ccache, RETR_FLAGS, - &ctx->tgtq, &ctx->cc_tgt); - if (code == 0) { - tkt_creds_set_state(context, ctx, ctx->state + 1, 0, - "tkt_creds_request_initial_tgt"); - ctx->tgtptr = &ctx->cc_tgt; - return 0; - } else if (HARD_CC_ERR(code)) { - return code; - } - krb5_free_cred_contents(context, &ctx->tgtq); - } - - code = do_traversal_request(context, ctx, req); - - return code; -} - -static krb5_error_code -tkt_creds_reply_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = do_traversal_reply(context, ctx, rep); - if (code != 0) { - DPRINTF(("tkt_creds_reply_initial_tgt: failed to find initial TGT for referral\n")); - } - - return code; -} - /* * Determine KDC options to use based on context options */ @@ -1422,8 +345,7 @@ tkt_creds_kdcopt(krb5_tkt_creds_context ctx) { krb5_flags kdcopt = ctx->kdcopt; - if (ctx->state == TKT_CREDS_REFERRAL_TGT) - kdcopt |= KDC_OPT_CANONICALIZE; + kdcopt |= KDC_OPT_CANONICALIZE; if (ctx->in_cred.second_ticket.length != 0 && (kdcopt & KDC_OPT_CNAME_IN_ADDL_TKT) == 0) { @@ -1454,6 +376,16 @@ tkt_creds_request_referral_tgt(krb5_context context, goto cleanup; } + assert(ctx->tgtptr); + + /* Copy krbtgt realm to server principal */ + krb5_free_data_contents(context, &ctx->server->realm); + code = krb5int_copy_data_contents(context, + &ctx->tgtptr->server->data[1], + &ctx->server->realm); + if (code != 0) + goto cleanup; + code = tkt_make_tgs_request(context, ctx, ctx->tgtptr, tkt_creds_kdcopt(ctx), &ctx->in_cred, req); @@ -1465,6 +397,51 @@ cleanup: } static krb5_error_code +tkt_creds_complete(krb5_context context, krb5_tkt_creds_context ctx) +{ + krb5_error_code code = 0; + + assert(ctx->out_cred); + + /* + * Deal with ccache TGT management: If tgts has been set from + * initial non-referral TGT discovery, leave it alone. Otherwise, if + * referral_tgts[0] exists return it as the only entry in tgts. + * (Further referrals are never cached, only the referral from the + * local KDC.) This is part of cleanup because useful received TGTs + * should be cached even if the main request resulted in failure. + */ + if (ctx->tgts == NULL && ctx->referral_tgts[0] != NULL) { + /* Allocate returnable TGT list. */ + ctx->tgts = k5alloc(2 * sizeof (krb5_creds *), &code); + if (code != 0) + goto cleanup; + + code = krb5_copy_creds(context, ctx->referral_tgts[0], &ctx->tgts[0]); + if (code != 0) + goto cleanup; + } + + DUMP_PRINC("tkt_creds_complete: final server after reversion", ctx->server); + + krb5_free_principal(context, ctx->out_cred->server); + ctx->out_cred->server = ctx->req_server; + ctx->req_server = NULL; + + assert(ctx->out_cred->authdata == NULL); + code = krb5_copy_authdata(context, ctx->in_cred.authdata, + &ctx->out_cred->authdata); + if (code != 0) + goto cleanup; + + ctx->complete = 1; + +cleanup: + return code; +} + + +static krb5_error_code tkt_creds_reply_referral_tgt(krb5_context context, krb5_tkt_creds_context ctx, krb5_data *rep) @@ -1477,16 +454,8 @@ tkt_creds_reply_referral_tgt(krb5_context context, code = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, tkt_creds_kdcopt(ctx), &ctx->in_cred, &ctx->out_cred); - if (code != 0) { - if (ctx->referral_count == 0) { - /* Fall through to non-referral case */ - tkt_creds_set_state(context, ctx, TKT_CREDS_FALLBACK_INITIAL_TGT, - 1, "tkt_creds_reply_referral_tgt"); - ctx->tgtptr = NULL; - code = 0; - } + if (code != 0) goto cleanup; - } /* * Referral request succeeded; let's see what it is @@ -1501,15 +470,13 @@ tkt_creds_reply_referral_tgt(krb5_context context, * needed. */ if (ctx->use_conf_ktypes || !context->tgs_etypes) { - tkt_creds_set_state(context, ctx, TKT_CREDS_COMPLETE, 0, - "tkt_creds_reply_referral_tgt"); + code = tkt_creds_complete(context, ctx); goto cleanup; } for (i = 0; context->tgs_etypes[i]; i++) { if (ctx->out_cred->keyblock.enctype == context->tgs_etypes[i]) { /* Found an allowable etype, so we're done */ - tkt_creds_set_state(context, ctx, TKT_CREDS_COMPLETE, 0, - "tkt_creds_reply_referral_tgt"); + code = tkt_creds_complete(context, ctx); goto cleanup; } } @@ -1527,7 +494,7 @@ tkt_creds_reply_referral_tgt(krb5_context context, r1 = &ctx->tgtptr->server->data[1]; } else { assert(ctx->referral_tgts[ctx->referral_count - 1] != NULL); - r1 = &ctx->referral_tgts[ctx->referral_count -1 ]->server->data[1]; + r1 = &ctx->referral_tgts[ctx->referral_count - 1]->server->data[1]; } r2 = &ctx->out_cred->server->data[1]; @@ -1562,164 +529,11 @@ tkt_creds_reply_referral_tgt(krb5_context context, ctx->referral_tgts[ctx->referral_count++] = ctx->out_cred; ctx->out_cred = NULL; - /* Copy krbtgt realm to server principal */ - krb5_free_data_contents(context, &ctx->server->realm); - code = krb5int_copy_data_contents(context, - &ctx->tgtptr->server->data[1], - &ctx->server->realm); - if (code != 0) - goto cleanup; - /* Future work: rewrite SPN in padata */ - tkt_creds_set_state(context, ctx, TKT_CREDS_REFERRAL_TGT, 0, - "tkt_creds_reply_referral_tgt"); } else { - /* Not a TGT; punt to fallback */ - krb5_free_creds(context, ctx->out_cred); - ctx->out_cred = NULL; - tkt_creds_set_state(context, ctx, TKT_CREDS_FALLBACK_INITIAL_TGT, 1, - "tkt_creds_reply_referral_tgt"); - ctx->tgtptr = NULL; - } - -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_request_fallback_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - char **hrealms; - - DUMP_PRINC("gc_from_kdc client at fallback", ctx->client); - DUMP_PRINC("gc_from_kdc server at fallback", ctx->server); - - /* - * At this point referrals have been tried and have failed. Go - * back to the server principal as originally issued and try the - * conventional path. - */ - if (ctx->is_referral_realm) { - if (ctx->server->length >= 2) { - code = krb5_get_fallback_host_realm(context, - &ctx->server->data[1], - &hrealms); - if (code != 0) - goto cleanup; - krb5_free_data_contents(context, &ctx->server->realm); - ctx->server->realm.data = hrealms[0]; - ctx->server->realm.length = strlen(hrealms[0]); - free(hrealms); - } else { - /* - * Problem case: Realm tagged for referral but apparently not - * in a <type>/<host> format that - * krb5_get_fallback_host_realm can deal with. - */ - DPRINTF(("gc_from_kdc: referral specified " - "but no fallback realm avaiable!\n")); - code = KRB5_ERR_HOST_REALM_UNKNOWN; - goto cleanup; - } + code = KRB5KRB_AP_ERR_NO_TGT; } - DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite", - ctx->server); - - /* - * Get a TGT for the target realm. - */ - code = tkt_creds_request_initial_tgt(context, ctx, req); - -cleanup: - return code; -} - -static krb5_error_code -tkt_creds_reply_fallback_initial_tgt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = tkt_creds_reply_initial_tgt(context, ctx, rep); - - return code; -} - -static krb5_error_code -tkt_creds_request_fallback_final_tkt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code; - - code = tkt_make_tgs_request(context, ctx, ctx->tgtptr, - tkt_creds_kdcopt(ctx), - &ctx->in_cred, req); - - return code; -} - -static krb5_error_code -tkt_creds_reply_fallback_final_tkt(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *rep) -{ - krb5_error_code code; - - code = tkt_process_tgs_reply(context, ctx, rep, ctx->tgtptr, - tkt_creds_kdcopt(ctx), &ctx->in_cred, - &ctx->out_cred); - - return code; -} - -static krb5_error_code -tkt_creds_request_complete(krb5_context context, - krb5_tkt_creds_context ctx, - krb5_data *req) -{ - krb5_error_code code = 0; - - DUMP_PRINC("gc_from_kdc: final server after reversion", ctx->server); - - assert(ctx->out_cred); - - /* - * Deal with ccache TGT management: If tgts has been set from - * initial non-referral TGT discovery, leave it alone. Otherwise, if - * referral_tgts[0] exists return it as the only entry in tgts. - * (Further referrals are never cached, only the referral from the - * local KDC.) This is part of cleanup because useful received TGTs - * should be cached even if the main request resulted in failure. - */ - if (ctx->tgts == NULL && ctx->referral_tgts[0] != NULL) { - /* Allocate returnable TGT list. */ - ctx->tgts = calloc(2, sizeof (krb5_creds *)); - if (ctx->tgts == NULL) { - - code = ENOMEM; - goto cleanup; - } - code = krb5_copy_creds(context, ctx->referral_tgts[0], &ctx->tgts[0]); - if (code != 0) - goto cleanup; - } - - krb5_free_principal(context, ctx->out_cred->server); - ctx->out_cred->server = ctx->supplied_server; - ctx->supplied_server = NULL; - - assert(ctx->out_cred->authdata == NULL); - code = krb5_copy_authdata(context, ctx->in_cred.authdata, - &ctx->out_cred->authdata); - if (code != 0) - goto cleanup; - cleanup: return code; } @@ -1730,39 +544,10 @@ tkt_creds_step_request(krb5_context context, krb5_data *req) { krb5_error_code code; - enum krb5_tkt_creds_state state; context->use_conf_ktypes = 1; - do { - krb5_free_cred_contents(context, &ctx->tgtq); - - krb5_free_keyblock(context, ctx->subkey); - ctx->subkey = NULL; - - switch ((state = ctx->state)) { - case TKT_CREDS_INITIAL_TGT: - code = tkt_creds_request_initial_tgt(context, ctx, req); - break; - case TKT_CREDS_REFERRAL_TGT: - case TKT_CREDS_REFERRAL_TGT_NOCANON: - code = tkt_creds_request_referral_tgt(context, ctx, req); - break; - case TKT_CREDS_FALLBACK_INITIAL_TGT: - code = tkt_creds_request_fallback_initial_tgt(context, ctx, req); - break; - case TKT_CREDS_FALLBACK_FINAL_TKT: - code = tkt_creds_request_fallback_final_tkt(context, ctx, req); - break; - case TKT_CREDS_COMPLETE: - code = tkt_creds_request_complete(context, ctx, req); - break; - default: - assert(0 && "tkt_creds_step_request invalid state"); - break; - } - /* If there has been a state transition, then try again. */ - } while (code == 0 && ctx->state != state); + code = tkt_creds_request_referral_tgt(context, ctx, req); context->use_conf_ktypes = ctx->use_conf_ktypes; @@ -1778,32 +563,10 @@ tkt_creds_reply(krb5_context context, context->use_conf_ktypes = 1; - { - krb5_free_creds(context, ctx->out_cred); - ctx->out_cred = NULL; + krb5_free_creds(context, ctx->out_cred); + ctx->out_cred = NULL; - assert(ctx->subkey); - - switch (ctx->state) { - case TKT_CREDS_INITIAL_TGT: - code = tkt_creds_reply_initial_tgt(context, ctx, rep); - break; - case TKT_CREDS_REFERRAL_TGT: - case TKT_CREDS_REFERRAL_TGT_NOCANON: - code = tkt_creds_reply_referral_tgt(context, ctx, rep); - break; - case TKT_CREDS_FALLBACK_INITIAL_TGT: - code = tkt_creds_reply_fallback_initial_tgt(context, ctx, rep); - break; - case TKT_CREDS_FALLBACK_FINAL_TKT: - code = tkt_creds_reply_fallback_final_tkt(context, ctx, rep); - break; - case TKT_CREDS_COMPLETE: - default: - assert(0 && "tkt_creds_reply invalid state"); - break; - } - } + code = tkt_creds_reply_referral_tgt(context, ctx, rep); context->use_conf_ktypes = ctx->use_conf_ktypes; @@ -1842,15 +605,15 @@ krb5_tkt_creds_step(krb5_context context, goto cleanup; } - code = tkt_creds_step_request(context, ctx, out); - if (code != 0) - goto cleanup; - - if (ctx->state == TKT_CREDS_COMPLETE) { + if (ctx->complete) { *flags = 1; goto cleanup; } + code = tkt_creds_step_request(context, ctx, out); + if (code != 0) + goto cleanup; + assert(out->length != 0); code = krb5int_copy_data_contents(context, @@ -1872,95 +635,3 @@ cleanup: return code; } -/* - * 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_cred all may point to - * the same credential at different times. - * - * Returns errors, system errors. - */ - -krb5_error_code -krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts, int kdcopt) -{ - krb5_error_code code; - krb5_tkt_creds_context ctx = NULL; - - code = krb5_tkt_creds_init(context, ccache, in_cred, kdcopt, &ctx); - if (code != 0) - goto cleanup; - - code = krb5_tkt_creds_get(context, ctx); - if (code != 0) - goto cleanup; - - *out_cred = ctx->out_cred; - ctx->out_cred = NULL; - -cleanup: - if (ctx != NULL) { - *tgts = ctx->tgts; - ctx->tgts = NULL; - } - - krb5_tkt_creds_free(context, ctx); - - return code; -} - -krb5_error_code -krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts) -{ - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - 0); -} - -krb5_error_code -krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts) -{ - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - KDC_OPT_VALIDATE); -} - -krb5_error_code -krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache, - krb5_creds *in_cred, krb5_creds **out_cred, - krb5_creds ***tgts) -{ - return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts, - KDC_OPT_RENEW); -} diff --git a/src/lib/krb5/libkrb5.exports b/src/lib/krb5/libkrb5.exports index 3c11ea1..56bb918 100644 --- a/src/lib/krb5/libkrb5.exports +++ b/src/lib/krb5/libkrb5.exports @@ -544,7 +544,6 @@ krb5_string_to_timestamp krb5_sync_disk_file krb5_tgtname krb5_tkt_creds_free -krb5_tkt_creds_get krb5_tkt_creds_get_creds krb5_tkt_creds_init krb5_tkt_creds_step |