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 <gtq, &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