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