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