1/* 2 * Copyright (c) 1997 - 2008 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2009 - 2010 Apple Inc. All rights reserved. 7 * 8 * Redistribution and use in source and binary forms, with or without 9 * modification, are permitted provided that the following conditions 10 * are met: 11 * 12 * 1. Redistributions of source code must retain the above copyright 13 * notice, this list of conditions and the following disclaimer. 14 * 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * 3. Neither the name of the Institute nor the names of its contributors 20 * may be used to endorse or promote products derived from this software 21 * without specific prior written permission. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 */ 35 36#include "krb5_locl.h" 37#include <assert.h> 38 39static krb5_error_code 40get_cred_kdc_capath(krb5_context, krb5_kdc_flags, 41 krb5_ccache, struct krb5_fast_state *, krb5_creds *, krb5_principal, 42 Ticket *, const char *, krb5_creds **, krb5_creds ***); 43 44krb5_error_code 45_krb5_get_krbtgt(krb5_context context, 46 krb5_ccache id, 47 krb5_realm realm, 48 krb5_creds **cred) 49{ 50 krb5_error_code ret; 51 krb5_creds tmp_cred; 52 53 memset(&tmp_cred, 0, sizeof(tmp_cred)); 54 55 ret = krb5_cc_get_principal(context, id, &tmp_cred.client); 56 if (ret) 57 return ret; 58 59 if (realm == NULL) 60 realm = tmp_cred.client->realm; 61 62 ret = krb5_make_principal(context, 63 &tmp_cred.server, 64 realm, 65 KRB5_TGS_NAME, 66 realm, 67 NULL); 68 if(ret) { 69 krb5_free_principal(context, tmp_cred.client); 70 return ret; 71 } 72 ret = krb5_get_credentials(context, 73 KRB5_GC_CACHED, 74 id, 75 &tmp_cred, 76 cred); 77 krb5_free_principal(context, tmp_cred.client); 78 krb5_free_principal(context, tmp_cred.server); 79 if(ret) 80 return ret; 81 return 0; 82} 83 84static krb5_error_code 85get_cred_kdc(krb5_context context, 86 krb5_ccache id, 87 struct krb5_fast_state *fast_state, 88 krb5_kdc_flags flags, 89 krb5_addresses *addresses, 90 krb5_creds *in_creds, 91 krb5_creds *krbtgt, 92 krb5_principal impersonate_principal, 93 Ticket *second_ticket, 94 const char *kdc_hostname, 95 krb5_creds *out_creds) 96{ 97 TGS_REQ req; 98 krb5_data enc; 99 krb5_data resp; 100 krb5_kdc_rep rep; 101 KRB_ERROR error; 102 krb5_error_code ret; 103 unsigned nonce; 104 krb5_keyblock *subkey = NULL; 105 size_t len = 0; 106 Ticket second_ticket_data; 107 krb5_deltat offset; 108 METHOD_DATA padata; 109 110 krb5_data_zero(&resp); 111 krb5_data_zero(&enc); 112 padata.val = NULL; 113 padata.len = 0; 114 115 krb5_generate_random_block(&nonce, sizeof(nonce)); 116 nonce &= 0xffffffff; 117 118 if(flags.b.enc_tkt_in_skey && second_ticket == NULL){ 119 ret = decode_Ticket(in_creds->second_ticket.data, 120 in_creds->second_ticket.length, 121 &second_ticket_data, &len); 122 if(ret) 123 return ret; 124 second_ticket = &second_ticket_data; 125 } 126 127 ret = krb5_cc_get_kdc_offset(context, id, &offset); 128 if (ret == 0) { 129 context->kdc_sec_offset = (uint32_t)offset; 130 context->kdc_usec_offset = 0; 131 } 132 133 ret = _krb5_init_tgs_req (context, 134 id, 135 fast_state, 136 addresses, 137 flags, 138 impersonate_principal, 139 second_ticket, 140 in_creds, 141 krbtgt, 142 nonce, 143 &padata, 144 &subkey, 145 &req); 146 if (ret) 147 goto out; 148 149 ASN1_MALLOC_ENCODE(TGS_REQ, enc.data, enc.length, &req, &len, ret); 150 if (ret) 151 goto out; 152 if(enc.length != len) 153 krb5_abortx(context, "internal error in ASN.1 encoder"); 154 155 /* don't free addresses */ 156 req.req_body.addresses = NULL; 157 free_TGS_REQ(&req); 158 159 /* 160 * Send and receive 161 */ 162 { 163 krb5_sendto_ctx stctx; 164 ret = krb5_sendto_ctx_alloc(context, &stctx); 165 if (ret) 166 return ret; 167 krb5_sendto_ctx_set_func(stctx, _krb5_kdc_retry, NULL); 168 169 if (kdc_hostname) 170 krb5_sendto_set_hostname(context, stctx, kdc_hostname); 171 172 ret = krb5_sendto_context (context, stctx, &enc, 173 krbtgt->server->name.name_string.val[1], 174 &resp); 175 krb5_sendto_ctx_free(context, stctx); 176 } 177 if(ret) 178 goto out; 179 180 memset(&rep, 0, sizeof(rep)); 181 if(decode_TGS_REP(resp.data, resp.length, &rep.kdc_rep, &len) == 0) { 182 struct krb5_decrypt_tkt_with_subkey_state state; 183 unsigned eflags = 0; 184 185 ret = _krb5_fast_unwrap_kdc_rep(context, nonce, NULL, 186 fast_state, &rep.kdc_rep); 187 if (ret) 188 goto out2; 189 190 ret = krb5_copy_principal(context, 191 in_creds->client, 192 &out_creds->client); 193 if(ret) 194 goto out2; 195 ret = krb5_copy_principal(context, 196 in_creds->server, 197 &out_creds->server); 198 if(ret) 199 goto out2; 200 /* this should go someplace else */ 201 out_creds->times.endtime = in_creds->times.endtime; 202 203#define constrained_delegation request_anonymous 204 205 /* XXX should do better testing */ 206 if (flags.b.constrained_delegation || impersonate_principal) 207 eflags |= EXTRACT_TICKET_ALLOW_CNAME_MISMATCH; 208 209 state.subkey = subkey; 210 state.fast_state = fast_state; 211 212 ret = _krb5_extract_ticket(context, 213 &rep, 214 out_creds, 215 &krbtgt->session, 216 0, 217 &krbtgt->addresses, 218 nonce, 219 eflags, 220 NULL, 221 _krb5_decrypt_tkt_with_subkey, 222 &state); 223 224 out2: 225 krb5_free_kdc_rep(context, &rep); 226 } else if(krb5_rd_error(context, &resp, &error) == 0) { 227 ret = krb5_error_from_rd_error(context, &error, in_creds); 228 krb5_free_error_contents(context, &error); 229 } else if(resp.length > 0 && ((char*)resp.data)[0] == 4) { 230 ret = KRB5KRB_AP_ERR_V4_REPLY; 231 krb5_clear_error_message(context); 232 } else { 233 ret = KRB5KRB_AP_ERR_MSG_TYPE; 234 krb5_clear_error_message(context); 235 } 236 237out: 238 if (second_ticket == &second_ticket_data) 239 free_Ticket(&second_ticket_data); 240 free_METHOD_DATA(&padata); 241 krb5_data_free(&resp); 242 krb5_data_free(&enc); 243 if(subkey) 244 krb5_free_keyblock(context, subkey); 245 return ret; 246 247} 248 249/* 250 * same as above, just get local addresses first if the krbtgt have 251 * them and the realm is not addressless 252 */ 253 254static krb5_error_code 255get_cred_kdc_address(krb5_context context, 256 krb5_ccache id, 257 struct krb5_fast_state *fast_state, 258 krb5_kdc_flags flags, 259 krb5_addresses *addrs, 260 krb5_creds *in_creds, 261 krb5_creds *krbtgt, 262 krb5_principal impersonate_principal, 263 Ticket *second_ticket, 264 const char *kdc_hostname, 265 krb5_creds *out_creds) 266{ 267 krb5_error_code ret; 268 krb5_addresses addresses = { 0, NULL }; 269 270 /* 271 * Inherit the address-ness of the krbtgt if the address is not 272 * specified. 273 */ 274 275 if (addrs == NULL && krbtgt->addresses.len != 0) { 276 krb5_boolean noaddr; 277 278 krb5_appdefault_boolean(context, NULL, krbtgt->server->realm, 279 "no-addresses", FALSE, &noaddr); 280 281 if (!noaddr) { 282 krb5_get_all_client_addrs(context, &addresses); 283 /* XXX this sucks. */ 284 addrs = &addresses; 285 if(addresses.len == 0) 286 addrs = NULL; 287 } 288 } 289 ret = get_cred_kdc(context, id, fast_state, flags, addrs, in_creds, 290 krbtgt, impersonate_principal, 291 second_ticket, kdc_hostname, out_creds); 292 krb5_free_addresses(context, &addresses); 293 return ret; 294} 295 296KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 297krb5_get_kdc_cred(krb5_context context, 298 krb5_ccache id, 299 krb5_kdc_flags flags, 300 krb5_addresses *addresses, 301 Ticket *second_ticket, 302 krb5_creds *in_creds, 303 krb5_creds **out_creds 304 ) 305{ 306 krb5_error_code ret; 307 krb5_creds *krbtgt; 308 309 *out_creds = calloc(1, sizeof(**out_creds)); 310 if(*out_creds == NULL) { 311 krb5_set_error_message(context, ENOMEM, 312 N_("malloc: out of memory", "")); 313 return ENOMEM; 314 } 315 ret = _krb5_get_krbtgt (context, 316 id, 317 in_creds->server->realm, 318 &krbtgt); 319 if(ret) { 320 free(*out_creds); 321 *out_creds = NULL; 322 return ret; 323 } 324 ret = get_cred_kdc(context, id, NULL, flags, addresses, 325 in_creds, krbtgt, NULL, NULL, NULL, *out_creds); 326 krb5_free_creds (context, krbtgt); 327 if(ret) { 328 free(*out_creds); 329 *out_creds = NULL; 330 } 331 return ret; 332} 333 334static int 335not_found(krb5_context context, krb5_const_principal p, krb5_error_code code) 336{ 337 krb5_error_code ret; 338 char *str; 339 340 ret = krb5_unparse_name(context, p, &str); 341 if(ret) { 342 krb5_clear_error_message(context); 343 return code; 344 } 345 krb5_set_error_message(context, code, 346 N_("Matching credential (%s) not found", ""), str); 347 free(str); 348 return code; 349} 350 351static krb5_error_code 352find_cred(krb5_context context, 353 krb5_ccache id, 354 krb5_principal server, 355 krb5_creds **tgts, 356 krb5_creds *out_creds) 357{ 358 krb5_error_code ret; 359 krb5_creds mcreds; 360 361 krb5_cc_clear_mcred(&mcreds); 362 mcreds.server = server; 363 ret = krb5_cc_retrieve_cred(context, id, KRB5_TC_DONT_MATCH_REALM, 364 &mcreds, out_creds); 365 if(ret == 0) 366 return 0; 367 while(tgts && *tgts){ 368 if(krb5_compare_creds(context, KRB5_TC_DONT_MATCH_REALM, 369 &mcreds, *tgts)){ 370 ret = krb5_copy_creds_contents(context, *tgts, out_creds); 371 return ret; 372 } 373 tgts++; 374 } 375 return not_found(context, server, KRB5_CC_NOTFOUND); 376} 377 378static krb5_error_code 379add_cred(krb5_context context, krb5_creds const *tkt, krb5_creds ***tgts) 380{ 381 int i; 382 krb5_error_code ret; 383 krb5_creds **tmp = *tgts; 384 385 for(i = 0; tmp && tmp[i]; i++); /* XXX */ 386 tmp = realloc(tmp, (i+2)*sizeof(*tmp)); 387 if(tmp == NULL) { 388 krb5_set_error_message(context, ENOMEM, 389 N_("malloc: out of memory", "")); 390 return ENOMEM; 391 } 392 *tgts = tmp; 393 ret = krb5_copy_creds(context, tkt, &tmp[i]); 394 tmp[i+1] = NULL; 395 return ret; 396} 397 398static krb5_error_code 399get_cred_kdc_capath_worker(krb5_context context, 400 krb5_kdc_flags flags, 401 krb5_ccache ccache, 402 struct krb5_fast_state *fast_state, 403 krb5_creds *in_creds, 404 krb5_const_realm try_realm, 405 krb5_principal impersonate_principal, 406 Ticket *second_ticket, 407 const char *kdc_hostname, 408 krb5_creds **out_creds, 409 krb5_creds ***ret_tgts) 410{ 411 krb5_error_code ret; 412 krb5_creds *tgt, tmp_creds; 413 krb5_const_realm client_realm, server_realm; 414 int ok_as_delegate = 1; 415 416 *out_creds = NULL; 417 418 client_realm = krb5_principal_get_realm(context, in_creds->client); 419 server_realm = krb5_principal_get_realm(context, in_creds->server); 420 memset(&tmp_creds, 0, sizeof(tmp_creds)); 421 ret = krb5_copy_principal(context, in_creds->client, &tmp_creds.client); 422 if(ret) 423 return ret; 424 425 ret = krb5_make_principal(context, 426 &tmp_creds.server, 427 try_realm, 428 KRB5_TGS_NAME, 429 server_realm, 430 NULL); 431 if(ret){ 432 krb5_free_principal(context, tmp_creds.client); 433 return ret; 434 } 435 { 436 krb5_creds tgts; 437 438 ret = find_cred(context, ccache, tmp_creds.server, 439 *ret_tgts, &tgts); 440 if(ret == 0){ 441 /* only allow implicit ok_as_delegate if the realm is the clients realm */ 442 if (strcmp(try_realm, client_realm) != 0 || strcmp(try_realm, server_realm) != 0) 443 ok_as_delegate = tgts.flags.b.ok_as_delegate; 444 445 *out_creds = calloc(1, sizeof(**out_creds)); 446 if(*out_creds == NULL) { 447 ret = ENOMEM; 448 krb5_set_error_message(context, ret, 449 N_("malloc: out of memory", "")); 450 } else { 451 ret = get_cred_kdc_address(context, ccache, fast_state, 452 flags, NULL, 453 in_creds, &tgts, 454 impersonate_principal, 455 second_ticket, 456 kdc_hostname, 457 *out_creds); 458 if (ret) { 459 free (*out_creds); 460 *out_creds = NULL; 461 } else if (krb5_principal_compare_any_realm(context, (*out_creds)->server, in_creds->server) != TRUE) { 462 krb5_free_creds(context, *out_creds); 463 *out_creds = NULL; 464 ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; 465 } else if (ok_as_delegate == 0) 466 (*out_creds)->flags.b.ok_as_delegate = 0; 467 468 } 469 krb5_free_cred_contents(context, &tgts); 470 krb5_free_principal(context, tmp_creds.server); 471 krb5_free_principal(context, tmp_creds.client); 472 return ret; 473 } 474 } 475 if(krb5_realm_compare(context, in_creds->client, in_creds->server)) 476 return not_found(context, in_creds->server, KRB5_CC_NOTFOUND); 477 478 /* XXX this can loop forever */ 479 while(1){ 480 heim_general_string tgt_inst; 481 482 ret = get_cred_kdc_capath(context, flags, ccache, fast_state, &tmp_creds, 483 NULL, NULL, kdc_hostname, &tgt, ret_tgts); 484 if(ret) { 485 krb5_free_principal(context, tmp_creds.server); 486 krb5_free_principal(context, tmp_creds.client); 487 return ret; 488 } 489 /* 490 * if either of the chain or the ok_as_delegate was stripped 491 * by the kdc, make sure we strip it too. 492 */ 493 if (ok_as_delegate == 0 || tgt->flags.b.ok_as_delegate == 0) { 494 ok_as_delegate = 0; 495 tgt->flags.b.ok_as_delegate = 0; 496 } 497 498 ret = add_cred(context, tgt, ret_tgts); 499 if(ret) { 500 krb5_free_principal(context, tmp_creds.server); 501 krb5_free_principal(context, tmp_creds.client); 502 return ret; 503 } 504 tgt_inst = tgt->server->name.name_string.val[1]; 505 if(strcmp(tgt_inst, server_realm) == 0) 506 break; 507 krb5_free_principal(context, tmp_creds.server); 508 ret = krb5_make_principal(context, &tmp_creds.server, 509 tgt_inst, KRB5_TGS_NAME, server_realm, NULL); 510 if(ret) { 511 krb5_free_principal(context, tmp_creds.server); 512 krb5_free_principal(context, tmp_creds.client); 513 return ret; 514 } 515 ret = krb5_free_creds(context, tgt); 516 if(ret) { 517 krb5_free_principal(context, tmp_creds.server); 518 krb5_free_principal(context, tmp_creds.client); 519 return ret; 520 } 521 } 522 523 krb5_free_principal(context, tmp_creds.server); 524 krb5_free_principal(context, tmp_creds.client); 525 *out_creds = calloc(1, sizeof(**out_creds)); 526 if(*out_creds == NULL) { 527 ret = ENOMEM; 528 krb5_set_error_message(context, ret, N_("malloc: out of memory", "")); 529 } else { 530 ret = get_cred_kdc_address (context, ccache, fast_state, flags, NULL, 531 in_creds, tgt, impersonate_principal, 532 second_ticket, kdc_hostname, *out_creds); 533 if (ret) { 534 free (*out_creds); 535 *out_creds = NULL; 536 } else if (krb5_principal_compare_any_realm(context, (*out_creds)->server, in_creds->server) != TRUE) { 537 krb5_free_creds(context, *out_creds); 538 *out_creds = NULL; 539 ret = KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN; 540 } 541 } 542 krb5_free_creds(context, tgt); 543 return ret; 544} 545 546/* 547get_cred(server) 548 creds = cc_get_cred(server) 549 if(creds) return creds 550 tgt = cc_get_cred(krbtgt/server_realm@any_realm) 551 if(tgt) 552 return get_cred_tgt(server, tgt) 553 if(client_realm == server_realm) 554 return NULL 555 tgt = get_cred(krbtgt/server_realm@client_realm) 556 while(tgt_inst != server_realm) 557 tgt = get_cred(krbtgt/server_realm@tgt_inst) 558 return get_cred_tgt(server, tgt) 559 */ 560 561static krb5_error_code 562get_cred_kdc_capath(krb5_context context, 563 krb5_kdc_flags flags, 564 krb5_ccache ccache, 565 struct krb5_fast_state *fast_state, 566 krb5_creds *in_creds, 567 krb5_principal impersonate_principal, 568 Ticket *second_ticket, 569 const char *kdc_hostname, 570 krb5_creds **out_creds, 571 krb5_creds ***ret_tgts) 572{ 573 krb5_error_code ret; 574 krb5_const_realm client_realm, server_realm, try_realm; 575 576 client_realm = krb5_principal_get_realm(context, in_creds->client); 577 server_realm = krb5_principal_get_realm(context, in_creds->server); 578 579 try_realm = client_realm; 580 ret = get_cred_kdc_capath_worker(context, flags, ccache, fast_state, in_creds, try_realm, 581 impersonate_principal, second_ticket, kdc_hostname, 582 out_creds, ret_tgts); 583 584 if (ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) { 585 try_realm = krb5_config_get_string(context, NULL, "capaths", 586 client_realm, server_realm, NULL); 587 588 if (try_realm != NULL && strcmp(try_realm, client_realm)) { 589 ret = get_cred_kdc_capath_worker(context, flags, ccache, fast_state, in_creds, 590 try_realm, impersonate_principal, 591 second_ticket, kdc_hostname, 592 out_creds, ret_tgts); 593 } 594 } 595 596 return ret; 597} 598 599static krb5_error_code 600get_cred_kdc_referral(krb5_context context, 601 krb5_kdc_flags flags, 602 krb5_ccache ccache, 603 struct krb5_fast_state *fast_state, 604 krb5_creds *in_creds, 605 krb5_principal impersonate_principal, 606 Ticket *second_ticket, 607 const char *kdc_hostname, 608 krb5_creds **out_creds, 609 krb5_creds ***ret_tgts) 610{ 611 krb5_const_realm client_realm; 612 krb5_const_principal server = in_creds->server; 613 krb5_error_code ret; 614 krb5_creds tgt, referral, ticket; 615 int loop = 0; 616 int ok_as_delegate = 1; 617 618 if (server->name.name_string.len < 2 && !flags.b.canonicalize) { 619 krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED, 620 N_("Name too short to do referals, skipping referals", "")); 621 return KRB5KDC_ERR_PATH_NOT_ACCEPTED; 622 } 623 /* XXX 9268316, make referrals checking saner */ 624 if (server->name.name_string.len > 0 && strcmp(server->name.name_string.val[0], "kadmin") == 0) { 625 krb5_set_error_message(context, KRB5KDC_ERR_PATH_NOT_ACCEPTED, 626 N_("Name[0] is kadmin, skipping referrals", "")); 627 return KRB5KDC_ERR_PATH_NOT_ACCEPTED; 628 } 629 630 memset(&tgt, 0, sizeof(tgt)); 631 memset(&ticket, 0, sizeof(ticket)); 632 633 flags.b.canonicalize = 1; 634 635 *out_creds = NULL; 636 637 client_realm = krb5_principal_get_realm(context, in_creds->client); 638 639 /* find tgt for the clients base realm */ 640 { 641 krb5_principal tgtname; 642 643 ret = krb5_make_principal(context, &tgtname, 644 client_realm, 645 KRB5_TGS_NAME, 646 client_realm, 647 NULL); 648 if(ret) 649 return ret; 650 651 ret = find_cred(context, ccache, tgtname, *ret_tgts, &tgt); 652 krb5_free_principal(context, tgtname); 653 if (ret) 654 return ret; 655 } 656 657 referral = *in_creds; 658 ret = krb5_copy_principal(context, server, &referral.server); 659 if (ret) { 660 krb5_free_cred_contents(context, &tgt); 661 return ret; 662 } 663 ret = krb5_principal_set_realm(context, referral.server, client_realm); 664 if (ret) { 665 krb5_free_cred_contents(context, &tgt); 666 krb5_free_principal(context, referral.server); 667 return ret; 668 } 669 670 while (loop++ < 17) { 671 krb5_creds **tickets; 672 krb5_creds mcreds; 673 char *referral_realm; 674 675 /* Use cache if we are not doing impersonation or contrainte deleg */ 676 if (impersonate_principal == NULL || flags.b.constrained_delegation) { 677 krb5_cc_clear_mcred(&mcreds); 678 mcreds.server = referral.server; 679 ret = krb5_cc_retrieve_cred(context, ccache, 0, &mcreds, &ticket); 680 } else 681 ret = EINVAL; 682 683 if (ret) { 684 ret = get_cred_kdc_address(context, ccache, fast_state, flags, NULL, 685 &referral, &tgt, impersonate_principal, 686 second_ticket, kdc_hostname, &ticket); 687 if (ret) 688 goto out; 689 } 690 691 /* Did we get the right ticket ? */ 692 if (krb5_principal_compare_any_realm(context, 693 referral.server, 694 ticket.server)) 695 break; 696 697 if (!krb5_principal_is_krbtgt(context, ticket.server)) { 698 krb5_set_error_message(context, KRB5KRB_AP_ERR_NOT_US, 699 N_("Got back an non krbtgt " 700 "ticket referrals", "")); 701 ret = KRB5KRB_AP_ERR_NOT_US; 702 goto out; 703 } 704 705 referral_realm = ticket.server->name.name_string.val[1]; 706 707 /* check that there are no referrals loops */ 708 tickets = *ret_tgts; 709 710 krb5_cc_clear_mcred(&mcreds); 711 mcreds.server = ticket.server; 712 713 while(tickets && *tickets){ 714 if(krb5_compare_creds(context, 715 KRB5_TC_DONT_MATCH_REALM, 716 &mcreds, 717 *tickets)) 718 { 719 krb5_set_error_message(context, KRB5_GET_IN_TKT_LOOP, 720 N_("Referral from %s " 721 "loops back to realm %s", ""), 722 tgt.server->realm, 723 referral_realm); 724 ret = KRB5_GET_IN_TKT_LOOP; 725 goto out; 726 } 727 tickets++; 728 } 729 730 /* 731 * if either of the chain or the ok_as_delegate was stripped 732 * by the kdc, make sure we strip it too. 733 */ 734 735 if (ok_as_delegate == 0 || ticket.flags.b.ok_as_delegate == 0) { 736 ok_as_delegate = 0; 737 ticket.flags.b.ok_as_delegate = 0; 738 } 739 740 ret = add_cred(context, &ticket, ret_tgts); 741 if (ret) 742 goto out; 743 744 /* try realm in the referral */ 745 ret = krb5_principal_set_realm(context, 746 referral.server, 747 referral_realm); 748 krb5_free_cred_contents(context, &tgt); 749 tgt = ticket; 750 memset(&ticket, 0, sizeof(ticket)); 751 if (ret) 752 goto out; 753 } 754 755 ret = krb5_copy_creds(context, &ticket, out_creds); 756 757out: 758 krb5_free_principal(context, referral.server); 759 krb5_free_cred_contents(context, &tgt); 760 krb5_free_cred_contents(context, &ticket); 761 return ret; 762} 763 764 765/* 766 * Glue function between referrals version and old client chasing 767 * codebase. 768 */ 769 770krb5_error_code 771_krb5_get_cred_kdc_any(krb5_context context, 772 krb5_kdc_flags flags, 773 krb5_ccache ccache, 774 struct krb5_fast_state *fast_state, 775 krb5_creds *in_creds, 776 krb5_principal impersonate_principal, 777 Ticket *second_ticket, 778 krb5_creds **out_creds, 779 krb5_creds ***ret_tgts) 780{ 781 char *kdc_hostname = NULL; 782 krb5_error_code ret; 783 krb5_deltat offset; 784 krb5_data data; 785 786 /* 787 * If we are using LKDC, lets pull out the addreses from the 788 * ticket and use that. 789 */ 790 791 ret = krb5_cc_get_config(context, ccache, NULL, "lkdc-hostname", &data); 792 if (ret == 0) { 793 kdc_hostname = malloc(data.length + 1); 794 if (kdc_hostname == NULL) { 795 krb5_set_error_message(context, ENOMEM, 796 N_("malloc: out of memory", "")); 797 return ENOMEM; 798 } 799 memcpy(kdc_hostname, data.data, data.length); 800 kdc_hostname[data.length] = '\0'; 801 krb5_data_free(&data); 802 } 803 804 ret = krb5_cc_get_kdc_offset(context, ccache, &offset); 805 if (ret == 0) { 806 context->kdc_sec_offset = (uint32_t)offset; 807 context->kdc_usec_offset = 0; 808 } 809 810 ret = get_cred_kdc_referral(context, 811 flags, 812 ccache, 813 fast_state, 814 in_creds, 815 impersonate_principal, 816 second_ticket, 817 kdc_hostname, 818 out_creds, 819 ret_tgts); 820 if (ret == 0 || flags.b.canonicalize) { 821 free(kdc_hostname); 822 return ret; 823 } 824 ret = get_cred_kdc_capath(context, 825 flags, 826 ccache, 827 fast_state, 828 in_creds, 829 impersonate_principal, 830 second_ticket, 831 kdc_hostname, 832 out_creds, 833 ret_tgts); 834 free(kdc_hostname); 835 return ret; 836} 837 838/* 839 * Store all credentials that seems not to be _the_ krbtgt 840 * credential. 841 */ 842 843static void 844store_tgts(krb5_context context, krb5_ccache ccache, krb5_creds **tgts) 845{ 846 size_t n; 847 848 for (n = 0; tgts && tgts[n]; n++) { 849 krb5_const_principal server = tgts[n]->server; 850 851 if (krb5_principal_is_krbtgt(context, server) && strcmp(server->name.name_string.val[1], server->realm) != 0) 852 krb5_cc_store_cred(context, ccache, tgts[n]); 853 } 854 for (n = 0; tgts && tgts[n]; n++) 855 krb5_free_creds(context, tgts[n]); 856} 857 858/** 859 * Get credentials specified by in_cred->server and flags 860 * 861 * @param context A kerberos 5 context. 862 * @param options KRB5_TC_* options 863 * @param flags KDC option flags 864 * @param ccache credential cache to use. 865 * @param in_creds input matching credential. 866 * @param out_creds the resulting credential. 867 * 868 * @return Return an error code or 0. 869 * 870 * @ingroup krb5_credential 871 */ 872 873KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 874krb5_get_credentials_with_flags(krb5_context context, 875 krb5_flags options, 876 krb5_kdc_flags flags, 877 krb5_ccache ccache, 878 krb5_creds *in_creds, 879 krb5_creds **out_creds) 880{ 881 struct krb5_fast_state *fast_state = NULL; 882 struct timeval start_time, stop_time; 883 krb5_error_code ret; 884 krb5_creds **tgts; 885 krb5_creds *res_creds; 886 887 888 gettimeofday(&start_time, NULL); 889 890 if (in_creds->session.keytype) { 891 ret = krb5_enctype_valid(context, in_creds->session.keytype); 892 if (ret) 893 return ret; 894 } 895 896 *out_creds = NULL; 897 res_creds = calloc(1, sizeof(*res_creds)); 898 if (res_creds == NULL) { 899 krb5_set_error_message(context, ENOMEM, 900 N_("malloc: out of memory", "")); 901 return ENOMEM; 902 } 903 904 if (in_creds->session.keytype) 905 options |= KRB5_TC_MATCH_KEYTYPE; 906 907 /* 908 * If we got a credential, check if credential is expired before 909 * returning it. Also check w/o realm since we might have a 910 * referrals lookup. 911 */ 912 ret = krb5_cc_retrieve_cred(context, 913 ccache, 914 options, 915 in_creds, res_creds); 916 917 /* 918 * If we got a credential, check if credential is expired before 919 * returning it, but only if KRB5_GC_EXPIRED_OK is not set. 920 */ 921 if (ret == 0) { 922 krb5_timestamp timeret; 923 924 /* If expired ok, don't bother checking */ 925 if(options & KRB5_GC_EXPIRED_OK) { 926 *out_creds = res_creds; 927 return 0; 928 } 929 930 krb5_timeofday(context, &timeret); 931 if(res_creds->times.endtime > timeret) { 932 *out_creds = res_creds; 933 return 0; 934 } 935 if(options & KRB5_GC_CACHED) 936 krb5_cc_remove_cred(context, ccache, 0, res_creds); 937 938 krb5_free_cred_contents(context, res_creds); 939 940 } else if(ret != KRB5_CC_NOTFOUND) { 941 free(res_creds); 942 return ret; 943 } 944 free(res_creds); 945 if(options & KRB5_GC_CACHED) 946 return not_found(context, in_creds->server, KRB5_CC_NOTFOUND); 947 948 /* if we don't use keytype, lets use negative cache */ 949 if ((options & KRB5_TC_MATCH_KEYTYPE) == 0 && context->tgs_negative_timeout) { 950 krb5_data neg; 951 952 ret = krb5_cc_get_config(context, ccache, in_creds->server, "negative-cache", &neg); 953 if (ret == 0) { 954 uint32_t t32 = (uint32_t)time(NULL); /* if entry less then 4, its a negativ entry */ 955 int32_t rv = KRB5_CC_NOTFOUND; /* if no error code, assume one */ 956 krb5_storage *sp; 957 char *estr = NULL; 958 959 sp = krb5_storage_from_data(&neg); 960 if (sp == NULL) { 961 krb5_data_free(&neg); 962 return ENOMEM; 963 } 964 965 ret = krb5_ret_uint32(sp, &t32); 966 if (ret == 0) 967 ret = krb5_ret_int32(sp, &rv); 968 if (ret == 0) 969 ret = krb5_ret_string(sp, &estr); 970 971 krb5_storage_free(sp); 972 krb5_data_free(&neg); 973 if (ret) { 974 free(estr); 975 return ret; 976 } 977 if (abs((uint32_t)time(NULL) - t32) < context->tgs_negative_timeout) { /* negative entry not expired, fail */ 978 char *str = NULL; 979 ret = rv; 980 krb5_unparse_name(context, in_creds->server, &str); 981 krb5_set_error_message(context, ret, 982 "%s while looking up '%s' (cached result, timeout in %ld sec)", 983 estr ? estr : "<no cached error string>", 984 str ? str : "unknown", 985 context->tgs_negative_timeout - abs((int)(t32 - time(NULL)))); 986 free(estr); 987 free(str); 988 return ret; 989 } 990 free(estr); 991 } 992 } 993 994 if(options & KRB5_GC_USER_USER) 995 flags.b.enc_tkt_in_skey = 1; 996 if (flags.b.enc_tkt_in_skey) 997 options |= KRB5_GC_NO_STORE; 998 999 tgts = NULL; 1000 ret = _krb5_get_cred_kdc_any(context, flags, ccache, fast_state, 1001 in_creds, NULL, NULL, out_creds, &tgts); 1002 if (tgts) { 1003 store_tgts(context, ccache, tgts); 1004 free(tgts); 1005 } 1006 if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) { 1007 krb5_cc_store_cred(context, ccache, *out_creds); 1008 1009 /* 1010 * Store an referrals entry since the server changed from that 1011 * expected and if we want to find it again next time, it 1012 * better have the right name. 1013 * 1014 * We only need to compare any realm since the referrals 1015 * matching code will do the same for us. 1016 */ 1017 if (krb5_principal_compare_any_realm(context, (*out_creds)->server, in_creds->server) == FALSE) { 1018 krb5_creds ref = **out_creds; 1019 krb5_principal_data refp = *in_creds->server; 1020 refp.realm = ""; 1021 ref.server = &refp; 1022 krb5_cc_store_cred(context, ccache, &ref); 1023 } 1024 } 1025 1026 if ((options & KRB5_TC_MATCH_KEYTYPE) == 0 && 1027 ((ret == KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN) || 1028 (ret == KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN))) 1029 { 1030 krb5_storage *sp = krb5_storage_emem(); 1031 krb5_data neg; 1032 1033 if (sp == NULL) 1034 goto out; 1035 1036 (void)krb5_store_uint32(sp, (uint32_t)time(NULL)); 1037 (void)krb5_store_int32(sp, ret); 1038 if (context->error_code == ret && context->error_string) 1039 (void)krb5_store_string(sp, context->error_string); 1040 1041 if (krb5_storage_to_data(sp, &neg) == 0) { 1042 (void)krb5_cc_set_config(context, ccache, in_creds->server, "negative-cache", &neg); 1043 krb5_data_free(&neg); 1044 } 1045 krb5_storage_free(sp); 1046 } 1047out: 1048 gettimeofday(&stop_time, NULL); 1049 timevalsub(&stop_time, &start_time); 1050 _krb5_debugx(context, 1, "krb5_get_credentials_with_flags: %s wc: %ld.%06d", 1051 in_creds->client->realm, 1052 stop_time.tv_sec, stop_time.tv_usec); 1053 1054 return ret; 1055} 1056 1057KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1058krb5_get_credentials(krb5_context context, 1059 krb5_flags options, 1060 krb5_ccache ccache, 1061 krb5_creds *in_creds, 1062 krb5_creds **out_creds) 1063{ 1064 krb5_kdc_flags flags; 1065 flags.i = 0; 1066 return krb5_get_credentials_with_flags(context, options, flags, 1067 ccache, in_creds, out_creds); 1068} 1069 1070struct krb5_get_creds_opt_data { 1071 krb5_principal self; 1072 krb5_flags options; 1073 krb5_enctype enctype; 1074 Ticket *ticket; 1075}; 1076 1077 1078KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1079krb5_get_creds_opt_alloc(krb5_context context, krb5_get_creds_opt *opt) 1080{ 1081 *opt = calloc(1, sizeof(**opt)); 1082 if (*opt == NULL) { 1083 krb5_set_error_message(context, ENOMEM, 1084 N_("malloc: out of memory", "")); 1085 return ENOMEM; 1086 } 1087 return 0; 1088} 1089 1090KRB5_LIB_FUNCTION void KRB5_LIB_CALL 1091krb5_get_creds_opt_free(krb5_context context, krb5_get_creds_opt opt) 1092{ 1093 if (opt->self) 1094 krb5_free_principal(context, opt->self); 1095 if (opt->ticket) { 1096 free_Ticket(opt->ticket); 1097 free(opt->ticket); 1098 } 1099 memset(opt, 0, sizeof(*opt)); 1100 free(opt); 1101} 1102 1103KRB5_LIB_FUNCTION void KRB5_LIB_CALL 1104krb5_get_creds_opt_set_options(krb5_context context, 1105 krb5_get_creds_opt opt, 1106 krb5_flags options) 1107{ 1108 opt->options = options; 1109} 1110 1111KRB5_LIB_FUNCTION void KRB5_LIB_CALL 1112krb5_get_creds_opt_add_options(krb5_context context, 1113 krb5_get_creds_opt opt, 1114 krb5_flags options) 1115{ 1116 opt->options |= options; 1117} 1118 1119KRB5_LIB_FUNCTION void KRB5_LIB_CALL 1120krb5_get_creds_opt_set_enctype(krb5_context context, 1121 krb5_get_creds_opt opt, 1122 krb5_enctype enctype) 1123{ 1124 opt->enctype = enctype; 1125} 1126 1127KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1128krb5_get_creds_opt_set_impersonate(krb5_context context, 1129 krb5_get_creds_opt opt, 1130 krb5_const_principal self) 1131{ 1132 if (opt->self) 1133 krb5_free_principal(context, opt->self); 1134 return krb5_copy_principal(context, self, &opt->self); 1135} 1136 1137KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1138krb5_get_creds_opt_set_ticket(krb5_context context, 1139 krb5_get_creds_opt opt, 1140 const Ticket *ticket) 1141{ 1142 if (opt->ticket) { 1143 free_Ticket(opt->ticket); 1144 free(opt->ticket); 1145 opt->ticket = NULL; 1146 } 1147 if (ticket) { 1148 krb5_error_code ret; 1149 1150 opt->ticket = malloc(sizeof(*ticket)); 1151 if (opt->ticket == NULL) { 1152 krb5_set_error_message(context, ENOMEM, 1153 N_("malloc: out of memory", "")); 1154 return ENOMEM; 1155 } 1156 ret = copy_Ticket(ticket, opt->ticket); 1157 if (ret) { 1158 free(opt->ticket); 1159 opt->ticket = NULL; 1160 krb5_set_error_message(context, ret, 1161 N_("malloc: out of memory", "")); 1162 return ret; 1163 } 1164 } 1165 return 0; 1166} 1167 1168 1169 1170KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1171krb5_get_creds(krb5_context context, 1172 krb5_get_creds_opt opt, 1173 krb5_ccache ccache, 1174 krb5_const_principal inprinc, 1175 krb5_creds **out_creds) 1176{ 1177 struct krb5_fast_state fast_state; 1178 krb5_kdc_flags flags; 1179 krb5_flags options; 1180 krb5_creds in_creds; 1181 krb5_error_code ret; 1182 krb5_creds **tgts; 1183 krb5_creds *res_creds; 1184 1185 memset(&fast_state, 0, sizeof(fast_state)); 1186 1187 if (opt && opt->enctype) { 1188 ret = krb5_enctype_valid(context, opt->enctype); 1189 if (ret) 1190 return ret; 1191 } 1192 1193 memset(&in_creds, 0, sizeof(in_creds)); 1194 in_creds.server = rk_UNCONST(inprinc); 1195 1196 if (_krb5_have_debug(context, 5)) { 1197 char *princ; 1198 ret = krb5_unparse_name(context, inprinc, &princ); 1199 if (ret == 0) { 1200 _krb5_debugx(context, 5, "krb5_get_creds: %s: opt: %d", princ, opt ? opt->options : 0); 1201 krb5_xfree(princ); 1202 } 1203 } 1204 1205 ret = krb5_cc_get_principal(context, ccache, &in_creds.client); 1206 if (ret) 1207 return ret; 1208 1209 if (opt) 1210 options = opt->options; 1211 else 1212 options = 0; 1213 flags.i = 0; 1214 1215 *out_creds = NULL; 1216 res_creds = calloc(1, sizeof(*res_creds)); 1217 if (res_creds == NULL) { 1218 krb5_free_principal(context, in_creds.client); 1219 krb5_set_error_message(context, ENOMEM, 1220 N_("malloc: out of memory", "")); 1221 return ENOMEM; 1222 } 1223 1224 if (opt && opt->enctype) { 1225 in_creds.session.keytype = opt->enctype; 1226 options |= KRB5_TC_MATCH_KEYTYPE; 1227 } 1228 1229 /* 1230 * If we got a credential, check if credential is expired before 1231 * returning it. 1232 */ 1233 ret = krb5_cc_retrieve_cred(context, 1234 ccache, 1235 options & KRB5_TC_MATCH_KEYTYPE, 1236 &in_creds, res_creds); 1237 /* 1238 * If we got a credential, check if credential is expired before 1239 * returning it, but only if KRB5_GC_EXPIRED_OK is not set. 1240 */ 1241 if (ret == 0) { 1242 krb5_timestamp timeret; 1243 1244 /* If expired ok, don't bother checking */ 1245 if(options & KRB5_GC_EXPIRED_OK) { 1246 *out_creds = res_creds; 1247 krb5_free_principal(context, in_creds.client); 1248 goto out; 1249 } 1250 1251 krb5_timeofday(context, &timeret); 1252 if(res_creds->times.endtime > timeret) { 1253 *out_creds = res_creds; 1254 krb5_free_principal(context, in_creds.client); 1255 goto out; 1256 } 1257 if(options & KRB5_GC_CACHED) 1258 krb5_cc_remove_cred(context, ccache, 0, res_creds); 1259 1260 } else if(ret != KRB5_CC_NOTFOUND) { 1261 free(res_creds); 1262 krb5_free_principal(context, in_creds.client); 1263 goto out; 1264 } 1265 free(res_creds); 1266 if(options & KRB5_GC_CACHED) { 1267 krb5_free_principal(context, in_creds.client); 1268 ret = not_found(context, in_creds.server, KRB5_CC_NOTFOUND); 1269 goto out; 1270 } 1271 if(options & KRB5_GC_USER_USER) { 1272 flags.b.enc_tkt_in_skey = 1; 1273 options |= KRB5_GC_NO_STORE; 1274 } 1275 if (options & KRB5_GC_FORWARDABLE) 1276 flags.b.forwardable = 1; 1277 if (options & KRB5_GC_NO_TRANSIT_CHECK) 1278 flags.b.disable_transited_check = 1; 1279 if (options & KRB5_GC_CONSTRAINED_DELEGATION) { 1280 flags.b.constrained_delegation = 1; 1281 } 1282 if (options & KRB5_GC_CANONICALIZE) 1283 flags.b.canonicalize = 1; 1284 1285 tgts = NULL; 1286 ret = _krb5_get_cred_kdc_any(context, flags, ccache, &fast_state, 1287 &in_creds, opt ? opt->self : NULL, opt->ticket, 1288 out_creds, &tgts); 1289 krb5_free_principal(context, in_creds.client); 1290 if (tgts) { 1291 store_tgts(context, ccache, tgts); 1292 free(tgts); 1293 } 1294 if(ret == 0 && (options & KRB5_GC_NO_STORE) == 0) 1295 krb5_cc_store_cred(context, ccache, *out_creds); 1296 1297 out: 1298 _krb5_fast_free(context, &fast_state); 1299 _krb5_debugx(context, 5, "krb5_get_creds: ret = %d", ret); 1300 1301 return ret; 1302} 1303 1304/* 1305 * 1306 */ 1307 1308KRB5_LIB_FUNCTION krb5_error_code KRB5_LIB_CALL 1309krb5_get_renewed_creds(krb5_context context, 1310 krb5_creds *creds, 1311 krb5_const_principal client, 1312 krb5_ccache ccache, 1313 const char *in_tkt_service) 1314{ 1315 krb5_error_code ret; 1316 krb5_kdc_flags flags; 1317 krb5_creds in, *template, *out = NULL; 1318 1319 memset(&in, 0, sizeof(in)); 1320 memset(creds, 0, sizeof(*creds)); 1321 1322 ret = krb5_copy_principal(context, client, &in.client); 1323 if (ret) 1324 return ret; 1325 1326 if (in_tkt_service) { 1327 ret = krb5_parse_name(context, in_tkt_service, &in.server); 1328 if (ret) { 1329 krb5_free_principal(context, in.client); 1330 return ret; 1331 } 1332 } else { 1333 const char *realm = krb5_principal_get_realm(context, client); 1334 1335 ret = krb5_make_principal(context, &in.server, realm, KRB5_TGS_NAME, 1336 realm, NULL); 1337 if (ret) { 1338 krb5_free_principal(context, in.client); 1339 return ret; 1340 } 1341 } 1342 1343 flags.i = 0; 1344 flags.b.renewable = flags.b.renew = 1; 1345 1346 /* 1347 * Get template from old credential cache for the same entry, if 1348 * this failes, no worries. 1349 */ 1350 ret = krb5_get_credentials(context, KRB5_GC_CACHED, ccache, &in, &template); 1351 if (ret == 0) { 1352 flags.b.forwardable = template->flags.b.forwardable; 1353 flags.b.proxiable = template->flags.b.proxiable; 1354 krb5_free_creds (context, template); 1355 } 1356 1357 ret = krb5_get_kdc_cred(context, ccache, flags, NULL, NULL, &in, &out); 1358 krb5_free_principal(context, in.client); 1359 krb5_free_principal(context, in.server); 1360 if (ret) 1361 return ret; 1362 1363 ret = krb5_copy_creds_contents(context, out, creds); 1364 krb5_free_creds(context, out); 1365 1366 return ret; 1367} 1368