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