1/* 2 * Copyright (c) 2013 Kungliga Tekniska Högskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2013 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 "heimcred.h" 38 39#ifdef HAVE_XCC 40 41#define CFRELEASE_NULL(x) do { if (x) { CFRelease(x); x = NULL; } } while(0) 42 43typedef struct krb5_xcc { 44 CFUUIDRef uuid; 45 HeimCredRef cred; 46 CFStringRef clientName; 47 krb5_principal primary_principal; 48 char *cache_name; 49} krb5_xcc; 50 51struct xcc_cursor { 52 CFArrayRef array; 53 CFIndex offset; 54}; 55 56#define XCACHE(X) ((krb5_xcc *)(X)->data.data) 57 58static void 59free_cursor(struct xcc_cursor *c) 60{ 61 if (c->array) 62 CFRelease(c->array); 63 free(c); 64} 65 66#if 0 67static krb5_error_code 68make_cred(krb5_context context, 69 HeimCredRef xcred, 70 krb5_creds *cred) 71{ 72 memset(cred, 0, sizeof(*cred)); 73 return 0; 74} 75#endif 76 77static CFStringRef 78CFStringCreateFromPrincipal(krb5_context context, krb5_principal principal) 79{ 80 CFStringRef str; 81 char *p; 82 83 if (krb5_unparse_name(context, principal, &p) != 0) 84 return NULL; 85 86 str = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8); 87 krb5_xfree(p); 88 89 return str; 90} 91 92static krb5_principal 93PrincipalFromCFString(krb5_context context, CFStringRef string) 94{ 95 krb5_principal principal = NULL; 96 char *p = rk_cfstring2cstring(string); 97 if (p == NULL) 98 return NULL; 99 100 (void)krb5_parse_name(context, p, &principal); 101 free(p); 102 return principal; 103} 104 105 106static const char* KRB5_CALLCONV 107xcc_get_name(krb5_context context, 108 krb5_ccache id) 109{ 110 krb5_xcc *a = XCACHE(id); 111 return a->cache_name; 112} 113 114static krb5_error_code KRB5_CALLCONV 115xcc_alloc(krb5_context context, krb5_ccache *id) 116{ 117 (*id)->data.data = calloc(1, sizeof(krb5_xcc)); 118 (*id)->data.length = sizeof(krb5_xcc); 119 120 return 0; 121} 122 123static void 124genName(krb5_xcc *x) 125{ 126 if (x->cache_name) 127 return; 128 CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid); 129 x->cache_name = malloc(37); 130 uuid_unparse((void *)&bytes, x->cache_name); 131} 132 133static krb5_error_code KRB5_CALLCONV 134xcc_resolve(krb5_context context, krb5_ccache *id, const char *res) 135{ 136 krb5_error_code ret; 137 CFUUIDBytes bytes; 138 krb5_xcc *x; 139 140 if (uuid_parse(res, (void *)&bytes) != 0) { 141 krb5_set_error_message(context, KRB5_CC_END, "failed to parse uuid: %s", res); 142 return KRB5_CC_END; 143 } 144 145 CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes); 146 if (uuidref == NULL) { 147 krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid from: %s", res); 148 return KRB5_CC_END; 149 } 150 151 ret = xcc_alloc(context, id); 152 if (ret) { 153 CFRELEASE_NULL(uuidref); 154 return ret; 155 } 156 157 x = XCACHE((*id)); 158 159 x->uuid = uuidref; 160 genName(x); 161 162 return 0; 163} 164 165static krb5_error_code 166xcc_create(krb5_context context, krb5_xcc *x, CFUUIDRef uuid) 167{ 168 const void *keys[] = { 169 (void *)kHEIMAttrCredentialGroupLead, 170 (void *)kHEIMAttrType, 171 (void *)kHEIMAttrUUID 172 }; 173 const void *values[] = { 174 (void *)kCFBooleanTrue, 175 (void *)kHEIMTypeKerberos, 176 (void *)uuid 177 }; 178 CFDictionaryRef attrs; 179 krb5_error_code ret; 180 181 CFIndex num_keys = sizeof(keys)/sizeof(keys[0]); 182 if (uuid == NULL) 183 num_keys -= 1; 184 185 attrs = CFDictionaryCreate(NULL, keys, values, num_keys, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 186 if (attrs == NULL) abort(); 187 188 /* 189 * Contract with HeimCredCreate is that it set parent and group uuid's when they are not set on the credential 190 */ 191 x->cred = HeimCredCreate(attrs, NULL); 192 CFRelease(attrs); 193 if (x->cred) { 194 if (x->uuid) abort(); 195 x->uuid = HeimCredGetUUID(x->cred); 196 if (x->uuid == NULL) abort(); 197 CFRetain(x->uuid); 198 199 ret = 0; 200 genName(x); 201 } else 202 ret = ENOMEM; 203 204 return ret; 205} 206 207static krb5_error_code KRB5_CALLCONV 208xcc_gen_new(krb5_context context, krb5_ccache *id) 209{ 210 krb5_error_code ret; 211 krb5_xcc *x; 212 213 ret = xcc_alloc(context, id); 214 if (ret) 215 return ret; 216 217 x = XCACHE(*id); 218 219 ret = xcc_create(context, x, NULL); 220 221 return ret; 222} 223 224static krb5_error_code KRB5_CALLCONV 225xcc_initialize(krb5_context context, 226 krb5_ccache id, 227 krb5_principal primary_principal) 228{ 229 krb5_xcc *x = XCACHE(id); 230 krb5_error_code ret; 231 232 if (x->primary_principal) 233 krb5_free_principal(context, x->primary_principal); 234 235 ret = krb5_copy_principal(context, primary_principal, &x->primary_principal); 236 if (ret) 237 return ret; 238 239 CFRELEASE_NULL(x->clientName); 240 241 x->clientName = CFStringCreateFromPrincipal(context, primary_principal); 242 if (x->clientName == NULL) 243 return krb5_enomem(context); 244 245 if (x->cred == NULL) { 246 ret = xcc_create(context, x, x->uuid); 247 if (ret) 248 return ret; 249 } 250 if (!HeimCredSetAttribute(x->cred, kHEIMAttrClientName, x->clientName, NULL)) {\ 251 ret = EINVAL; 252 krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name); 253 } 254 255 256 return ret; 257} 258 259static krb5_error_code KRB5_CALLCONV 260xcc_close(krb5_context context, 261 krb5_ccache id) 262{ 263 krb5_xcc *x = XCACHE(id); 264 krb5_free_principal(context, x->primary_principal); 265 CFRELEASE_NULL(x->uuid); 266 CFRELEASE_NULL(x->cred); 267 free(x->cache_name); 268 return 0; 269} 270 271static krb5_error_code KRB5_CALLCONV 272xcc_destroy(krb5_context context, 273 krb5_ccache id) 274{ 275 krb5_xcc *x = XCACHE(id); 276 if (x->uuid) 277 HeimCredDeleteByUUID(x->uuid); 278 CFRELEASE_NULL(x->cred); 279 280 return 0; 281} 282 283static krb5_error_code KRB5_CALLCONV 284xcc_store_cred(krb5_context context, 285 krb5_ccache id, 286 krb5_creds *creds) 287{ 288 krb5_xcc *x = XCACHE(id); 289 krb5_storage *sp = NULL; 290 CFDataRef dref = NULL; 291 krb5_data data; 292 CFStringRef principal = NULL; 293 CFDictionaryRef query = NULL; 294 krb5_error_code ret; 295 296 krb5_data_zero(&data); 297 298 sp = krb5_storage_emem(); 299 if (sp == NULL) { 300 ret = krb5_enomem(context); 301 goto out; 302 } 303 304 ret = krb5_store_creds(sp, creds); 305 if (ret) 306 goto out; 307 308 krb5_storage_to_data(sp, &data); 309 310 dref = CFDataCreateWithBytesNoCopy(NULL, data.data, data.length, kCFAllocatorNull); 311 if (dref == NULL) { 312 ret = krb5_enomem(context); 313 goto out; 314 } 315 316 principal = CFStringCreateFromPrincipal(context, creds->server); 317 if (principal == NULL) { 318 ret = krb5_enomem(context); 319 goto out; 320 } 321 322 const void *add_keys[] = { 323 kHEIMAttrType, 324 kHEIMAttrClientName, 325 kHEIMAttrServerName, 326 kHEIMAttrData, 327 kHEIMAttrCredentialGroup, 328 kHEIMAttrParentCredential, 329 }; 330 const void *add_values[] = { 331 kHEIMTypeKerberos, 332 x->clientName, 333 principal, 334 dref, 335 x->uuid, 336 x->uuid, 337 }; 338 339 query = CFDictionaryCreate(NULL, add_keys, add_values, sizeof(add_keys) / sizeof(add_keys[0]), NULL, NULL); 340 heim_assert(query != NULL, "out of memory"); 341 342 HeimCredRef ccred = HeimCredCreate(query, NULL); 343 if (ccred) { 344 CFRelease(ccred); 345 } else { 346 _krb5_debugx(context, 5, "failed to add credential to %s\n", x->cache_name); 347 ret = EINVAL; 348 krb5_set_error_message(context, ret, "failed to store credential to %s", x->cache_name); 349 goto out; 350 } 351 352out: 353 if (sp) 354 krb5_storage_free(sp); 355 CFRELEASE_NULL(dref); 356 CFRELEASE_NULL(principal); 357 krb5_data_free(&data); 358 359 return ret; 360} 361 362static krb5_error_code KRB5_CALLCONV 363xcc_get_principal(krb5_context context, 364 krb5_ccache id, 365 krb5_principal *principal) 366{ 367 krb5_xcc *x = XCACHE(id); 368 if (x->cred == NULL) { 369 x->cred = HeimCredCopyFromUUID(x->uuid); 370 if (x->cred == NULL) { 371 krb5_set_error_message(context, KRB5_CC_END, "no credential for %s", x->cache_name); 372 return KRB5_CC_END; 373 } 374 } 375 if (x->clientName == NULL) { 376 x->clientName = HeimCredCopyAttribute(x->cred, kHEIMAttrClientName); 377 if (x->clientName == NULL) { 378 krb5_set_error_message(context, KRB5_CC_END, "no principal name for %s", x->cache_name); 379 return KRB5_CC_END; 380 } 381 } 382 if (x->primary_principal == NULL) { 383 x->primary_principal = PrincipalFromCFString(context, x->clientName); 384 if (x->primary_principal == NULL) { 385 krb5_set_error_message(context, KRB5_CC_END, "no principal for %s", x->cache_name); 386 return KRB5_CC_END; 387 } 388 } 389 390 return krb5_copy_principal(context, x->primary_principal, principal); 391} 392 393static krb5_error_code KRB5_CALLCONV 394xcc_get_first (krb5_context context, 395 krb5_ccache id, 396 krb5_cc_cursor *cursor) 397{ 398 CFDictionaryRef query; 399 krb5_xcc *x = XCACHE(id); 400 CFUUIDRef uuid = HeimCredGetUUID(x->cred); 401 struct xcc_cursor *c; 402 403 c = calloc(1, sizeof(*c)); 404 if (c == NULL) 405 return krb5_enomem(context); 406 407 const void *keys[] = { (void *)kHEIMAttrCredentialGroup, kHEIMAttrType }; 408 const void *values[] = { (void *)uuid, kHEIMTypeKerberos }; 409 410 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 411 if (query == NULL) abort(); 412 413 c->array = HeimCredCopyQuery(query); 414 CFRELEASE_NULL(query); 415 if (c->array == NULL) { 416 free_cursor(c); 417 return KRB5_CC_END; 418 } 419 420 *cursor = c; 421 422 return 0; 423} 424 425 426static krb5_error_code KRB5_CALLCONV 427xcc_get_next (krb5_context context, 428 krb5_ccache id, 429 krb5_cc_cursor *cursor, 430 krb5_creds *creds) 431{ 432 struct xcc_cursor *c = *cursor; 433 krb5_error_code ret; 434 krb5_storage *sp; 435 HeimCredRef cred; 436 CFDataRef data; 437 438 if (c->array == NULL) 439 return KRB5_CC_END; 440 441next: 442 if (c->offset >= CFArrayGetCount(c->array)) 443 return KRB5_CC_END; 444 445 cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++); 446 if (cred == NULL) 447 return KRB5_CC_END; 448 449 data = HeimCredCopyAttribute(cred, kHEIMAttrData); 450 if (data == NULL) 451 goto next; 452 453 sp = krb5_storage_from_readonly_mem(CFDataGetBytePtr(data), CFDataGetLength(data)); 454 if (sp == NULL) { 455 CFRELEASE_NULL(data); 456 return KRB5_CC_END; 457 } 458 459 ret = krb5_ret_creds(sp, creds); 460 krb5_storage_free(sp); 461 CFRELEASE_NULL(data); 462 463 return ret; 464} 465 466static krb5_error_code KRB5_CALLCONV 467xcc_end_get (krb5_context context, 468 krb5_ccache id, 469 krb5_cc_cursor *cursor) 470{ 471 free_cursor((struct xcc_cursor *)*cursor); 472 *cursor = NULL; 473 474 return 0; 475} 476 477static krb5_error_code KRB5_CALLCONV 478xcc_remove_cred(krb5_context context, 479 krb5_ccache id, 480 krb5_flags which, 481 krb5_creds *cred) 482{ 483 CFDictionaryRef query; 484 krb5_xcc *x = XCACHE(id); 485 486 CFStringRef servername = CFStringCreateFromPrincipal(context, cred->server); 487 if (servername == NULL) 488 return KRB5_CC_END; 489 490 const void *keys[] = { (void *)kHEIMAttrCredentialGroup, kHEIMAttrType, kHEIMAttrServerName }; 491 const void *values[] = { (void *)x->uuid, kHEIMTypeKerberos, servername }; 492 493 /* XXX match enctype */ 494 495 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 496 if (query == NULL) abort(); 497 498 CFRELEASE_NULL(servername); 499 500 bool res = HeimCredDeleteQuery(query, NULL); 501 CFRELEASE_NULL(query); 502 503 if (!res) 504 return KRB5_CC_END; 505 return 0; 506} 507 508static krb5_error_code KRB5_CALLCONV 509xcc_set_flags(krb5_context context, 510 krb5_ccache id, 511 krb5_flags flags) 512{ 513 return 0; 514} 515 516static int KRB5_CALLCONV 517xcc_get_version(krb5_context context, 518 krb5_ccache id) 519{ 520 return 0; 521} 522 523static krb5_error_code KRB5_CALLCONV 524xcc_get_cache_first(krb5_context context, krb5_cc_cursor *cursor) 525{ 526 CFDictionaryRef query; 527 struct xcc_cursor *c; 528 529 const void *keys[] = { 530 (void *)kHEIMAttrCredentialGroupLead, 531 (void *)kHEIMAttrType, 532 }; 533 const void *values[] = { 534 (void *)kCFBooleanTrue, 535 (void *)kHEIMTypeKerberos, 536 }; 537 538 c = calloc(1, sizeof(*c)); 539 if (c == NULL) 540 return krb5_enomem(context); 541 542 query = CFDictionaryCreate(NULL, keys, values, sizeof(keys)/sizeof(keys[0]), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 543 if (query == NULL) abort(); 544 545 c->array = HeimCredCopyQuery(query); 546 CFRELEASE_NULL(query); 547 if (c->array == NULL) { 548 free_cursor(c); 549 return KRB5_CC_END; 550 } 551 *cursor = c; 552 return 0; 553} 554 555static krb5_error_code KRB5_CALLCONV 556xcc_get_cache_next(krb5_context context, krb5_cc_cursor cursor, krb5_ccache *id) 557{ 558 struct xcc_cursor *c = cursor; 559 krb5_error_code ret; 560 HeimCredRef cred; 561 krb5_xcc *x; 562 563 if (c->array == NULL) 564 return KRB5_CC_END; 565 566 if (c->offset >= CFArrayGetCount(c->array)) 567 return KRB5_CC_END; 568 569 cred = (HeimCredRef)CFArrayGetValueAtIndex(c->array, c->offset++); 570 if (cred == NULL) 571 return KRB5_CC_END; 572 573 ret = _krb5_cc_allocate(context, &krb5_xcc_ops, id); 574 if (ret) 575 return ret; 576 577 xcc_alloc(context, id); 578 x = XCACHE((*id)); 579 580 x->uuid = HeimCredGetUUID(cred); 581 CFRetain(x->uuid); 582 x->cred = cred; 583 CFRetain(cred); 584 genName(x); 585 586 return ret; 587} 588 589static krb5_error_code KRB5_CALLCONV 590xcc_end_cache_get(krb5_context context, krb5_cc_cursor cursor) 591{ 592 free_cursor((struct xcc_cursor *)cursor); 593 return 0; 594} 595 596static krb5_error_code KRB5_CALLCONV 597xcc_move(krb5_context context, krb5_ccache from, krb5_ccache to) 598{ 599 krb5_xcc *xfrom = XCACHE(from); 600 krb5_xcc *xto = XCACHE(to); 601 602 if (!HeimCredMove(xfrom->uuid, xto->uuid)) 603 return KRB5_CC_END; 604 605 CFRELEASE_NULL(xto->cred); 606 CFRELEASE_NULL(xfrom->cred); 607 608 free(xto->cache_name); 609 xto->cache_name = NULL; 610 genName(xto); 611 612 CFRELEASE_NULL(xto->clientName); 613 xto->clientName = xfrom->clientName; 614 xfrom->clientName = NULL; 615 616 if (xto->primary_principal) 617 krb5_free_principal(context, xto->primary_principal); 618 xto->primary_principal = xfrom->primary_principal; 619 xfrom->primary_principal = NULL; 620 621 return 0; 622} 623 624static krb5_error_code KRB5_CALLCONV 625xcc_get_default_name(krb5_context context, char **str) 626{ 627 krb5_set_error_message(context, EINVAL, "XCACHE doesn't have a default name"); 628 return EINVAL; 629} 630 631static krb5_error_code KRB5_CALLCONV 632xcc_set_default(krb5_context context, krb5_ccache id) 633{ 634 krb5_set_error_message(context, EINVAL, "XCACHE doesn't have a default name"); 635 return EINVAL; 636} 637 638static krb5_error_code KRB5_CALLCONV 639xcc_lastchange(krb5_context context, krb5_ccache id, krb5_timestamp *mtime) 640{ 641 *mtime = 0; 642 return 0; 643} 644 645static krb5_error_code 646xcc_get_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid) 647{ 648 krb5_xcc *x = XCACHE(id); 649 CFUUIDBytes bytes = CFUUIDGetUUIDBytes(x->uuid); 650 memcpy(uuid, &bytes, sizeof(krb5_uuid)); 651 return 0; 652} 653 654static krb5_error_code 655xcc_resolve_by_uuid(krb5_context context, krb5_ccache id, krb5_uuid uuid) 656{ 657 krb5_error_code ret; 658 CFUUIDBytes bytes; 659 krb5_xcc *x; 660 661 memcpy(&bytes, uuid, sizeof(bytes)); 662 663 CFUUIDRef uuidref = CFUUIDCreateFromUUIDBytes(NULL, bytes); 664 if (uuidref == NULL) { 665 krb5_set_error_message(context, KRB5_CC_END, "failed to create uuid"); 666 return KRB5_CC_END; 667 } 668 669 ret = xcc_alloc(context, &id); 670 if (ret) { 671 CFRELEASE_NULL(uuidref); 672 return ret; 673 } 674 675 x = XCACHE(id); 676 677 x->uuid = uuidref; 678 genName(x); 679 680 return 0; 681} 682 683static krb5_error_code 684xcc_set_acl(krb5_context context, krb5_ccache id, const char *type, /* heim_object_t */ void *obj) 685{ 686 krb5_xcc *x = XCACHE(id); 687 bool res; 688 689 CFStringRef t = CFStringCreateWithCString(NULL, type, kCFStringEncodingUTF8); 690 if (t == NULL) 691 return krb5_enomem(context); 692 693 res = HeimCredSetAttribute(x->cred, t, obj, NULL); 694 CFRELEASE_NULL(t); 695 696 if (!res) 697 return KRB5_CC_END; 698 699 return 0; 700} 701 702/** 703 * Variable containing the XCACHE based credential cache implemention. 704 * 705 * @ingroup krb5_ccache 706 */ 707 708KRB5_LIB_VARIABLE const krb5_cc_ops krb5_xcc_ops = { 709 KRB5_CC_OPS_VERSION, 710 "XCACHE", 711 xcc_get_name, 712 xcc_resolve, 713 xcc_gen_new, 714 xcc_initialize, 715 xcc_destroy, 716 xcc_close, 717 xcc_store_cred, 718 NULL, /* acc_retrieve */ 719 xcc_get_principal, 720 xcc_get_first, 721 xcc_get_next, 722 xcc_end_get, 723 xcc_remove_cred, 724 xcc_set_flags, 725 xcc_get_version, 726 xcc_get_cache_first, 727 xcc_get_cache_next, 728 xcc_end_cache_get, 729 xcc_move, 730 xcc_get_default_name, 731 xcc_set_default, 732 xcc_lastchange, 733 NULL, /* set_kdc_offset */ 734 NULL, /* get_kdc_offset */ 735 NULL, /* hold */ 736 NULL, /* unhold */ 737 xcc_get_uuid, 738 xcc_resolve_by_uuid, 739 NULL, 740 NULL, 741 xcc_set_acl 742}; 743 744#endif /* HAVE_XCC */ 745