xref: /illumos-gate/usr/src/lib/gss_mechs/mech_krb5/krb5/krb/gc_frm_kdc.c (revision 581cede61ac9c14d8d4ea452562a567189eead78)
1 /*
2  * Copyright 2008 Sun Microsystems, Inc.  All rights reserved.
3  * Use is subject to license terms.
4  */
5 
6 
7 /*
8  * Copyright (c) 1994,2003,2005 by the Massachusetts Institute of Technology.
9  * Copyright (c) 1994 CyberSAFE Corporation
10  * Copyright (c) 1993 Open Computing Security Group
11  * Copyright (c) 1990,1991 by the Massachusetts Institute of Technology.
12  * All Rights Reserved.
13  *
14  * Export of this software from the United States of America may
15  *   require a specific license from the United States Government.
16  *   It is the responsibility of any person or organization contemplating
17  *   export to obtain such a license before exporting.
18  *
19  * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
20  * distribute this software and its documentation for any purpose and
21  * without fee is hereby granted, provided that the above copyright
22  * notice appear in all copies and that both that copyright notice and
23  * this permission notice appear in supporting documentation, and that
24  * the name of M.I.T. not be used in advertising or publicity pertaining
25  * to distribution of the software without specific, written prior
26  * permission.  Furthermore if you modify this software you must label
27  * your software as modified software and not distribute it in such a
28  * fashion that it might be confused with the original M.I.T. software.
29  * Neither M.I.T., the Open Computing Security Group, nor
30  * CyberSAFE Corporation make any representations about the suitability of
31  * this software for any purpose.  It is provided "as is" without express
32  * or implied warranty.
33  *
34  * krb5_get_cred_from_kdc() and related functions:
35  *
36  * Get credentials from some KDC somewhere, possibly accumulating TGTs
37  * along the way.
38  */
39 
40 #include "k5-int.h"
41 #include <stdio.h>
42 #include "int-proto.h"
43 
44 struct tr_state;
45 
46 /*
47  * Ring buffer abstraction for TGTs returned from a ccache; avoids
48  * lots of excess copying.
49  */
50 
51 #define NCC_TGTS 2
52 struct cc_tgts {
53     krb5_creds cred[NCC_TGTS];
54     int dirty[NCC_TGTS];
55     unsigned int cur, nxt;
56 };
57 
58 /* NOTE: This only checks if NXT_TGT is CUR_CC_TGT. */
59 #define NXT_TGT_IS_CACHED(ts)			\
60 	((ts)->nxt_tgt == (ts)->cur_cc_tgt)
61 
62 #define MARK_CUR_CC_TGT_CLEAN(ts)			\
63 do {							\
64 	(ts)->cc_tgts.dirty[(ts)->cc_tgts.cur] = 0;	\
65 } while (0)
66 
67 static void init_cc_tgts(struct tr_state *);
68 static void shift_cc_tgts(struct tr_state *);
69 static void clean_cc_tgts(struct tr_state *);
70 
71 /*
72  * State struct for do_traversal() and helpers.
73  *
74  * CUR_TGT and NXT_TGT can each point either into CC_TGTS or into
75  * KDC_TGTS.
76  *
77  * CUR_TGT is the "working" TGT, which will be used to obtain new
78  * TGTs.  NXT_TGT will be CUR_TGT for the next iteration of the loop.
79  *
80  * Part of the baroqueness of this setup is to deal with annoying
81  * differences between krb5_cc_retrieve_cred() and
82  * krb5_get_cred_via_tkt(); krb5_cc_retrieve_cred() fills in a
83  * caller-allocated krb5_creds, while krb5_get_cred_via_tkt()
84  * allocates a krb5_creds for return.
85  */
86 struct tr_state {
87     krb5_context ctx;
88     krb5_ccache ccache;
89     krb5_principal *kdc_list;
90     unsigned int nkdcs;
91     krb5_principal *cur_kdc;
92     krb5_principal *nxt_kdc;
93     krb5_principal *lst_kdc;
94     krb5_creds *cur_tgt;
95     krb5_creds *nxt_tgt;
96     krb5_creds **kdc_tgts;
97     struct cc_tgts cc_tgts;
98     krb5_creds *cur_cc_tgt;
99     krb5_creds *nxt_cc_tgt;
100     unsigned int ntgts;
101 };
102 
103 /*
104  * Debug support
105  */
106 #ifdef DEBUG_GC_FRM_KDC
107 
108 #define TR_DBG(ts, prog) tr_dbg(ts, prog)
109 #define TR_DBG_RET(ts, prog, ret) tr_dbg_ret(ts, prog, ret)
110 #define TR_DBG_RTREE(ts, prog, princ) tr_dbg_rtree(ts, prog, princ)
111 
112 static void tr_dbg(struct tr_state *, const char *);
113 static void tr_dbg_ret(struct tr_state *, const char *, krb5_error_code);
114 static void tr_dbg_rtree(struct tr_state *, const char *, krb5_principal);
115 
116 #else
117 
118 #define TR_DBG(ts, prog)
119 #define TR_DBG_RET(ts, prog, ret)
120 #define TR_DBG_RTREE(ts, prog, princ)
121 
122 #endif /* !DEBUG_GC_FRM_KDC */
123 
124 #ifdef DEBUG_REFERRALS
125 
126 #define DPRINTF(x) printf x
127 #define DFPRINTF(x) fprintf x
128 #define DUMP_PRINC(x, y) krb5int_dbgref_dump_principal((x), (y))
129 
130 #else
131 
132 #define DPRINTF(x)
133 #define DFPRINTF(x)
134 #define DUMP_PRINC(x, y)
135 
136 #endif
137 
138 /* Convert ticket flags to necessary KDC options */
139 #define FLAGS2OPTS(flags) (flags & KDC_TKT_COMMON_MASK)
140 
141 /*
142  * Certain krb5_cc_retrieve_cred() errors are soft errors when looking
143  * for a cross-realm TGT.
144  */
145 #define HARD_CC_ERR(r) ((r) && (r) != KRB5_CC_NOTFOUND &&	\
146 	(r) != KRB5_CC_NOT_KTYPE)
147 
148 #define IS_TGS_PRINC(c, p)				\
149     ((krb5_princ_size((c), (p)) == 2) &&		\
150      (krb5_princ_component((c), (p), 0)->length ==	\
151       KRB5_TGS_NAME_SIZE) &&				\
152      (!memcmp(krb5_princ_component((c), (p), 0)->data,	\
153 	      KRB5_TGS_NAME, KRB5_TGS_NAME_SIZE)))
154 
155 /*
156  * Flags for ccache lookups of cross-realm TGTs.
157  *
158  * A cross-realm TGT may be issued by some other intermediate realm's
159  * KDC, so we use KRB5_TC_MATCH_SRV_NAMEONLY.
160  */
161 /*
162  * Solaris Kerberos:
163  * Include KRB5_TC_MATCH_TIMES so as to ensure that the retrieved ticket
164  * isn't stale.
165  */
166 #define RETR_FLAGS (KRB5_TC_MATCH_SRV_NAMEONLY | \
167 					KRB5_TC_SUPPORTED_KTYPES |   \
168 					KRB5_TC_MATCH_TIMES)
169 
170 /*
171  * Prototypes of helper functions
172  */
173 static krb5_error_code tgt_mcred(krb5_context, krb5_principal,
174     krb5_principal, krb5_principal, krb5_creds *);
175 static krb5_error_code retr_local_tgt(struct tr_state *, krb5_principal);
176 static krb5_error_code try_ccache(struct tr_state *, krb5_creds *);
177 static krb5_error_code find_nxt_kdc(struct tr_state *);
178 static krb5_error_code try_kdc(struct tr_state *, krb5_creds *);
179 static krb5_error_code kdc_mcred(struct tr_state *, krb5_principal,
180     krb5_creds *mcreds);
181 static krb5_error_code next_closest_tgt(struct tr_state *, krb5_principal);
182 static krb5_error_code init_rtree(struct tr_state *,
183     krb5_principal, krb5_principal);
184 static krb5_error_code do_traversal(krb5_context ctx, krb5_ccache,
185     krb5_principal client, krb5_principal server,
186     krb5_creds *out_cc_tgt, krb5_creds **out_tgt,
187     krb5_creds ***out_kdc_tgts);
188 static krb5_error_code krb5_get_cred_from_kdc_opt(krb5_context, krb5_ccache,
189     krb5_creds *, krb5_creds **, krb5_creds ***, int);
190 
191 /*
192  * init_cc_tgts()
193  *
194  * Initialize indices for cached-TGT ring buffer.  Caller must zero
195  * CC_TGTS, CC_TGT_DIRTY arrays prior to calling.
196  */
197 static void
198 init_cc_tgts(struct tr_state *ts)
199 {
200 
201     ts->cc_tgts.cur = 0;
202     ts->cc_tgts.nxt = 1;
203     ts->cur_cc_tgt = &ts->cc_tgts.cred[0];
204     ts->nxt_cc_tgt = &ts->cc_tgts.cred[1];
205 }
206 
207 /*
208  * shift_cc_tgts()
209  *
210  * Given a fresh assignment to NXT_CC_TGT, mark NXT_CC_TGT as dirty,
211  * and shift indices so old NXT_CC_TGT becomes new CUR_CC_TGT.  Clean
212  * the new NXT_CC_TGT.
213  */
214 static void
215 shift_cc_tgts(struct tr_state *ts)
216 {
217     unsigned int i;
218     struct cc_tgts *rb;
219 
220     rb = &ts->cc_tgts;
221     i = rb->cur = rb->nxt;
222     rb->dirty[i] = 1;
223     ts->cur_cc_tgt = ts->nxt_cc_tgt;
224 
225     i = (i + 1) % NCC_TGTS;
226 
227     rb->nxt = i;
228     ts->nxt_cc_tgt = &rb->cred[i];
229     if (rb->dirty[i]) {
230 	krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
231 	rb->dirty[i] = 0;
232     }
233 }
234 
235 /*
236  * clean_cc_tgts()
237  *
238  * Free CC_TGTS which were dirty, then mark them clean.
239  */
240 static void
241 clean_cc_tgts(struct tr_state *ts)
242 {
243     unsigned int i;
244     struct cc_tgts *rb;
245 
246     rb = &ts->cc_tgts;
247     for (i = 0; i < NCC_TGTS; i++) {
248 	if (rb->dirty[i]) {
249 	    krb5_free_cred_contents(ts->ctx, &rb->cred[i]);
250 	    rb->dirty[i] = 0;
251 	}
252     }
253 }
254 
255 /*
256  * Debug support
257  */
258 #ifdef DEBUG_GC_FRM_KDC
259 static void
260 tr_dbg(struct tr_state *ts, const char *prog)
261 {
262     krb5_error_code retval;
263     char *cur_tgt_str, *cur_kdc_str, *nxt_kdc_str;
264 
265     cur_tgt_str = cur_kdc_str = nxt_kdc_str = NULL;
266     retval = krb5_unparse_name(ts->ctx, ts->cur_tgt->server, &cur_tgt_str);
267     if (retval) goto cleanup;
268     retval = krb5_unparse_name(ts->ctx, *ts->cur_kdc, &cur_kdc_str);
269     if (retval) goto cleanup;
270     retval = krb5_unparse_name(ts->ctx, *ts->nxt_kdc, &nxt_kdc_str);
271     if (retval) goto cleanup;
272     fprintf(stderr, "%s: cur_tgt %s\n", prog, cur_tgt_str);
273     fprintf(stderr, "%s: cur_kdc %s\n", prog, cur_kdc_str);
274     fprintf(stderr, "%s: nxt_kdc %s\n", prog, nxt_kdc_str);
275 cleanup:
276     if (cur_tgt_str)
277 	krb5_free_unparsed_name(ts->ctx, cur_tgt_str);
278     if (cur_kdc_str)
279 	krb5_free_unparsed_name(ts->ctx, cur_kdc_str);
280     if (nxt_kdc_str)
281 	krb5_free_unparsed_name(ts->ctx, nxt_kdc_str);
282 }
283 
284 static void
285 tr_dbg_ret(struct tr_state *ts, const char *prog, krb5_error_code ret)
286 {
287     fprintf(stderr, "%s: return %d (%s)\n", prog, (int)ret,
288 	    error_message(ret));
289 }
290 
291 static void
292 tr_dbg_rtree(struct tr_state *ts, const char *prog, krb5_principal princ)
293 {
294     char *str;
295 
296     if (krb5_unparse_name(ts->ctx, princ, &str))
297 	return;
298     fprintf(stderr, "%s: %s\n", prog, str);
299     krb5_free_unparsed_name(ts->ctx, str);
300 }
301 #endif /* DEBUG_GC_FRM_KDC */
302 
303 /*
304  * tgt_mcred()
305  *
306  * Return MCREDS for use as a match criterion.
307  *
308  * Resulting credential has CLIENT as the client principal, and
309  * krbtgt/realm_of(DST)@realm_of(SRC) as the server principal.  Zeroes
310  * MCREDS first, does not allocate MCREDS, and cleans MCREDS on
311  * failure.  The peculiar ordering of DST and SRC args is for
312  * consistency with krb5_tgtname().
313  */
314 static krb5_error_code
315 tgt_mcred(krb5_context ctx, krb5_principal client,
316 	  krb5_principal dst, krb5_principal src,
317 	  krb5_creds *mcreds)
318 {
319     krb5_error_code retval;
320 
321     retval = 0;
322     memset(mcreds, 0, sizeof(*mcreds));
323 
324     retval = krb5_copy_principal(ctx, client, &mcreds->client);
325     if (retval)
326 	goto cleanup;
327 
328     retval = krb5_tgtname(ctx, krb5_princ_realm(ctx, dst),
329 			  krb5_princ_realm(ctx, src), &mcreds->server);
330     if (retval)
331 	goto cleanup;
332 
333 cleanup:
334     if (retval)
335 	krb5_free_cred_contents(ctx, mcreds);
336 
337     return retval;
338 }
339 
340 /*
341  * init_rtree()
342  *
343  * Populate KDC_LIST with the output of krb5_walk_realm_tree().
344  */
345 static krb5_error_code
346 init_rtree(struct tr_state *ts,
347 	   krb5_principal client, krb5_principal server)
348 {
349     krb5_error_code retval;
350 
351     ts->kdc_list = NULL;
352     retval = krb5_walk_realm_tree(ts->ctx, krb5_princ_realm(ts->ctx, client),
353 				  krb5_princ_realm(ts->ctx, server),
354 				  &ts->kdc_list, KRB5_REALM_BRANCH_CHAR);
355     if (retval)
356 	return retval;
357 
358     for (ts->nkdcs = 0; ts->kdc_list[ts->nkdcs]; ts->nkdcs++) {
359 	assert(krb5_princ_size(ts->ctx, ts->kdc_list[ts->nkdcs]) == 2);
360 	TR_DBG_RTREE(ts, "init_rtree", ts->kdc_list[ts->nkdcs]);
361     }
362     assert(ts->nkdcs > 1);
363     ts->lst_kdc = ts->kdc_list + ts->nkdcs - 1;
364 
365     ts->kdc_tgts = calloc(ts->nkdcs + 1, sizeof(krb5_creds));
366     if (ts->kdc_tgts == NULL)
367 	return ENOMEM;
368 
369     return 0;
370 }
371 
372 /*
373  * retr_local_tgt()
374  *
375  * Prime CUR_TGT with the cached TGT of the client's local realm.
376  */
377 static krb5_error_code
378 retr_local_tgt(struct tr_state *ts, krb5_principal client)
379 {
380     krb5_error_code retval;
381     krb5_creds tgtq;
382 
383     memset(&tgtq, 0, sizeof(tgtq));
384     retval = tgt_mcred(ts->ctx, client, client, client, &tgtq);
385     if (retval)
386 	return retval;
387 
388     /* Match realm, unlike other ccache retrievals here. */
389     retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache,
390 				   KRB5_TC_SUPPORTED_KTYPES,
391 				   &tgtq, ts->nxt_cc_tgt);
392     krb5_free_cred_contents(ts->ctx, &tgtq);
393     if (!retval) {
394 	shift_cc_tgts(ts);
395 	ts->nxt_tgt = ts->cur_tgt = ts->cur_cc_tgt;
396     }
397     return retval;
398 }
399 
400 /*
401  * try_ccache()
402  *
403  * Attempt to retrieve desired NXT_TGT from ccache.  Point NXT_TGT to
404  * it if successful.
405  */
406 static krb5_error_code
407 try_ccache(struct tr_state *ts, krb5_creds *tgtq)
408 {
409     krb5_error_code retval;
410     krb5_timestamp saved_endtime;
411 
412     TR_DBG(ts, "try_ccache");
413     /*
414      * Solaris Kerberos:
415      * Ensure the retrieved cred isn't stale.
416      * Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
417      */
418     saved_endtime = tgtq->times.endtime;
419     if ((retval = krb5_timeofday(ts->ctx, &(tgtq->times.endtime))) != 0) {
420     	tgtq->times.endtime = saved_endtime;
421     	return retval;
422     }
423     retval = krb5_cc_retrieve_cred(ts->ctx, ts->ccache, RETR_FLAGS,
424 				   tgtq, ts->nxt_cc_tgt);
425     if (!retval) {
426 	shift_cc_tgts(ts);
427 	ts->nxt_tgt = ts->cur_cc_tgt;
428     }
429     /*
430      * Solaris Kerberos:
431      * Ensure that tgtq->times.endtime is reset back to its original value so
432      * that if tgtq is used to request a ticket from the KDC it doesn't request
433      * a ticket with an endtime set to "now".
434      */
435     tgtq->times.endtime = saved_endtime;
436     TR_DBG_RET(ts, "try_ccache", retval);
437     return retval;
438 }
439 
440 /*
441  * find_nxt_kdc()
442  *
443  * A NXT_TGT gotten from an intermediate KDC might actually be a
444  * referral.  Search KDC_LIST forward starting from CUR_KDC, looking
445  * for the KDC with the same remote realm as NXT_TGT.  If we don't
446  * find it, the intermediate KDC is leading us off the transit path.
447  *
448  * Match on CUR_KDC's remote realm, not local realm, because, among
449  * other reasons, we can get a referral to the final realm; e.g.,
450  * given
451  *
452  *     KDC_LIST == { krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2,
453  *                   krbtgt/R4@R3, NULL }
454  *     CUR_TGT->SERVER == krbtgt/R2@R1
455  *     NXT_TGT->SERVER == krbtgt/R4@R2
456  *
457  * i.e., we got a ticket issued by R2 with remote realm R4, we want to
458  * find krbtgt/R4@R3, not krbtgt/R3@R2, even though we have no TGT
459  * with R3 as its local realm.
460  *
461  * Set up for next iteration of do_traversal() loop by pointing
462  * NXT_KDC to one entry forward of the match.
463  */
464 static krb5_error_code
465 find_nxt_kdc(struct tr_state *ts)
466 {
467     krb5_data *r1, *r2;
468     krb5_principal *kdcptr;
469 
470     TR_DBG(ts, "find_nxt_kdc");
471   /*
472    * Solaris Kerberos:
473    * The following assertion is not be true for the case when
474    * ts->nxt points to a cached ticket and not to a freshly
475    * fetched TGT in ts->kdc_tgts. See changes in try_kdc()
476    */
477   /*  assert(ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]); */
478     if (krb5_princ_size(ts->ctx, ts->nxt_tgt->server) != 2)
479 	return KRB5_KDCREP_MODIFIED;
480 
481     r1 = krb5_princ_component(ts->ctx, ts->nxt_tgt->server, 1);
482 
483     for (kdcptr = ts->cur_kdc + 1; *kdcptr != NULL; kdcptr++) {
484 
485 	r2 = krb5_princ_component(ts->ctx, *kdcptr, 1);
486 
487 	if (r1 != NULL && r2 != NULL &&
488 	    r1->length == r2->length &&
489 	    !memcmp(r1->data, r2->data, r1->length)) {
490 	    break;
491 	}
492     }
493     if (*kdcptr == NULL) {
494 	/*
495 	 * Not found; we probably got an unexpected realm referral.
496 	 * Don't touch NXT_KDC, thus allowing next_closest_tgt() to
497 	 * continue looping backwards.
498 	 */
499 	/*
500 	 * Solaris Kerberos:
501 	 * Only free the allocated creds if they are in kdc_tgts. If they
502 	 * are in cc_tgts no freeing is necessary.
503 	 */
504 	if (ts->ntgts > 0 && ts->nxt_tgt == ts->kdc_tgts[ts->ntgts-1]) {
505 	    /* Punt NXT_TGT from KDC_TGTS if bogus. */
506 	    krb5_free_creds(ts->ctx, ts->kdc_tgts[--ts->ntgts]);
507 	    ts->kdc_tgts[ts->ntgts] = NULL;
508 	}
509 	TR_DBG_RET(ts, "find_nxt_kdc", KRB5_KDCREP_MODIFIED);
510 	return KRB5_KDCREP_MODIFIED;
511     }
512     ts->nxt_kdc = kdcptr;
513     TR_DBG_RET(ts, "find_nxt_kdc", 0);
514     return 0;
515 }
516 
517 /*
518  * try_kdc()
519  *
520  * Using CUR_TGT, attempt to get desired NXT_TGT.  Update NXT_KDC if
521  * successful.
522  */
523 static krb5_error_code
524 try_kdc(struct tr_state *ts, krb5_creds *tgtq)
525 {
526     krb5_error_code retval;
527     krb5_creds ltgtq;
528     krb5_creds *tmp_out_cred;
529 
530     TR_DBG(ts, "try_kdc");
531     /* This check should probably be in gc_via_tkt. */
532     if (!krb5_c_valid_enctype(ts->cur_tgt->keyblock.enctype))
533 	return KRB5_PROG_ETYPE_NOSUPP;
534 
535     ltgtq = *tgtq;
536     ltgtq.is_skey = FALSE;
537     ltgtq.ticket_flags = ts->cur_tgt->ticket_flags;
538     /*
539      * Solaris Kerberos:
540      * Store credential in a temporary ticket as we may not
541      * want to add it to ts->kdc_tgts if it is already in
542      * the cache.
543      */
544     retval = krb5_get_cred_via_tkt(ts->ctx, ts->cur_tgt,
545 				   FLAGS2OPTS(ltgtq.ticket_flags),
546 				   ts->cur_tgt->addresses,
547 				   &ltgtq, &tmp_out_cred);
548     if (retval) {
549 	ts->ntgts--;
550 	ts->nxt_tgt = ts->cur_tgt;
551 	TR_DBG_RET(ts, "try_kdc", retval);
552 	return retval;
553     }
554 
555     /*
556      * Solaris Kerberos:
557      * See if the returned creds are different to the requested creds.
558      * This can happen when the server returns a TGT "closer" to the
559      * desired realm.
560      */
561     if (!(krb5_principal_compare(ts->ctx, tgtq->server, tmp_out_cred->server))) {
562 	    /* Not equal, ticket may already be in the cache */
563 	    retval = try_ccache(ts, tmp_out_cred);
564 	    if (!retval) {
565 	        krb5_free_creds(ts->ctx, tmp_out_cred);
566 	        retval = find_nxt_kdc(ts);
567 	        return retval;
568 	    }
569 	}
570 
571     ts->kdc_tgts[ts->ntgts++] = tmp_out_cred;
572     ts->nxt_tgt = ts->kdc_tgts[ts->ntgts-1];
573     retval = find_nxt_kdc(ts);
574     TR_DBG_RET(ts, "try_kdc", retval);
575     return retval;
576 }
577 
578 /*
579  * kdc_mcred()
580  *
581  * Return MCREDS for use as a match criterion.
582  *
583  * Resulting credential has CLIENT as the client principal, and
584  * krbtgt/remote_realm(NXT_KDC)@local_realm(CUR_KDC) as the server
585  * principal.  Zeroes MCREDS first, does not allocate MCREDS, and
586  * cleans MCREDS on failure.
587  */
588 static krb5_error_code
589 kdc_mcred(struct tr_state *ts, krb5_principal client, krb5_creds *mcreds)
590 {
591     krb5_error_code retval;
592     krb5_data *rdst, *rsrc;
593 
594     retval = 0;
595     memset(mcreds, 0, sizeof(*mcreds));
596 
597     rdst = krb5_princ_component(ts->ctx, *ts->nxt_kdc, 1);
598     rsrc = krb5_princ_component(ts->ctx, *ts->cur_kdc, 1);
599     retval = krb5_copy_principal(ts->ctx, client, &mcreds->client);
600     if (retval)
601 	goto cleanup;
602 
603     retval = krb5_tgtname(ts->ctx, rdst, rsrc, &mcreds->server);
604     if (retval)
605 	goto cleanup;
606 
607 cleanup:
608     if (retval)
609 	krb5_free_cred_contents(ts->ctx, mcreds);
610 
611     return retval;
612 }
613 
614 /*
615  * next_closest_tgt()
616  *
617  * Using CUR_TGT, attempt to get the cross-realm TGT having its remote
618  * realm closest to the target principal's.  Update NXT_TGT, NXT_KDC
619  * accordingly.
620  */
621 static krb5_error_code
622 next_closest_tgt(struct tr_state *ts, krb5_principal client)
623 {
624     krb5_error_code retval;
625     krb5_creds tgtq;
626 
627     retval = 0;
628     memset(&tgtq, 0, sizeof(tgtq));
629 
630     for (ts->nxt_kdc = ts->lst_kdc;
631 	 ts->nxt_kdc > ts->cur_kdc;
632 	 ts->nxt_kdc--) {
633 
634 	krb5_free_cred_contents(ts->ctx, &tgtq);
635 	retval = kdc_mcred(ts, client, &tgtq);
636 	if (retval)
637 	    goto cleanup;
638 	/* Don't waste time retrying ccache for direct path. */
639 	if (ts->cur_kdc != ts->kdc_list || ts->nxt_kdc != ts->lst_kdc) {
640 	    retval = try_ccache(ts, &tgtq);
641 	    if (!retval)
642 		break;
643 	    if (HARD_CC_ERR(retval))
644 		goto cleanup;
645 	}
646 	/* Not in the ccache, so talk to a KDC. */
647 	retval = try_kdc(ts, &tgtq);
648 	if (!retval) {
649 	    break;
650 	}
651 	/*
652 	 * Because try_kdc() validates referral TGTs, it can return an
653 	 * error indicating a bogus referral.  The loop continues when
654 	 * it gets a bogus referral, which is arguably the right
655 	 * thing.  (Previous implementation unconditionally failed.)
656 	 */
657     }
658     /*
659      * If we have a non-zero retval, we either have a hard error or we
660      * failed to find a closer TGT.
661      */
662 cleanup:
663     krb5_free_cred_contents(ts->ctx, &tgtq);
664     return retval;
665 }
666 
667 /*
668  * do_traversal()
669  *
670  * Find final TGT needed to get CLIENT a ticket for SERVER.  Point
671  * OUT_TGT at the desired TGT, which may be an existing cached TGT
672  * (copied into OUT_CC_TGT) or one of the newly obtained TGTs
673  * (collected in OUT_KDC_TGTS).
674  *
675  * Get comfortable; this is somewhat complicated.
676  *
677  * Nomenclature: Cross-realm TGS principal names have the form:
678  *
679  *     krbtgt/REMOTE@LOCAL
680  *
681  * krb5_walk_realm_tree() returns a list like:
682  *
683  *     krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, ...
684  *
685  * These are prinicpal names, not realm names.  We only really use the
686  * remote parts of the TGT principal names.
687  *
688  * The do_traversal loop calls next_closest_tgt() to find the next
689  * closest TGT to the destination realm.  next_closest_tgt() updates
690  * NXT_KDC for the following iteration of the do_traversal() loop.
691  *
692  * At the beginning of any given iteration of the do_traversal() loop,
693  * CUR_KDC's remote realm is the remote realm of CUR_TGT->SERVER.  The
694  * local realms of CUR_KDC and CUR_TGT->SERVER may not match due to
695  * short-circuit paths provided by intermediate KDCs, e.g., CUR_KDC
696  * might be krbtgt/D@C, while CUR_TGT->SERVER is krbtgt/D@B.
697  *
698  * For example, given KDC_LIST of
699  *
700  * krbtgt/R1@R1, krbtgt/R2@R1, krbtgt/R3@R2, krbtgt/R4@R3,
701  * krbtgt/R5@R4
702  *
703  * The next_closest_tgt() loop moves NXT_KDC to the left starting from
704  * R5, stopping before it reaches CUR_KDC.  When next_closest_tgt()
705  * returns, the do_traversal() loop updates CUR_KDC to be NXT_KDC, and
706  * calls next_closest_tgt() again.
707  *
708  * next_closest_tgt() at start of its loop:
709  *
710  *      CUR                 NXT
711  *       |                   |
712  *       V                   V
713  *     +----+----+----+----+----+
714  *     | R1 | R2 | R3 | R4 | R5 |
715  *     +----+----+----+----+----+
716  *
717  * next_closest_tgt() returns after finding a ticket for krbtgt/R3@R1:
718  *
719  *      CUR       NXT
720  *       |         |
721  *       V         V
722  *     +----+----+----+----+----+
723  *     | R1 | R2 | R3 | R4 | R5 |
724  *     +----+----+----+----+----+
725  *
726  * do_traversal() updates CUR_KDC:
727  *
728  *                NXT
729  *                CUR
730  *                 |
731  *                 V
732  *     +----+----+----+----+----+
733  *     | R1 | R2 | R3 | R4 | R5 |
734  *     +----+----+----+----+----+
735  *
736  * next_closest_tgt() at start of its loop:
737  *
738  *                CUR       NXT
739  *                 |         |
740  *                 V         V
741  *     +----+----+----+----+----+
742  *     | R1 | R2 | R3 | R4 | R5 |
743  *     +----+----+----+----+----+
744  *
745  * etc.
746  *
747  * The algorithm executes in n*(n-1)/2 (the sum of integers from 1 to
748  * n-1) attempts in the worst case, i.e., each KDC only has a
749  * cross-realm ticket for the immediately following KDC in the transit
750  * path.  Typically, short-circuit paths will cause execution occur
751  * faster than this worst-case scenario.
752  *
753  * When next_closest_tgt() updates NXT_KDC, it may not perform a
754  * simple increment from CUR_KDC, in part because some KDC may
755  * short-circuit pieces of the transit path.
756  */
757 static krb5_error_code
758 do_traversal(krb5_context ctx,
759 	     krb5_ccache ccache,
760 	     krb5_principal client,
761 	     krb5_principal server,
762 	     krb5_creds *out_cc_tgt,
763 	     krb5_creds **out_tgt,
764 	     krb5_creds ***out_kdc_tgts)
765 {
766     krb5_error_code retval;
767     struct tr_state state, *ts;
768 
769     *out_tgt = NULL;
770     *out_kdc_tgts = NULL;
771     ts = &state;
772     memset(ts, 0, sizeof(*ts));
773     ts->ctx = ctx;
774     ts->ccache = ccache;
775     init_cc_tgts(ts);
776 
777     retval = init_rtree(ts, client, server);
778     if (retval)
779 	goto cleanup;
780 
781     retval = retr_local_tgt(ts, client);
782     if (retval)
783 	goto cleanup;
784 
785     for (ts->cur_kdc = ts->kdc_list, ts->nxt_kdc = NULL;
786 	 ts->cur_kdc != NULL && ts->cur_kdc < ts->lst_kdc;
787 	 ts->cur_kdc = ts->nxt_kdc, ts->cur_tgt = ts->nxt_tgt) {
788 
789 	retval = next_closest_tgt(ts, client);
790 	if (retval)
791 	    goto cleanup;
792 	assert(ts->cur_kdc != ts->nxt_kdc);
793     }
794 
795     if (NXT_TGT_IS_CACHED(ts)) {
796 	*out_cc_tgt = *ts->cur_cc_tgt;
797 	*out_tgt = out_cc_tgt;
798 	MARK_CUR_CC_TGT_CLEAN(ts);
799     } else {
800 	/* CUR_TGT is somewhere in KDC_TGTS; no need to copy. */
801 	*out_tgt = ts->nxt_tgt;
802     }
803 
804 cleanup:
805     clean_cc_tgts(ts);
806     if (ts->kdc_list != NULL)
807 	krb5_free_realm_tree(ctx, ts->kdc_list);
808     if (ts->ntgts == 0) {
809 	*out_kdc_tgts = NULL;
810 	if (ts->kdc_tgts != NULL)
811 	    free(ts->kdc_tgts);
812     } else
813 	*out_kdc_tgts = ts->kdc_tgts;
814     return retval;
815 }
816 
817 /*
818  * krb5_get_cred_from_kdc_opt()
819  * krb5_get_cred_from_kdc()
820  * krb5_get_cred_from_kdc_validate()
821  * krb5_get_cred_from_kdc_renew()
822  *
823  * Retrieve credentials for client IN_CRED->CLIENT, server
824  * IN_CRED->SERVER, ticket flags IN_CRED->TICKET_FLAGS, possibly
825  * second_ticket if needed.
826  *
827  * Request credentials from the KDC for the server's realm.  Point
828  * TGTS to an allocated array of pointers to krb5_creds, containing
829  * any intermediate credentials obtained in the process of contacting
830  * the server's KDC; if no intermediate credentials were obtained,
831  * TGTS is a null pointer.  Return intermediate credentials if
832  * intermediate KDCs provided credentials, even if no useful end
833  * ticket results.
834  *
835  * Caller must free TGTS, regardless of whether this function returns
836  * success.
837  *
838  * This function does NOT cache the intermediate TGTs.
839  *
840  * Do not call this routine if desired credentials are already cached.
841  *
842  * On success, OUT_CRED contains the desired credentials; the caller
843  * must free them.
844  *
845  * Beware memory management issues if you have modifications in mind.
846  * With the addition of referral support, it is now the case that *tgts,
847  * referral_tgts, tgtptr, referral_tgts, and *out_creds all may point to
848  * the same credential at different times.
849  *
850  * Returns errors, system errors.
851  */
852 
853 static krb5_error_code
854 krb5_get_cred_from_kdc_opt(krb5_context context, krb5_ccache ccache,
855 			   krb5_creds *in_cred, krb5_creds **out_cred,
856 			   krb5_creds ***tgts, int kdcopt)
857 {
858     krb5_error_code retval, subretval;
859     krb5_principal client, server, supplied_server, out_supplied_server;
860     krb5_creds tgtq, cc_tgt, *tgtptr, *referral_tgts[KRB5_REFERRAL_MAXHOPS];
861     krb5_boolean old_use_conf_ktypes;
862     char **hrealms;
863     int referral_count, i;
864 
865     /*
866      * Set up client and server pointers.  Make a fresh and modifyable
867      * copy of the in_cred server and save the supplied version.
868      */
869     client = in_cred->client;
870     if ((retval=krb5_copy_principal(context, in_cred->server, &server)))
871         return retval;
872     /* We need a second copy for the output creds. */
873     if ((retval = krb5_copy_principal(context, server,
874 				      &out_supplied_server)) != 0 ) {
875 	krb5_free_principal(context, server);
876 	return retval;
877     }
878     supplied_server = in_cred->server;
879     in_cred->server=server;
880 
881     DUMP_PRINC("gc_from_kdc initial client", client);
882     DUMP_PRINC("gc_from_kdc initial server", server);
883     memset(&cc_tgt, 0, sizeof(cc_tgt));
884     memset(&tgtq, 0, sizeof(tgtq));
885     memset(&referral_tgts, 0, sizeof(referral_tgts));
886 
887     tgtptr = NULL;
888     *tgts = NULL;
889     *out_cred=NULL;
890     old_use_conf_ktypes = context->use_conf_ktypes;
891 
892     /* Copy client realm to server if no hint. */
893     if (krb5_is_referral_realm(&server->realm)) {
894         /* Use the client realm. */
895         DPRINTF(("gc_from_kdc: no server realm supplied, "
896 		 "using client realm.\n"));
897 	krb5_free_data_contents(context, &server->realm);
898 	if (!( server->realm.data = (char *)malloc(client->realm.length+1)))
899 	    return ENOMEM;
900 	memcpy(server->realm.data, client->realm.data, client->realm.length);
901 	server->realm.length = client->realm.length;
902 	server->realm.data[server->realm.length] = 0;
903     }
904     /*
905      * Retreive initial TGT to match the specified server, either for the
906      * local realm in the default (referral) case or for the remote
907      * realm if we're starting someplace non-local.
908      */
909     retval = tgt_mcred(context, client, server, client, &tgtq);
910     if (retval)
911 	goto cleanup;
912 
913     /* Fast path: Is it in the ccache? */
914     context->use_conf_ktypes = 1;
915 
916     /*
917      * Solaris Kerberos:
918      * Ensure the retrieved cred isn't stale.
919      * Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
920      */
921     if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
922     	goto cleanup;
923     }
924     retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
925 				   &tgtq, &cc_tgt);
926     if (!retval) {
927 	tgtptr = &cc_tgt;
928     } else if (!HARD_CC_ERR(retval)) {
929         DPRINTF(("gc_from_kdc: starting do_traversal to find "
930 		 "initial TGT for referral\n"));
931 	retval = do_traversal(context, ccache, client, server,
932 			      &cc_tgt, &tgtptr, tgts);
933     }
934     if (retval) {
935         DPRINTF(("gc_from_kdc: failed to find initial TGT for referral\n"));
936         goto cleanup;
937     }
938 
939     DUMP_PRINC("gc_from_kdc: server as requested", supplied_server);
940 
941     /*
942      * Try requesting a service ticket from our local KDC with referrals
943      * turned on.  If the first referral succeeds, follow a referral-only
944      * path, otherwise fall back to old-style assumptions.
945      */
946 
947     for (referral_count = 0;
948 	 referral_count < KRB5_REFERRAL_MAXHOPS;
949 	 referral_count++) {
950 #if 0
951         DUMP_PRINC("gc_from_kdc: referral loop: tgt in use", tgtptr->server);
952         DUMP_PRINC("gc_from_kdc: referral loop: request is for", server);
953 #endif
954         retval = krb5_get_cred_via_tkt(context, tgtptr,
955 				       KDC_OPT_CANONICALIZE |
956 				       FLAGS2OPTS(tgtptr->ticket_flags) |
957 				       kdcopt |
958 				       (in_cred->second_ticket.length ?
959 					KDC_OPT_ENC_TKT_IN_SKEY : 0),
960 				       tgtptr->addresses, in_cred, out_cred);
961 	if (retval) {
962 	    DPRINTF(("gc_from_kdc: referral TGS-REQ request failed: <%s>\n",
963 		     error_message(retval)));
964 	    /* If we haven't gone anywhere yet, fail through to the
965 	       non-referral case. */
966 	    if (referral_count==0) {
967 	        DPRINTF(("gc_from_kdc: initial referral failed; "
968 			 "punting to fallback.\n"));
969 	        break;
970 	    }
971 	    /* Otherwise, try the same query without canonicalization
972 	       set, and fail hard if that doesn't work. */
973 	    DPRINTF(("gc_from_kdc: referral #%d failed; "
974 		     "retrying without option.\n", referral_count + 1));
975 	    retval = krb5_get_cred_via_tkt(context, tgtptr,
976 					   FLAGS2OPTS(tgtptr->ticket_flags) |
977 					   kdcopt |
978 					   (in_cred->second_ticket.length ?
979 					    KDC_OPT_ENC_TKT_IN_SKEY : 0),
980 					   tgtptr->addresses,
981 					   in_cred, out_cred);
982 	    /* Whether or not that succeeded, we're done. */
983 	    goto cleanup;
984 	}
985 	    /* Referral request succeeded; let's see what it is. */
986 	    if (krb5_principal_compare(context, in_cred->server,
987 				       (*out_cred)->server)) {
988 		DPRINTF(("gc_from_kdc: request generated ticket "
989 			 "for requested server principal\n"));
990 		DUMP_PRINC("gc_from_kdc final referred reply",
991 			   in_cred->server);
992 
993 	    /*
994 	     * Check if the return enctype is one that we requested if
995 	     * needed.
996 	     */
997 	    if (old_use_conf_ktypes || context->tgs_ktype_count == 0)
998 		goto cleanup;
999 	    for (i = 0; i < context->tgs_ktype_count; i++) {
1000 		if ((*out_cred)->keyblock.enctype == context->tgs_ktypes[i]) {
1001 		    /* Found an allowable etype, so we're done */
1002 		    goto cleanup;
1003 		}
1004 	    }
1005 	    /*
1006 	     *  We need to try again, but this time use the
1007 	     *  tgs_ktypes in the context. At this point we should
1008 	     *  have all the tgts to succeed.
1009 	     */
1010 
1011 	    /* Free "wrong" credential */
1012 	    krb5_free_creds(context, *out_cred);
1013 	    *out_cred = NULL;
1014 	    /* Re-establish tgs etypes */
1015 	    context->use_conf_ktypes = old_use_conf_ktypes;
1016 	    retval = krb5_get_cred_via_tkt(context, tgtptr,
1017 					   KDC_OPT_CANONICALIZE |
1018 					   FLAGS2OPTS(tgtptr->ticket_flags) |
1019 					   kdcopt |
1020 					   (in_cred->second_ticket.length ?
1021 					    KDC_OPT_ENC_TKT_IN_SKEY : 0),
1022 					   tgtptr->addresses,
1023 					   in_cred, out_cred);
1024 		goto cleanup;
1025 	    }
1026 	    else if (IS_TGS_PRINC(context, (*out_cred)->server)) {
1027 		krb5_data *r1, *r2;
1028 
1029 		DPRINTF(("gc_from_kdc: request generated referral tgt\n"));
1030 		DUMP_PRINC("gc_from_kdc credential received",
1031 			   (*out_cred)->server);
1032 
1033 		if (referral_count == 0)
1034 		    r1 = &tgtptr->server->data[1];
1035 		else
1036 		    r1 = &referral_tgts[referral_count-1]->server->data[1];
1037 
1038 		r2 = &(*out_cred)->server->data[1];
1039 		if (r1->length == r2->length &&
1040 		    !memcmp(r1->data, r2->data, r1->length)) {
1041 		    DPRINTF(("gc_from_kdc: referred back to "
1042 			     "previous realm; fall back\n"));
1043 		    krb5_free_creds(context, *out_cred);
1044 		    *out_cred = NULL;
1045 		    break;
1046 		}
1047 		/* Check for referral routing loop. */
1048 		for (i=0;i<referral_count;i++) {
1049 #if 0
1050 		    DUMP_PRINC("gc_from_kdc: loop compare #1",
1051 			       (*out_cred)->server);
1052 		    DUMP_PRINC("gc_from_kdc: loop compare #2",
1053 			       referral_tgts[i]->server);
1054 #endif
1055 		    if (krb5_principal_compare(context,
1056 					       (*out_cred)->server,
1057 					       referral_tgts[i]->server)) {
1058 			DFPRINTF((stderr,
1059 				  "krb5_get_cred_from_kdc_opt: "
1060 				  "referral routing loop - "
1061 				  "got referral back to hop #%d\n", i));
1062 			retval=KRB5_KDC_UNREACH;
1063 			goto cleanup;
1064 		    }
1065 		}
1066 		/* Point current tgt pointer at newly-received TGT. */
1067 		if (tgtptr == &cc_tgt)
1068 		    krb5_free_cred_contents(context, tgtptr);
1069 		tgtptr=*out_cred;
1070 		/* Save pointer to tgt in referral_tgts. */
1071 		referral_tgts[referral_count]=*out_cred;
1072 		/* Copy krbtgt realm to server principal. */
1073 		krb5_free_data_contents(context, &server->realm);
1074 		retval = krb5int_copy_data_contents(context,
1075 						    &tgtptr->server->data[1],
1076 						    &server->realm);
1077 		if (retval)
1078 		    return retval;
1079 		/*
1080 		 * Future work: rewrite server principal per any
1081 		 * supplied padata.
1082 		 */
1083 	    } else {
1084 		/* Not a TGT; punt to fallback. */
1085 		krb5_free_creds(context, *out_cred);
1086 		*out_cred = NULL;
1087 		break;
1088 	}
1089     }
1090 
1091     DUMP_PRINC("gc_from_kdc client at fallback", client);
1092     DUMP_PRINC("gc_from_kdc server at fallback", server);
1093 
1094     /*
1095      * At this point referrals have been tried and have failed.  Go
1096      * back to the server principal as originally issued and try the
1097      * conventional path.
1098      */
1099 
1100     /*
1101      * Referrals have failed.  Look up fallback realm if not
1102      * originally provided.
1103      */
1104     if (krb5_is_referral_realm(&supplied_server->realm)) {
1105         if (server->length >= 2) {
1106 	    retval=krb5_get_fallback_host_realm(context, &server->data[1],
1107 						&hrealms);
1108 	    if (retval) goto cleanup;
1109 #if 0
1110 	    DPRINTF(("gc_from_kdc: using fallback realm of %s\n",
1111 		     hrealms[0]));
1112 #endif
1113 	    krb5_free_data_contents(context,&in_cred->server->realm);
1114 	    server->realm.data=hrealms[0];
1115 	    server->realm.length=strlen(hrealms[0]);
1116 	    free(hrealms);
1117 	}
1118 	else {
1119 	    /*
1120 	     * Problem case: Realm tagged for referral but apparently not
1121 	     * in a <type>/<host> format that
1122 	     * krb5_get_fallback_host_realm can deal with.
1123 	     */
1124 	    DPRINTF(("gc_from_kdc: referral specified "
1125 		     "but no fallback realm avaiable!\n"));
1126 	    return KRB5_ERR_HOST_REALM_UNKNOWN;
1127 	}
1128     }
1129 
1130     DUMP_PRINC("gc_from_kdc server at fallback after fallback rewrite",
1131 	       server);
1132 
1133     /*
1134      * Get a TGT for the target realm.
1135      */
1136 
1137     krb5_free_cred_contents(context, &tgtq);
1138     retval = tgt_mcred(context, client, server, client, &tgtq);
1139     if (retval)
1140 	goto cleanup;
1141 
1142     /* Fast path: Is it in the ccache? */
1143     /* Free tgtptr data if reused from above. */
1144     if (tgtptr == &cc_tgt)
1145 	krb5_free_cred_contents(context, tgtptr);
1146     /* Free TGTS if previously filled by do_traversal() */
1147     if (*tgts != NULL) {
1148 	for (i = 0; (*tgts)[i] != NULL; i++) {
1149 	    krb5_free_creds(context, (*tgts)[i]);
1150 	}
1151 	free(*tgts);
1152 	*tgts = NULL;
1153     }
1154     context->use_conf_ktypes = 1;
1155     /*
1156      * Solaris Kerberos:
1157      * Ensure the retrieved cred isn't stale.
1158      * Set endtime to now so krb5_cc_retrieve_cred won't return an expired ticket.
1159      */
1160     if ((retval = krb5_timeofday(context, &(tgtq.times.endtime))) != 0) {
1161     	goto cleanup;
1162     }
1163     retval = krb5_cc_retrieve_cred(context, ccache, RETR_FLAGS,
1164 				   &tgtq, &cc_tgt);
1165     if (!retval) {
1166 	tgtptr = &cc_tgt;
1167     } else if (!HARD_CC_ERR(retval)) {
1168 	retval = do_traversal(context, ccache, client, server,
1169 			      &cc_tgt, &tgtptr, tgts);
1170     }
1171     if (retval)
1172 	goto cleanup;
1173 
1174     /*
1175      * Finally have TGT for target realm!  Try using it to get creds.
1176      */
1177 
1178     if (!krb5_c_valid_enctype(tgtptr->keyblock.enctype)) {
1179 	retval = KRB5_PROG_ETYPE_NOSUPP;
1180 	goto cleanup;
1181     }
1182 
1183     context->use_conf_ktypes = old_use_conf_ktypes;
1184     retval = krb5_get_cred_via_tkt(context, tgtptr,
1185 				   FLAGS2OPTS(tgtptr->ticket_flags) |
1186 				   kdcopt |
1187 				   (in_cred->second_ticket.length ?
1188 				    KDC_OPT_ENC_TKT_IN_SKEY : 0),
1189 				   tgtptr->addresses, in_cred, out_cred);
1190 
1191 cleanup:
1192     krb5_free_cred_contents(context, &tgtq);
1193     if (tgtptr == &cc_tgt)
1194 	krb5_free_cred_contents(context, tgtptr);
1195     context->use_conf_ktypes = old_use_conf_ktypes;
1196     /* Drop the original principal back into in_cred so that it's cached
1197        in the expected format. */
1198     DUMP_PRINC("gc_from_kdc: final hacked server principal at cleanup",
1199 	       server);
1200     krb5_free_principal(context, server);
1201     in_cred->server = supplied_server;
1202     if (*out_cred && !retval) {
1203         /* Success: free server, swap supplied server back in. */
1204         krb5_free_principal (context, (*out_cred)->server);
1205 	(*out_cred)->server= out_supplied_server;
1206     }
1207     else {
1208         /*
1209 	 * Failure: free out_supplied_server.  Don't free out_cred here
1210 	 * since it's either null or a referral TGT that we free below,
1211 	 * and we may need it to return.
1212 	 */
1213         krb5_free_principal (context, out_supplied_server);
1214     }
1215     DUMP_PRINC("gc_from_kdc: final server after reversion", in_cred->server);
1216     /*
1217      * Deal with ccache TGT management: If tgts has been set from
1218      * initial non-referral TGT discovery, leave it alone.  Otherwise, if
1219      * referral_tgts[0] exists return it as the only entry in tgts.
1220      * (Further referrals are never cached, only the referral from the
1221      * local KDC.)  This is part of cleanup because useful received TGTs
1222      * should be cached even if the main request resulted in failure.
1223      */
1224 
1225     if (*tgts == NULL) {
1226         if (referral_tgts[0]) {
1227 #if 0
1228   	    /*
1229 	     * This should possibly be a check on the candidate return
1230 	     * credential against the cache, in the circumstance where we
1231 	     * don't want to clutter the cache with near-duplicate
1232 	     * credentials on subsequent iterations.  For now, it is
1233 	     * disabled.
1234 	     */
1235 	    subretval=...?;
1236 	    if (subretval) {
1237 #endif
1238 	        /* Allocate returnable TGT list. */
1239 	        if (!(*tgts=calloc(sizeof (krb5_creds *), 2)))
1240 		    return ENOMEM;
1241 		subretval=krb5_copy_creds(context, referral_tgts[0], &((*tgts)[0]));
1242 		if(subretval)
1243 		    return subretval;
1244 		(*tgts)[1]=NULL;
1245 		DUMP_PRINC("gc_from_kdc: returning referral TGT for ccache",
1246 			   (*tgts)[0]->server);
1247 #if 0
1248 	    }
1249 #endif
1250 	}
1251     }
1252 
1253     /* Free referral TGTs list. */
1254     for (i=0;i<KRB5_REFERRAL_MAXHOPS;i++) {
1255         if(referral_tgts[i]) {
1256 	    krb5_free_creds(context, referral_tgts[i]);
1257 	}
1258     }
1259     DPRINTF(("gc_from_kdc finishing with %s\n",
1260 	     retval ? error_message(retval) : "no error"));
1261     return retval;
1262 }
1263 
1264 krb5_error_code
1265 krb5_get_cred_from_kdc(krb5_context context, krb5_ccache ccache,
1266 		       krb5_creds *in_cred, krb5_creds **out_cred,
1267 		       krb5_creds ***tgts)
1268 {
1269     return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
1270 				      0);
1271 }
1272 
1273 krb5_error_code
1274 krb5_get_cred_from_kdc_validate(krb5_context context, krb5_ccache ccache,
1275 				krb5_creds *in_cred, krb5_creds **out_cred,
1276 				krb5_creds ***tgts)
1277 {
1278     return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
1279 				      KDC_OPT_VALIDATE);
1280 }
1281 
1282 krb5_error_code
1283 krb5_get_cred_from_kdc_renew(krb5_context context, krb5_ccache ccache,
1284 			     krb5_creds *in_cred, krb5_creds **out_cred,
1285 			     krb5_creds ***tgts)
1286 {
1287     return krb5_get_cred_from_kdc_opt(context, ccache, in_cred, out_cred, tgts,
1288 				      KDC_OPT_RENEW);
1289 }
1290