2 * Copyright (c) 1994,2003,2005,2007 by the Massachusetts Institute of Technology.
3 * Copyright (c) 1994 CyberSAFE Corporation
4 * Copyright (c) 1993 Open Computing Security Group
5 * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology.
8 * Export of this software from the United States of America may
9 * require a specific license from the United States Government.
10 * It is the responsibility of any person or organization contemplating
11 * export to obtain such a license before exporting.
13 * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
14 * distribute this software and its documentation for any purpose and
15 * without fee is hereby granted, provided that the above copyright
16 * notice appear in all copies and that both that copyright notice and
17 * this permission notice appear in supporting documentation, and that
18 * the name of M.I.T. not be used in advertising or publicity pertaining
19 * to distribution of the software without specific, written prior
20 * permission. Furthermore if you modify this software you must label
21 * your software as modified software and not distribute it in such a
22 * fashion that it might be confused with the original M.I.T. software.
23 * Neither M.I.T., the Open Computing Security Group, nor
24 * CyberSAFE Corporation make any representations about the suitability of
25 * this software for any purpose. It is provided "as is" without express
26 * or implied warranty.
28 * krb5_get_cred_from_kdc() and related functions:
30 * Get credentials from some KDC somewhere, possibly accumulating TGTs
36 #include "int-proto.h"
41 * Ring buffer abstraction for TGTs returned from a ccache; avoids
42 * lots of excess copying.
47 krb5_creds cred[NCC_TGTS];
49 unsigned int cur, nxt;
52 /* NOTE: This only checks if NXT_TGT is CUR_CC_TGT. */
53 #define NXT_TGT_IS_CACHED(ts) \
54 ((ts)->nxt_tgt == (ts)->cur_cc_tgt)
56 #define MARK_CUR_CC_TGT_CLEAN(ts) \
58 (ts)->cc_tgts.dirty[(ts)->cc_tgts.cur] = 0; \
61 static void init_cc_tgts(struct tr_state *);
62 static void shift_cc_tgts(struct tr_state *);
63 static void clean_cc_tgts(struct tr_state *);
66 * State struct for do_traversal() and helpers.
68 * CUR_TGT and NXT_TGT can each point either into CC_TGTS or into
71 * CUR_TGT is the "working" TGT, which will be used to obtain new
72 * TGTs. NXT_TGT will be CUR_TGT for the next iteration of the loop.
74 * Part of the baroqueness of this setup is to deal with annoying
75 * differences between krb5_cc_retrieve_cred() and
76 * krb5_get_cred_via_tkt(); krb5_cc_retrieve_cred() fills in a
77 * caller-allocated krb5_creds, while krb5_get_cred_via_tkt()
78 * allocates a krb5_creds for return.
83 krb5_principal *kdc_list;
85 krb5_principal *cur_kdc;
86 krb5_principal *nxt_kdc;
87 krb5_principal *lst_kdc;
90 krb5_creds **kdc_tgts;
91 struct cc_tgts cc_tgts;
92 krb5_creds *cur_cc_tgt;
93 krb5_creds *nxt_cc_tgt;
95 krb5_creds *offpath_tgt;
101 #ifdef DEBUG_GC_FRM_KDC
103 #define TR_DBG(ts, prog) tr_dbg(ts, prog)
104 #define TR_DBG_RET(ts, prog, ret) tr_dbg_ret(ts, prog, ret)
105 #define TR_DBG_RTREE(ts, prog, princ) tr_dbg_rtree(ts, prog, princ)
107 static void tr_dbg(struct tr_state *, const char *);
108 static void tr_dbg_ret(struct tr_state *, const char *, krb5_error_code);
109 static void tr_dbg_rtree(struct tr_state *, const char *, krb5_principal);
113 #define TR_DBG(ts, prog)
114 #define TR_DBG_RET(ts, prog, ret)
115 #define TR_DBG_RTREE(ts, prog, princ)
117 #endif /* !DEBUG_GC_FRM_KDC */
119 #ifdef DEBUG_REFERRALS
121 #define DPRINTF(x) printf x
122 #define DFPRINTF(x) fprintf x
123 #define DUMP_PRINC(x, y) krb5int_dbgref_dump_principal((x), (y))
129 #define DUMP_PRINC(x, y)
133 /* Convert ticket flags to necessary KDC options */
134 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
137 * Certain krb5_cc_retrieve_cred() errors are soft errors when looking
138 * for a cross-realm TGT.
140 #define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND && \
141 (r) != KRB5_CC_NOT_KTYPE)
144 * Flags for ccache lookups of cross-realm TGTs.
146 * A cross-realm TGT may be issued by some other intermediate realm's
147 * KDC, so we use KRB5_TC_MATCH_SRV_NAMEONLY.
149 #define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | KRB5_TC_SUPPORTED_KTYPES)
152 * Prototypes of helper functions
154 static krb5_error_code tgt_mcred(krb5_context, krb5_principal,
155 krb5_principal, krb5_principal, krb5_creds *);
156 static krb5_error_code retr_local_tgt(struct tr_state *, krb5_principal);
157 static krb5_error_code try_ccache(struct tr_state *, krb5_creds *);
158 static krb5_error_code find_nxt_kdc(struct tr_state *);
159 static krb5_error_code try_kdc(struct tr_state *, krb5_creds *);
160 static krb5_error_code kdc_mcred(struct tr_state *, krb5_principal,
162 static krb5_error_code next_closest_tgt(struct tr_state *, krb5_principal);
163 static krb5_error_code init_rtree(struct tr_state *,
164 krb5_principal, krb5_principal);
165 static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache,
166 krb5_principal client, krb5_principal server,
167 krb5_creds *out_cc_tgt, krb5_creds **out_tgt,
168 krb5_creds ***out_kdc_tgts, int *tgtptr_isoffpath);
169 static krb5_error_code chase_offpath(struct tr_state *, krb5_principal,
171 static krb5_error_code offpath_loopchk(struct tr_state *ts,
172 krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount);
177 * Initialize indices for cached-TGT ring buffer. Caller must zero
178 * CC_TGTS, CC_TGT_DIRTY arrays prior to calling.
181 init_cc_tgts(struct tr_state *ts)
186 ts->cur_cc_tgt = &ts->cc_tgts.cred[0];
187 ts->nxt_cc_tgt = &ts->cc_tgts.cred[1];
193 * Given a fresh assignment to NXT_CC_TGT, mark NXT_CC_TGT as dirty,
194 * and shift indices so old NXT_CC_TGT becomes new CUR_CC_TGT. Clean
195 * the new NXT_CC_TGT.
198 shift_cc_tgts(struct tr_state *ts)
204 i = rb->cur = rb->nxt;
206 ts->cur_cc_tgt = ts->nxt_cc_tgt;
208 i = (i + 1) % NCC_TGTS;
211 ts->nxt_cc_tgt = &rb->cred[i];
213 krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
221 * Free CC_TGTS which were dirty, then mark them clean.
224 clean_cc_tgts(struct tr_state *ts)
230 for (i = 0; i < NCC_TGTS; i++) {
232 krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
241 #ifdef DEBUG_GC_FRM_KDC
243 tr_dbg(struct tr_state *ts, const char *prog)
245 krb5_error_code retval;
246 char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str;
248 cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL;
249 retval = krb5_unparse_name(ts->ctx, ts->cur_tgt->server, &cur_tgt_str);
250 if (retval) goto cleanup;
251 retval = krb5_unparse_name(ts->ctx, *ts->cur_kdc, &cur_kdc_str);
252 if (retval) goto cleanup;
253 retval = krb5_unparse_name(ts->ctx, *ts->nxt_kdc, &nxt_kdc_str);
254 if (retval) goto cleanup;
255 fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str);
256 fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str);
257 fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str);
260 krb5_free_unparsed_name(ts->ctx, cur_tgt_str);
262 krb5_free_unparsed_name(ts->ctx, cur_kdc_str);
264 krb5_free_unparsed_name(ts->ctx, nxt_kdc_str);
268 tr_dbg_ret(struct tr_state *ts, const char *prog, krb5_error_code ret)
270 fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret,
275 tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ)
279 if (krb5_unparse_name(ts->ctx, princ, &str))
281 fprintf(stderr, "%s: %s\n", prog, str);
282 krb5_free_unparsed_name(ts->ctx, str);
284 #endif /* DEBUG_GC_FRM_KDC */
289 * Return MCREDS for use as a match criterion.
291 * Resulting credential has CLIENT as the client principal, and
292 * krbtgt/realm_of(DST)@realm_of(SRC) as the server principal. Zeroes
293 * MCREDS first, does not allocate MCREDS, and cleans MCREDS on
294 * failure. The peculiar ordering of DST and SRC args is for
295 * consistency with krb5_tgtname().
297 static krb5_error_code
298 tgt_mcred(krb5_context ctx, krb5_principal client,
299 krb5_principal dst, krb5_principal src,
302 krb5_error_code retval;
305 memset(mcreds, 0, sizeof(*mcreds));
307 retval = krb5_copy_principal(ctx, client, &mcreds->client);
311 retval = krb5_tgtname(ctx, krb5_princ_realm(ctx, dst),
312 krb5_princ_realm(ctx, src), &mcreds->server);
318 krb5_free_cred_contents(ctx, mcreds);
326 * Populate KDC_LIST with the output of krb5_walk_realm_tree().
328 static krb5_error_code
329 init_rtree(struct tr_state *ts,
330 krb5_principal client, krb5_principal server)
332 krb5_error_code retval;
335 retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client),
336 krb5_princ_realm(ts->ctx, server),
337 &ts->kdc_list, KRB5_REALM_BRANCH_CHAR);
341 for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) {
342 assert(krb5_princ_size(ts->ctx, ts->kdc_list[ts->nkdcs]) == 2);
343 TR_DBG_RTREE(ts, "init_rtree", ts->kdc_list[ts->nkdcs]);
345 assert(ts->nkdcs > 1);
346 ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1;
348 ts->kdc_tgts = calloc(ts->nkdcs + 1, sizeof(krb5_creds));
349 if (ts->kdc_tgts == NULL)
358 * Prime CUR_TGT with the cached TGT of the client's local realm.
360 static krb5_error_code
361 retr_local_tgt(struct tr_state *ts, krb5_principal client)
363 krb5_error_code retval;
366 memset(&tgtq, 0, sizeof(tgtq));
367 retval = tgt_mcred(ts->ctx, client, client, client, &tgtq);
371 /* Match realm, unlike other ccache retrievals here. */
372 retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache,
373 KRB5_TC_SUPPORTED_KTYPES,
374 &tgtq, ts->nxt_cc_tgt);
375 krb5_free_cred_contents(ts->ctx, &tgtq);
378 ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt;
386 * Attempt to retrieve desired NXT_TGT from ccache. Point NXT_TGT to
389 static krb5_error_code
390 try_ccache(struct tr_state *ts, krb5_creds *tgtq)
392 krb5_error_code retval;
394 TR_DBG(ts, "try_ccache");
395 retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache, RETR_FLAGS,
396 tgtq, ts->nxt_cc_tgt);
399 ts->nxt_tgt = ts->cur_cc_tgt;
401 TR_DBG_RET(ts, "try_ccache", retval);
408 * A NXT_TGT gotten from an intermediate KDC might actually be a
409 * referral. Search KDC_LIST forward starting from CUR_KDC, looking
410 * for the KDC with the same remote realm as NXT_TGT. If we don't
411 * find it, the intermediate KDC is leading us off the transit path.
413 * Match on CUR_KDC's remote realm, not local realm, because, among
414 * other reasons, we can get a referral to the final realm; e.g.,
417 * KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2,
418 * krbtgt/R4@R3, NULL }
419 * CUR_TGT->SERVER == krbtgt/R2@R1
420 * NXT_TGT->SERVER == krbtgt/R4@R2
422 * i.e., we got a ticket issued by R2 with remote realm R4, we want to
423 * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT
424 * with R3 as its local realm.
426 * Set up for next iteration of do_traversal() loop by pointing
427 * NXT_KDC to one entry forward of the match.
429 static krb5_error_code
430 find_nxt_kdc(struct tr_state *ts)
433 krb5_principal *kdcptr;
435 TR_DBG(ts, "find_nxt_kdc");
436 assert(ts->ntgts > 0);
437 assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]);
438 if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2)
439 return KRB5_KDCREP_MODIFIED;
441 r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1);
443 for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) {
445 r2 = krb5_princ_component(ts->ctx, *kdcptr, 1);
447 if (r1 != NULL && r2 != NULL && data_eq(*r1, *r2)) {
451 if (*kdcptr != NULL) {
452 ts->nxt_kdc = kdcptr;
453 TR_DBG_RET(ts, "find_nxt_kdc", 0);
457 r2 = krb5_princ_component(ts->ctx, ts->kdc_list[0], 1);
458 if (r1 != NULL && r2 != NULL &&
459 r1->length == r2->length &&
460 !memcmp(r1->data, r2->data, r1->length)) {
461 TR_DBG_RET(ts, "find_nxt_kdc: looped back to local",
462 KRB5_KDCREP_MODIFIED);
463 return KRB5_KDCREP_MODIFIED;
467 * Realm is not in our list; we probably got an unexpected realm
470 ts->offpath_tgt = ts->nxt_tgt;
471 if (ts->cur_kdc == ts->kdc_list) {
473 * Local KDC referred us off path; trust it for caching
479 * Unlink the off-path TGT from KDC_TGTS but don't free it,
480 * because we should return it.
482 ts->kdc_tgts[--ts->ntgts] = NULL;
483 ts->nxt_tgt = ts->cur_tgt;
484 TR_DBG_RET(ts, "find_nxt_kdc", 0);
491 * Using CUR_TGT, attempt to get desired NXT_TGT. Update NXT_KDC if
494 static krb5_error_code
495 try_kdc(struct tr_state *ts, krb5_creds *tgtq)
497 krb5_error_code retval;
500 TR_DBG(ts, "try_kdc");
501 /* This check should probably be in gc_via_tkt. */
502 if (!krb5_c_valid_enctype(ts->cur_tgt->keyblock.enctype))
503 return KRB5_PROG_ETYPE_NOSUPP;
506 ltgtq.is_skey = FALSE;
507 ltgtq.ticket_flags = ts->cur_tgt->ticket_flags;
508 retval = krb5_get_cred_via_tkt(ts->ctx, ts->cur_tgt,
509 FLAGS2OPTS(ltgtq.ticket_flags),
510 ts->cur_tgt->addresses,
511 <gtq, &ts->kdc_tgts[ts->ntgts++]);
514 ts->nxt_tgt = ts->cur_tgt;
515 TR_DBG_RET(ts, "try_kdc", retval);
518 ts->nxt_tgt = ts->kdc_tgts[ts->ntgts-1];
519 retval = find_nxt_kdc(ts);
520 TR_DBG_RET(ts, "try_kdc", retval);
527 * Return MCREDS for use as a match criterion.
529 * Resulting credential has CLIENT as the client principal, and
530 * krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server
531 * principal. Zeroes MCREDS first, does not allocate MCREDS, and
532 * cleans MCREDS on failure.
534 static krb5_error_code
535 kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds)
537 krb5_error_code retval;
538 krb5_data *rdst, *rsrc;
541 memset(mcreds, 0, sizeof(*mcreds));
543 rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1);
544 rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1);
545 retval = krb5_copy_principal(ts->ctx, client, &mcreds->client);
549 retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcreds->server);
555 krb5_free_cred_contents(ts->ctx, mcreds);
563 * Using CUR_TGT, attempt to get the cross-realm TGT having its remote
564 * realm closest to the target principal's. Update NXT_TGT, NXT_KDC
567 static krb5_error_code
568 next_closest_tgt(struct tr_state *ts, krb5_principal client)
570 krb5_error_code retval;
574 memset(&tgtq, 0, sizeof(tgtq));
576 for (ts->nxt_kdc = ts->lst_kdc;
577 ts->nxt_kdc > ts->cur_kdc;
580 krb5_free_cred_contents(ts->ctx, &tgtq);
581 retval = kdc_mcred(ts, client, &tgtq);
584 /* Don't waste time retrying ccache for direct path. */
585 if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) {
586 retval = try_ccache(ts, &tgtq);
589 if (HARD_CC_ERR(retval))
592 /* Not in the ccache, so talk to a KDC. */
593 retval = try_kdc(ts, &tgtq);
598 * In case of errors in try_kdc() or find_nxt_kdc(), continue
599 * looping through the KDC list.
603 * If we have a non-zero retval, we either have a hard error or we
604 * failed to find a closer TGT.
607 krb5_free_cred_contents(ts->ctx, &tgtq);
614 * Find final TGT needed to get CLIENT a ticket for SERVER. Point
615 * OUT_TGT at the desired TGT, which may be an existing cached TGT
616 * (copied into OUT_CC_TGT) or one of the newly obtained TGTs
617 * (collected in OUT_KDC_TGTS).
619 * Get comfortable; this is somewhat complicated.
621 * Nomenclature: Cross-realm TGS principal names have the form:
623 * krbtgt/REMOTE@LOCAL
625 * krb5_walk_realm_tree() returns a list like:
627 * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, ...
629 * These are prinicpal names, not realm names. We only really use the
630 * remote parts of the TGT principal names.
632 * The do_traversal loop calls next_closest_tgt() to find the next
633 * closest TGT to the destination realm. next_closest_tgt() updates
634 * NXT_KDC for the following iteration of the do_traversal() loop.
636 * At the beginning of any given iteration of the do_traversal() loop,
637 * CUR_KDC's remote realm is the remote realm of CUR_TGT->SERVER. The
638 * local realms of CUR_KDC and CUR_TGT->SERVER may not match due to
639 * short-circuit paths provided by intermediate KDCs, e.g., CUR_KDC
640 * might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B.
642 * For example, given KDC_LIST of
644 * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3,
647 * The next_closest_tgt() loop moves NXT_KDC to the left starting from
648 * R5, stopping before it reaches CUR_KDC. When next_closest_tgt()
649 * returns, the do_traversal() loop updates CUR_KDC to be NXT_KDC, and
650 * calls next_closest_tgt() again.
652 * next_closest_tgt() at start of its loop:
657 * +----+----+----+----+----+
658 * | R1 | R2 | R3 | R4 | R5 |
659 * +----+----+----+----+----+
661 * next_closest_tgt() returns after finding a ticket for krbtgt/R3@R1:
666 * +----+----+----+----+----+
667 * | R1 | R2 | R3 | R4 | R5 |
668 * +----+----+----+----+----+
670 * do_traversal() updates CUR_KDC:
676 * +----+----+----+----+----+
677 * | R1 | R2 | R3 | R4 | R5 |
678 * +----+----+----+----+----+
680 * next_closest_tgt() at start of its loop:
685 * +----+----+----+----+----+
686 * | R1 | R2 | R3 | R4 | R5 |
687 * +----+----+----+----+----+
691 * The algorithm executes in n*(n-1)/2 (the sum of integers from 1 to
692 * n-1) attempts in the worst case, i.e., each KDC only has a
693 * cross-realm ticket for the immediately following KDC in the transit
694 * path. Typically, short-circuit paths will cause execution occur
695 * faster than this worst-case scenario.
697 * When next_closest_tgt() updates NXT_KDC, it may not perform a
698 * simple increment from CUR_KDC, in part because some KDC may
699 * short-circuit pieces of the transit path.
701 static krb5_error_code
702 do_traversal(krb5_context ctx,
704 krb5_principal client,
705 krb5_principal server,
706 krb5_creds *out_cc_tgt,
707 krb5_creds **out_tgt,
708 krb5_creds ***out_kdc_tgts,
709 int *tgtptr_isoffpath)
711 krb5_error_code retval;
712 struct tr_state state, *ts;
715 *out_kdc_tgts = NULL;
717 memset(ts, 0, sizeof(*ts));
722 retval = init_rtree(ts, client, server);
726 retval = retr_local_tgt(ts, client);
730 for (ts->cur_kdc = ts->kdc_list, ts->nxt_kdc = NULL;
731 ts->cur_kdc != NULL && ts->cur_kdc < ts->lst_kdc;
732 ts->cur_kdc = ts->nxt_kdc, ts->cur_tgt = ts->nxt_tgt) {
734 retval = next_closest_tgt(ts, client);
738 if (ts->offpath_tgt != NULL) {
739 retval = chase_offpath(ts, client, server);
744 assert(ts->cur_kdc != ts->nxt_kdc);
747 if (NXT_TGT_IS_CACHED(ts)) {
748 assert(ts->offpath_tgt == NULL);
749 *out_cc_tgt = *ts->cur_cc_tgt;
750 *out_tgt = out_cc_tgt;
751 MARK_CUR_CC_TGT_CLEAN(ts);
752 } else if (ts->offpath_tgt != NULL){
753 *out_tgt = ts->offpath_tgt;
755 /* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */
756 *out_tgt = ts->nxt_tgt;
761 if (ts->kdc_list != NULL)
762 krb5_free_realm_tree(ctx, ts->kdc_list);
763 if (ts->ntgts == 0) {
764 *out_kdc_tgts = NULL;
765 if (ts->kdc_tgts != NULL)
768 *out_kdc_tgts = ts->kdc_tgts;
769 *tgtptr_isoffpath = (ts->offpath_tgt != NULL);
776 * Chase off-path TGT referrals.
778 * If we are traversing a trusted path (either hierarchically derived
779 * or explicit capath) and get a TGT pointing to a realm off this
780 * path, query the realm referenced by that off-path TGT. Repeat
781 * until we get to the destination realm or encounter an error.
783 * CUR_TGT is always either pointing into REFTGTS or is an alias for
786 static krb5_error_code
787 chase_offpath(struct tr_state *ts,
788 krb5_principal client, krb5_principal server)
790 krb5_error_code retval;
792 krb5_creds *cur_tgt, *nxt_tgt, *reftgts[KRB5_REFERRAL_MAXHOPS];
793 krb5_data *rsrc, *rdst, *r1;
794 unsigned int rcount, i;
796 rdst = krb5_princ_realm(ts->ctx, server);
797 cur_tgt = ts->offpath_tgt;
799 for (rcount = 0; rcount < KRB5_REFERRAL_MAXHOPS; rcount++) {
801 memset(&mcred, 0, sizeof(mcred));
802 rsrc = krb5_princ_component(ts->ctx, cur_tgt->server, 1);
803 retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcred.server);
806 mcred.client = client;
807 retval = krb5_get_cred_via_tkt(ts->ctx, cur_tgt,
808 FLAGS2OPTS(cur_tgt->ticket_flags),
809 cur_tgt->addresses, &mcred, &nxt_tgt);
811 krb5_free_principal(ts->ctx, mcred.server);
815 if (!IS_TGS_PRINC(ts->ctx, nxt_tgt->server)) {
816 retval = KRB5_KDCREP_MODIFIED;
819 r1 = krb5_princ_component(ts->ctx, nxt_tgt->server, 1);
820 if (rdst->length == r1->length &&
821 !memcmp(rdst->data, r1->data, rdst->length)) {
825 retval = offpath_loopchk(ts, nxt_tgt, reftgts, rcount);
828 reftgts[rcount] = nxt_tgt;
832 /* Max hop count exceeded. */
833 retval = KRB5_KDCREP_MODIFIED;
836 if (mcred.server != NULL) {
837 krb5_free_principal(ts->ctx, mcred.server);
840 * Don't free TS->OFFPATH_TGT if it's in the list of cacheable
841 * TGTs to be returned by do_traversal().
843 if (ts->offpath_tgt != ts->nxt_tgt) {
844 krb5_free_creds(ts->ctx, ts->offpath_tgt);
846 ts->offpath_tgt = NULL;
847 if (nxt_tgt != NULL) {
849 krb5_free_creds(ts->ctx, nxt_tgt);
851 ts->offpath_tgt = nxt_tgt;
853 for (i = 0; i < rcount; i++) {
854 krb5_free_creds(ts->ctx, reftgts[i]);
862 * Check for loop back to previously-visited realms, both off-path and
865 static krb5_error_code
866 offpath_loopchk(struct tr_state *ts,
867 krb5_creds *tgt, krb5_creds *reftgts[], unsigned int rcount)
872 r1 = krb5_princ_component(ts->ctx, tgt->server, 1);
873 for (i = 0; i < rcount; i++) {
874 r2 = krb5_princ_component(ts->ctx, reftgts[i]->server, 1);
875 if (r1->length == r2->length &&
876 !memcmp(r1->data, r2->data, r1->length))
877 return KRB5_KDCREP_MODIFIED;
879 for (i = 0; i < ts->ntgts; i++) {
880 r2 = krb5_princ_component(ts->ctx, ts->kdc_tgts[i]->server, 1);
881 if (r1->length == r2->length &&
882 !memcmp(r1->data, r2->data, r1->length))
883 return KRB5_KDCREP_MODIFIED;
889 * krb5_get_cred_from_kdc_opt()
890 * krb5_get_cred_from_kdc()
891 * krb5_get_cred_from_kdc_validate()
892 * krb5_get_cred_from_kdc_renew()
894 * Retrieve credentials for client IN_CRED->CLIENT, server
895 * IN_CRED->SERVER, ticket flags IN_CRED->TICKET_FLAGS, possibly
896 * second_ticket if needed.
898 * Request credentials from the KDC for the server's realm. Point
899 * TGTS to an allocated array of pointers to krb5_creds, containing
900 * any intermediate credentials obtained in the process of contacting
901 * the server's KDC; if no intermediate credentials were obtained,
902 * TGTS is a null pointer. Return intermediate credentials if
903 * intermediate KDCs provided credentials, even if no useful end
906 * Caller must free TGTS, regardless of whether this function returns
909 * This function does NOT cache the intermediate TGTs.
911 * Do not call this routine if desired credentials are already cached.
913 * On success, OUT_CRED contains the desired credentials; the caller
916 * Beware memory management issues if you have modifications in mind.
917 * With the addition of referral support, it is now the case that *tgts,
918 * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to
919 * the same credential at different times.
921 * Returns errors, system errors.
925 krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
926 krb5_creds *in_cred, krb5_creds **out_cred,
927 krb5_creds ***tgts, int kdcopt)
929 krb5_error_code retval, subretval;
930 krb5_principal client, server, supplied_server, out_supplied_server;
931 krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS];
932 krb5_creds *otgtptr = NULL;
933 int tgtptr_isoffpath = 0;
934 krb5_boolean old_use_conf_ktypes;
936 unsigned int referral_count, i;
939 * Set up client and server pointers. Make a fresh and modifyable
940 * copy of the in_cred server and save the supplied version.
942 client = in_cred->client;
943 if ((retval=krb5_copy_principal(context, in_cred->server, &server)))
945 /* We need a second copy for the output creds. */
946 if ((retval = krb5_copy_principal(context, server,
947 &out_supplied_server)) != 0 ) {
948 krb5_free_principal(context, server);
951 supplied_server = in_cred->server;
952 in_cred->server=server;
954 DUMP_PRINC("gc_from_kdc initial client", client);
955 DUMP_PRINC("gc_from_kdc initial server", server);
956 memset(&cc_tgt, 0, sizeof(cc_tgt));
957 memset(&tgtq, 0, sizeof(tgtq));
958 memset(&referral_tgts, 0, sizeof(referral_tgts));
963 old_use_conf_ktypes = context->use_conf_ktypes;
965 /* Copy client realm to server if no hint. */
966 if (krb5_is_referral_realm(&server->realm)) {
967 /* Use the client realm. */
968 DPRINTF(("gc_from_kdc: no server realm supplied, "
969 "using client realm.\n"));
970 krb5_free_data_contents(context, &server->realm);
971 server->realm.data = malloc(client->realm.length + 1);
972 if (server->realm.data == NULL) {
976 memcpy(server->realm.data, client->realm.data, client->realm.length);
977 server->realm.length = client->realm.length;
978 server->realm.data[server->realm.length] = 0;
981 * Retreive initial TGT to match the specified server, either for the
982 * local realm in the default (referral) case or for the remote
983 * realm if we're starting someplace non-local.
985 retval = tgt_mcred(context, client, server, client, &tgtq);
989 /* Fast path: Is it in the ccache? */
990 context->use_conf_ktypes = 1;
991 retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
995 } else if (!HARD_CC_ERR(retval)) {
996 DPRINTF(("gc_from_kdc: starting do_traversal to find "
997 "initial TGT for referral\n"));
998 tgtptr_isoffpath = 0;
1000 retval = do_traversal(context, ccache, client, server,
1001 &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
1004 DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n"));
1008 DUMP_PRINC("gc_from_kdc: server as requested", supplied_server);
1011 * Try requesting a service ticket from our local KDC with referrals
1012 * turned on. If the first referral succeeds, follow a referral-only
1013 * path, otherwise fall back to old-style assumptions.
1017 * Save TGTPTR because we rewrite it in the referral loop, and
1018 * we might need to explicitly free it later.
1021 for (referral_count = 0;
1022 referral_count < KRB5_REFERRAL_MAXHOPS;
1025 DUMP_PRINC("gc_from_kdc: referral loop: tgt in use", tgtptr->server);
1026 DUMP_PRINC("gc_from_kdc: referral loop: request is for", server);
1028 retval = krb5_get_cred_via_tkt(context, tgtptr,
1029 KDC_OPT_CANONICALIZE |
1030 FLAGS2OPTS(tgtptr->ticket_flags) |
1032 (in_cred->second_ticket.length ?
1033 KDC_OPT_ENC_TKT_IN_SKEY : 0),
1034 tgtptr->addresses, in_cred, out_cred);
1036 DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",
1037 error_message(retval)));
1038 /* If we haven't gone anywhere yet, fail through to the
1039 non-referral case. */
1040 if (referral_count==0) {
1041 DPRINTF(("gc_from_kdc: initial referral failed; "
1042 "punting to fallback.\n"));
1045 /* Otherwise, try the same query without canonicalization
1046 set, and fail hard if that doesn't work. */
1047 DPRINTF(("gc_from_kdc: referral #%d failed; "
1048 "retrying without option.\n", referral_count + 1));
1049 retval = krb5_get_cred_via_tkt(context, tgtptr,
1050 FLAGS2OPTS(tgtptr->ticket_flags) |
1052 (in_cred->second_ticket.length ?
1053 KDC_OPT_ENC_TKT_IN_SKEY : 0),
1056 /* Whether or not that succeeded, we're done. */
1059 /* Referral request succeeded; let's see what it is. */
1060 if (krb5_principal_compare(context, in_cred->server,
1061 (*out_cred)->server)) {
1062 DPRINTF(("gc_from_kdc: request generated ticket "
1063 "for requested server principal\n"));
1064 DUMP_PRINC("gc_from_kdc final referred reply",
1068 * Check if the return enctype is one that we requested if
1071 if (old_use_conf_ktypes || context->tgs_ktype_count == 0)
1073 for (i = 0; i < context->tgs_ktype_count; i++) {
1074 if ((*out_cred)->keyblock.enctype == context->tgs_ktypes[i]) {
1075 /* Found an allowable etype, so we're done */
1080 * We need to try again, but this time use the
1081 * tgs_ktypes in the context. At this point we should
1082 * have all the tgts to succeed.
1085 /* Free "wrong" credential */
1086 krb5_free_creds(context, *out_cred);
1088 /* Re-establish tgs etypes */
1089 context->use_conf_ktypes = old_use_conf_ktypes;
1090 retval = krb5_get_cred_via_tkt(context, tgtptr,
1091 KDC_OPT_CANONICALIZE |
1092 FLAGS2OPTS(tgtptr->ticket_flags) |
1094 (in_cred->second_ticket.length ?
1095 KDC_OPT_ENC_TKT_IN_SKEY : 0),
1100 else if (IS_TGS_PRINC(context, (*out_cred)->server)) {
1103 DPRINTF(("gc_from_kdc: request generated referral tgt\n"));
1104 DUMP_PRINC("gc_from_kdc credential received",
1105 (*out_cred)->server);
1107 if (referral_count == 0)
1108 r1 = &tgtptr->server->data[1];
1110 r1 = &referral_tgts[referral_count-1]->server->data[1];
1112 r2 = &(*out_cred)->server->data[1];
1113 if (data_eq(*r1, *r2)) {
1114 DPRINTF(("gc_from_kdc: referred back to "
1115 "previous realm; fall back\n"));
1116 krb5_free_creds(context, *out_cred);
1120 /* Check for referral routing loop. */
1121 for (i=0;i<referral_count;i++) {
1123 DUMP_PRINC("gc_from_kdc: loop compare #1",
1124 (*out_cred)->server);
1125 DUMP_PRINC("gc_from_kdc: loop compare #2",
1126 referral_tgts[i]->server);
1128 if (krb5_principal_compare(context,
1129 (*out_cred)->server,
1130 referral_tgts[i]->server)) {
1132 "krb5_get_cred_from_kdc_opt: "
1133 "referral routing loop - "
1134 "got referral back to hop #%d\n", i));
1135 retval=KRB5_KDC_UNREACH;
1139 /* Point current tgt pointer at newly-received TGT. */
1140 if (tgtptr == &cc_tgt)
1141 krb5_free_cred_contents(context, tgtptr);
1143 /* Save pointer to tgt in referral_tgts. */
1144 referral_tgts[referral_count]=*out_cred;
1146 /* Copy krbtgt realm to server principal. */
1147 krb5_free_data_contents(context, &server->realm);
1148 retval = krb5int_copy_data_contents(context,
1149 &tgtptr->server->data[1],
1154 * Future work: rewrite server principal per any
1158 /* Not a TGT; punt to fallback. */
1159 krb5_free_creds(context, *out_cred);
1165 DUMP_PRINC("gc_from_kdc client at fallback", client);
1166 DUMP_PRINC("gc_from_kdc server at fallback", server);
1169 * At this point referrals have been tried and have failed. Go
1170 * back to the server principal as originally issued and try the
1171 * conventional path.
1175 * Referrals have failed. Look up fallback realm if not
1176 * originally provided.
1178 if (krb5_is_referral_realm(&supplied_server->realm)) {
1179 if (server->length >= 2) {
1180 retval=krb5_get_fallback_host_realm(context, &server->data[1],
1182 if (retval) goto cleanup;
1184 DPRINTF(("gc_from_kdc: using fallback realm of %s\n",
1187 krb5_free_data_contents(context,&in_cred->server->realm);
1188 server->realm.data=hrealms[0];
1189 server->realm.length=strlen(hrealms[0]);
1194 * Problem case: Realm tagged for referral but apparently not
1195 * in a <type>/<host> format that
1196 * krb5_get_fallback_host_realm can deal with.
1198 DPRINTF(("gc_from_kdc: referral specified "
1199 "but no fallback realm avaiable!\n"));
1200 retval = KRB5_ERR_HOST_REALM_UNKNOWN;
1205 DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite",
1209 * Get a TGT for the target realm.
1212 krb5_free_cred_contents(context, &tgtq);
1213 retval = tgt_mcred(context, client, server, client, &tgtq);
1217 /* Fast path: Is it in the ccache? */
1218 /* Free tgtptr data if reused from above. */
1219 if (tgtptr == &cc_tgt)
1220 krb5_free_cred_contents(context, tgtptr);
1222 /* Free saved TGT in OTGTPTR if it was off-path. */
1223 if (tgtptr_isoffpath)
1224 krb5_free_creds(context, otgtptr);
1226 /* Free TGTS if previously filled by do_traversal() */
1227 if (*tgts != NULL) {
1228 for (i = 0; (*tgts)[i] != NULL; i++) {
1229 krb5_free_creds(context, (*tgts)[i]);
1234 context->use_conf_ktypes = 1;
1235 retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
1239 } else if (!HARD_CC_ERR(retval)) {
1240 tgtptr_isoffpath = 0;
1241 retval = do_traversal(context, ccache, client, server,
1242 &cc_tgt, &tgtptr, tgts, &tgtptr_isoffpath);
1249 * Finally have TGT for target realm! Try using it to get creds.
1252 if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) {
1253 retval = KRB5_PROG_ETYPE_NOSUPP;
1257 context->use_conf_ktypes = old_use_conf_ktypes;
1258 retval = krb5_get_cred_via_tkt(context, tgtptr,
1259 FLAGS2OPTS(tgtptr->ticket_flags) |
1261 (in_cred->second_ticket.length ?
1262 KDC_OPT_ENC_TKT_IN_SKEY : 0),
1263 tgtptr->addresses, in_cred, out_cred);
1266 krb5_free_cred_contents(context, &tgtq);
1267 if (tgtptr == &cc_tgt)
1268 krb5_free_cred_contents(context, tgtptr);
1269 if (tgtptr_isoffpath)
1270 krb5_free_creds(context, otgtptr);
1271 context->use_conf_ktypes = old_use_conf_ktypes;
1272 /* Drop the original principal back into in_cred so that it's cached
1273 in the expected format. */
1274 DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup",
1276 krb5_free_principal(context, server);
1277 in_cred->server = supplied_server;
1278 if (*out_cred && !retval) {
1279 /* Success: free server, swap supplied server back in. */
1280 krb5_free_principal (context, (*out_cred)->server);
1281 (*out_cred)->server= out_supplied_server;
1285 * Failure: free out_supplied_server. Don't free out_cred here
1286 * since it's either null or a referral TGT that we free below,
1287 * and we may need it to return.
1289 krb5_free_principal (context, out_supplied_server);
1291 DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server);
1293 * Deal with ccache TGT management: If tgts has been set from
1294 * initial non-referral TGT discovery, leave it alone. Otherwise, if
1295 * referral_tgts[0] exists return it as the only entry in tgts.
1296 * (Further referrals are never cached, only the referral from the
1297 * local KDC.) This is part of cleanup because useful received TGTs
1298 * should be cached even if the main request resulted in failure.
1301 if (*tgts == NULL) {
1302 if (referral_tgts[0]) {
1305 * This should possibly be a check on the candidate return
1306 * credential against the cache, in the circumstance where we
1307 * don't want to clutter the cache with near-duplicate
1308 * credentials on subsequent iterations. For now, it is
1314 /* Allocate returnable TGT list. */
1315 *tgts = calloc(2, sizeof (krb5_creds *));
1316 if (*tgts == NULL && retval == 0)
1319 subretval = krb5_copy_creds(context, referral_tgts[0],
1328 DUMP_PRINC("gc_from_kdc: referral TGT for ccache",
1329 (*tgts)[0]->server);
1338 /* Free referral TGTs list. */
1339 for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
1340 if(referral_tgts[i]) {
1341 krb5_free_creds(context, referral_tgts[i]);
1344 DPRINTF(("gc_from_kdc finishing with %s\n",
1345 retval ? error_message(retval) : "no error"));
1350 krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache,
1351 krb5_creds *in_cred, krb5_creds **out_cred,
1354 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
1359 krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache,
1360 krb5_creds *in_cred, krb5_creds **out_cred,
1363 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
1368 krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache,
1369 krb5_creds *in_cred, krb5_creds **out_cred,
1372 return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,