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