1/* 2 * Copyright (c) 2009 Kungliga Tekniska H�gskolan 3 * (Royal Institute of Technology, Stockholm, Sweden). 4 * All rights reserved. 5 * 6 * Portions Copyright (c) 2009 - 2011 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 37#include "hdb_locl.h" 38#include <heimbase.h> 39#include <hx509.h> 40#include <base64.h> 41#include <rfc2459_asn1.h> 42#include <ifaddrs.h> 43#include <heimbase.h> 44#include <roken.h> 45 46#ifdef HAVE_OPENDIRECTORY 47 48#include <CoreFoundation/CoreFoundation.h> 49#include <OpenDirectory/OpenDirectory.h> 50#include <opendirectory/odconstants.h> 51#ifdef __APPLE_PRIVATE__ 52#include <OpenDirectory/OpenDirectoryPriv.h> 53#include <DirectoryServer/DirectoryServer.h> 54#include <dlfcn.h> 55#endif 56#include <SystemConfiguration/SCPreferences.h> 57 58struct iter_ctx { 59 CFIndex idx; 60 CFArrayRef odRecordArray; 61} iter; 62 63struct hdb_od; 64 65typedef krb5_error_code (*hod_locate_record)(krb5_context, struct hdb_od *, krb5_principal, unsigned, int, int *, ODRecordRef *); 66 67typedef struct hdb_od { 68 CFStringRef rootName; 69 ODNodeRef rootNode; 70 CFArrayRef inNodeAttributes; 71 CFArrayRef inKDCAttributes; 72 CFArrayRef inComputerOrUsers; 73 CFStringRef restoreRoot; 74 char *LKDCRealm; 75 SCDynamicStoreRef *store; 76 char *ntlmDomain; 77 struct iter_ctx iter; 78 hod_locate_record locate_record; 79} *hdb_od; 80 81static CFStringRef kRealName = CFSTR("dsAttrTypeStandard:RealName"); 82 83static const char wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC"; 84 85/* 86 * 87 */ 88 89typedef struct hdb_entry_ex_ctx { 90 krb5_principal principal; 91 ODRecordRef record; 92} hdb_entry_ex_ctx; 93 94static void 95free_hod_ctx(krb5_context context, hdb_entry_ex *entry) 96{ 97 hdb_entry_ex_ctx *ctx = entry->ctx; 98 if (ctx) { 99 if (ctx->record) 100 CFRelease(ctx->record); 101 free(ctx); 102 } 103 entry->ctx = NULL; 104} 105 106 107static hdb_entry_ex_ctx * 108get_ex_ctx(hdb_entry_ex *entry) 109{ 110 if (entry->ctx == NULL) { 111 entry->ctx = calloc(1, sizeof(*entry->ctx)); 112 if (entry->ctx == NULL) 113 return NULL; 114 entry->free_entry = free_hod_ctx; 115 } 116 return entry->ctx; 117} 118 119/* 120 * 121 */ 122 123static int 124is_lkdc(hdb_od d, const char *realm) 125{ 126 return d->LKDCRealm != NULL && krb5_realm_is_lkdc(realm); 127} 128 129static krb5_error_code 130map_service(krb5_context context, krb5_const_principal principal, krb5_principal *eprinc) 131{ 132 const char *service; 133 krb5_error_code ret; 134 135 ret = krb5_copy_principal(context, principal, eprinc); 136 if (ret) 137 return ret; 138 139 service = krb5_principal_get_comp_string(context, principal, 0); 140 if (strcasecmp(service, "afpserver") == 0 || 141 strcasecmp(service, "cifs") == 0 || 142 strcasecmp(service, "smb") == 0 || 143 strcasecmp(service, "vnc") == 0) { 144 145 free((*eprinc)->name.name_string.val[0]); 146 (*eprinc)->name.name_string.val[0] = strdup("host"); 147 } 148 return 0; 149} 150 151static bool 152tryAgainP(krb5_context context, int *tryAgain, CFErrorRef *error) 153{ 154 if (*tryAgain <= 1) 155 return 0; 156 157 if (error && *error) { 158 CFRelease(*error); 159 *error = NULL; 160 } 161 (*tryAgain)--; 162 163 krb5_warnx(context, "try fetching result again"); 164 165 return 1; 166} 167 168#define MAX_TRIES 3 169 170 171/* 172 * Returns the matching User or Computer record, if not, returns the node itself 173 */ 174 175static ODRecordRef 176HODODNodeCopyLinkageRecordFromAuthenticationData(krb5_context context, ODNodeRef node, ODRecordRef record) 177{ 178 CFMutableArrayRef types = NULL; 179 CFArrayRef linkageArray = NULL, resultArray = NULL; 180 CFTypeRef userLinkage; 181 ODQueryRef query = NULL; 182 CFErrorRef error = NULL; 183 ODRecordRef res; 184 int tryAgain = MAX_TRIES; 185 186 types = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks); 187 if (types == NULL) 188 goto out; 189 190 CFArrayAppendValue(types, kODRecordTypeUsers); 191 CFArrayAppendValue(types, kODRecordTypeComputers); 192 193 linkageArray = ODRecordCopyValues(record, CFSTR("dsAttrTypeNative:userLinkage"), &error); 194 if (linkageArray == NULL) 195 goto out; 196 197 if (CFArrayGetCount(linkageArray) == 0) 198 goto out; 199 200 userLinkage = CFArrayGetValueAtIndex(linkageArray, 0); 201 if (userLinkage == NULL) 202 goto out; 203 204 do { 205 query = ODQueryCreateWithNode(NULL, node, types, 206 CFSTR("dsAttrTypeNative:entryUUID"), 207 kODMatchEqualTo, 208 userLinkage, NULL, 1, &error); 209 if (query == NULL) 210 goto out; 211 212 resultArray = ODQueryCopyResults(query, FALSE, &error); 213 CFRelease(query); 214 if (resultArray == NULL && error == NULL) 215 tryAgain = 0; 216 } while(resultArray == NULL && tryAgainP(context, &tryAgain, &error)); 217 if (resultArray == NULL) 218 goto out; 219 220 if (CFArrayGetCount(resultArray) == 0) 221 goto out; 222 223 res = (ODRecordRef)CFArrayGetValueAtIndex(resultArray, 0); 224 if (res) 225 record = res; 226 227out: 228 CFRetain(record); 229 if (error) 230 CFRelease(error); 231 if (linkageArray) 232 CFRelease(linkageArray); 233 if (resultArray) 234 CFRelease(resultArray); 235 if (types) 236 CFRelease(types); 237 return record; 238 239} 240 241/* 242 * 243 */ 244 245static krb5_error_code 246od2hdb_usercert(krb5_context context, hdb_od d, 247 CFArrayRef data, unsigned flags, hdb_entry_ex *entry) 248{ 249 HDB_extension ext; 250 void *ptr; 251 krb5_error_code ret; 252 CFIndex i; 253 254 memset(&ext, 0, sizeof(ext)); 255 256 ext.data.element = choice_HDB_extension_data_pkinit_cert; 257 258 for (i = 0; i < CFArrayGetCount(data); i++) { 259 CFDataRef c = (CFDataRef) CFArrayGetValueAtIndex(data, i); 260 krb5_data cd; 261 262 if (CFGetTypeID((CFTypeRef)c) != CFDataGetTypeID()) 263 continue; 264 265 ret = krb5_data_copy(&cd, CFDataGetBytePtr(c), 266 CFDataGetLength(c)); 267 if (ret) { 268 free_HDB_extension(&ext); 269 return ENOMEM; 270 } 271 272 ext.data.u.pkinit_cert.len += 1; 273 274 ptr = realloc(ext.data.u.pkinit_cert.val, 275 sizeof(ext.data.u.pkinit_cert.val[0]) * ext.data.u.pkinit_cert.len); 276 if (ptr == NULL) { 277 krb5_data_free(&cd); 278 free_HDB_extension(&ext); 279 return ENOMEM; 280 } 281 ext.data.u.pkinit_cert.val = ptr; 282 283 memset(&ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1], 284 0, sizeof(ext.data.u.pkinit_cert.val[0])); 285 286 ext.data.u.pkinit_cert.val[ext.data.u.pkinit_cert.len - 1].cert = cd; 287 } 288 289 ret = hdb_replace_extension(context, &entry->entry, &ext); 290 free_HDB_extension(&ext); 291 292 return ret; 293} 294 295/* 296 * 297 */ 298 299static krb5_error_code 300nodeCreateWithName(krb5_context context, hdb_od d, ODNodeRef *node) 301{ 302 ODSessionRef session = kODSessionDefault; 303 304 *node = NULL; 305 306#ifdef __APPLE_PRIVATE__ 307 if (d->restoreRoot) { 308 CFMutableDictionaryRef options; 309 310 options = CFDictionaryCreateMutable(NULL, 0, 311 &kCFTypeDictionaryKeyCallBacks, 312 &kCFTypeDictionaryValueCallBacks); 313 if (options == NULL) 314 return ENOMEM; 315 316 CFDictionaryAddValue(options, kODSessionLocalPath, d->restoreRoot); 317 318 session = ODSessionCreate(kCFAllocatorDefault, options, NULL); 319 CFRelease(options); 320 if (session == NULL) { 321 krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create session"); 322 return HDB_ERR_DB_INUSE; 323 } 324 } 325#endif 326 327 *node = ODNodeCreateWithName(kCFAllocatorDefault, 328 session, 329 d->rootName, 330 NULL); 331 if (session) 332 CFRelease(session); 333 334 if (*node == NULL) { 335 char *restoreRoot = NULL, *path = NULL; 336 337 if (d->restoreRoot) 338 restoreRoot = rk_cfstring2cstring(d->restoreRoot); 339 path = rk_cfstring2cstring(d->rootName); 340 krb5_set_error_message(context, HDB_ERR_DB_INUSE, "failed to create root node: %s %s", 341 path ? path : "<nopath>", 342 restoreRoot ? restoreRoot : ""); 343 free(restoreRoot); 344 free(path); 345 return HDB_ERR_DB_INUSE; 346 } 347 348 return 0; 349} 350 351 352 353/* 354 * 355 */ 356 357static krb5_error_code 358od2hdb_null(krb5_context context, hdb_od d, 359 CFArrayRef data, unsigned flags, hdb_entry_ex *entry) 360{ 361 return 0; 362} 363 364/* 365 * 366 */ 367 368static krb5_error_code 369map_lkdc_principal(krb5_context context, krb5_principal principal, const char *from, const char *to) 370{ 371 size_t num = krb5_principal_get_num_comp(context, principal); 372 krb5_error_code ret; 373 374 if (strcasecmp(principal->realm, from) == 0) { 375 ret = krb5_principal_set_realm(context, principal, to); 376 if (ret) 377 return ret; 378 } 379 380 if (num == 2 && strcasecmp(principal->name.name_string.val[1], from) == 0) { 381 free(principal->name.name_string.val[1]); 382 principal->name.name_string.val[1] = strdup(to); 383 if (principal->name.name_string.val[1] == NULL) 384 return ENOMEM; 385 } 386 return 0; 387} 388 389/* 390 * 391 */ 392 393static krb5_error_code 394od2hdb_principal(krb5_context context, hdb_od d, 395 CFArrayRef data, unsigned flags, hdb_entry_ex *entry) 396{ 397 hdb_entry_ex_ctx *ctx = entry->ctx; 398 krb5_error_code ret; 399 char *str; 400 401 /* the magic store principal in hdb entry was found, lets skip this step */ 402 if (ctx && ctx->principal) 403 return 0; 404 405 if ((flags & HDB_F_CANON) == 0) 406 return 0; 407 408 if (entry->entry.principal) { 409 krb5_free_principal(context, entry->entry.principal); 410 entry->entry.principal = NULL; 411 } 412 413 str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0)); 414 if (str == NULL) 415 return ENOMEM; 416 417 ret = krb5_parse_name(context, str, &entry->entry.principal); 418 free(str); 419 if (ret) 420 return ret; 421 422 if (d->LKDCRealm) { 423 ret = map_lkdc_principal(context, entry->entry.principal, wellknown_lkdc, d->LKDCRealm); 424 if (ret) 425 return ret; 426 } 427 428 return 0; 429} 430 431static krb5_error_code 432od2hdb_def_principal(krb5_context context, hdb_od d, 433 int flags, hdb_entry_ex *entry) 434{ 435 if (entry->entry.principal) 436 return 0; 437 return HDB_ERR_NOENTRY; 438} 439 440static krb5_error_code 441hdb2od_principal_lkdc(krb5_context context, hdb_od d, ODAttributeType type, 442 unsigned flags, hdb_entry_ex *entry, ODRecordRef record) 443{ 444 size_t num = krb5_principal_get_num_comp(context, entry->entry.principal); 445 CFMutableArrayRef array = NULL; 446 krb5_principal principal = NULL; 447 krb5_error_code ret = 0; 448 CFStringRef element; 449 char *user; 450 451 if (d->LKDCRealm == NULL) 452 return 0; 453 454 /* 455 * "User" LKDC names are implicitly stored 456 */ 457 if (num == 1) 458 return 0; 459 460 ret = krb5_copy_principal(context, entry->entry.principal, &principal); 461 if (ret) 462 return ret; 463 464 ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc); 465 if (ret) 466 goto out; 467 468 /* 2 entry nodes are servers :/ */ 469 if (num == 2) { 470 krb5_principal eprinc; 471 type = CFSTR("dsAttrTypeNative:KerberosServerName"); 472 473 ret = map_service(context, principal, &eprinc); 474 if (ret) 475 goto out; 476 krb5_free_principal(context, principal); 477 principal = eprinc; 478 } 479 480 array = CFArrayCreateMutable(kCFAllocatorDefault, 1, 481 &kCFTypeArrayCallBacks); 482 if (array == NULL) { 483 ret = ENOMEM; 484 goto out; 485 } 486 487 ret = krb5_unparse_name(context, principal, &user); 488 if (ret) 489 goto out; 490 491 element = CFStringCreateWithCString(kCFAllocatorDefault, user, kCFStringEncodingUTF8); 492 if (element == NULL) { 493 ret = ENOMEM; 494 goto out; 495 } 496 497 CFArrayAppendValue(array, element); 498 CFRelease(element); 499 500 bool r = ODRecordSetValue(record, type, array, NULL); 501 if (!r) 502 ret = HDB_ERR_UK_SERROR; 503 504 out: 505 if (principal) 506 krb5_free_principal(context, principal); 507 if (array) 508 CFRelease(array); 509 510 return ret; 511} 512 513 514static krb5_error_code 515hdb2od_principal_server(krb5_context context, hdb_od d, ODAttributeType type, 516 unsigned flags, hdb_entry_ex *entry, ODRecordRef record) 517{ 518 krb5_error_code ret; 519 CFStringRef name; 520 char *user; 521 522 if (d->LKDCRealm) 523 return 0; 524 525 ret = krb5_unparse_name(context, entry->entry.principal, &user); 526 if (ret) 527 return ret; 528 529 name = CFStringCreateWithCString(NULL, user, kCFStringEncodingUTF8); 530 free(user); 531 if (name == NULL) 532 return ENOMEM; 533 534 bool r = ODRecordSetValue(record, type, name, NULL); 535 CFRelease(name); 536 if (!r) 537 return HDB_ERR_UK_SERROR; 538 539 return 0; 540} 541 542/* 543 * 544 */ 545 546static krb5_error_code 547od2hdb_alias(krb5_context context, hdb_od d, 548 CFArrayRef data, unsigned flags, hdb_entry_ex *entry) 549{ 550 hdb_entry_ex_ctx *ctx = entry->ctx; 551 krb5_error_code ret; 552 krb5_principal principal; 553 char *str; 554 555 /* skip aliases on magic principal since they don't have aliases */ 556 if (ctx && ctx->principal) 557 return 0; 558 559 str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0)); 560 if (str == NULL) 561 return ENOMEM; 562 563 ret = krb5_parse_name(context, str, &principal); 564 free(str); 565 if (ret) 566 return ret; 567 568 krb5_free_principal(context, principal); 569 570 return 0; 571} 572/* 573 * 574 */ 575 576static krb5_error_code 577od2hdb_keys(krb5_context context, hdb_od d, 578 CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry) 579{ 580 CFIndex i, count = CFArrayGetCount(akeys); 581 krb5_error_code ret; 582 hdb_keyset_aapl *keys; 583 krb5_kvno specific_match = -1, general_match = -1; 584 krb5_kvno specific_kvno = 0, general_kvno = 0; 585 CFIndex len; 586 587 if (count == 0) 588 return ENOMEM; 589 590 keys = calloc(count, sizeof(keys[0])); 591 if (keys == NULL) 592 return ENOMEM; 593 594 ret = 0; 595 for (i = 0; i < count; i++) { 596 CFTypeRef type = CFArrayGetValueAtIndex(akeys, i); 597 void *ptr; 598 599 if (CFGetTypeID(type) == CFDataGetTypeID()) { 600 CFDataRef data = (CFDataRef) type; 601 602 len = CFDataGetLength(data); 603 ptr = malloc(len); 604 if (ptr == NULL) { 605 ret = ENOMEM; 606 goto out; 607 } 608 memcpy(ptr, CFDataGetBytePtr(data), len); 609 } else if (CFGetTypeID(type) == CFStringGetTypeID()) { 610 CFStringRef cfstr = (CFStringRef)type; 611 char *str; 612 613 str = rk_cfstring2cstring(cfstr); 614 if (str == NULL) { 615 ret = ENOMEM; 616 goto out; 617 } 618 ptr = malloc(strlen(str)); 619 if (ptr == NULL) { 620 free(str); 621 ret = ENOMEM; 622 goto out; 623 } 624 625 ret = base64_decode(str, ptr); 626 free(str); 627 if (ret < 0) { 628 free(ptr); 629 ret = EINVAL; 630 goto out; 631 } 632 633 len = ret; 634 635 } else { 636 ret = EINVAL; /* XXX */ 637 goto out; 638 } 639 640 ret = decode_hdb_keyset_aapl(ptr, len, &keys[i], NULL); 641 free(ptr); 642 if (ret) 643 goto out; 644 645 if (keys[i].principal) { 646 if (krb5_principal_compare(context, keys[i].principal, entry->entry.principal)) { 647 648 if (keys[i].kvno > specific_kvno) { 649 hdb_entry_ex_ctx *ctx = get_ex_ctx(entry); 650 if (ctx == NULL) { 651 ret = ENOMEM; 652 goto out; 653 } 654 ctx->principal = entry->entry.principal; 655 656 specific_match = (krb5_kvno)i; 657 specific_kvno = keys[i].kvno; 658 } 659 } 660 } else { 661 if (keys[i].kvno > general_kvno) { 662 general_match = (uint32_t)i; 663 general_kvno = keys[i].kvno; 664 } 665 } 666 } 667 668 if (specific_match != -1) { 669 i = specific_match; 670 entry->entry.kvno = specific_kvno; 671 } else if (general_match != -1) { 672 i = general_match; 673 entry->entry.kvno = general_kvno; 674 } else { 675 ret = HDB_ERR_NOENTRY; 676 goto out; 677 } 678 679 free_Keys(&entry->entry.keys); 680 681 entry->entry.keys.len = keys[i].keys.len; 682 entry->entry.keys.val = keys[i].keys.val; 683 keys[i].keys.val = NULL; 684 keys[i].keys.len = 0; 685 686 out: 687 for (i = 0; i < count; i++) 688 free_hdb_keyset_aapl(&keys[i]); 689 free(keys); 690 691 return ret; 692} 693 694static krb5_error_code 695hdb2od_keys(krb5_context context, hdb_od d, ODAttributeType type, 696 unsigned flags, hdb_entry_ex *entry, ODRecordRef record) 697{ 698 CFMutableArrayRef array; 699 heim_octet_string data; 700 krb5_error_code ret; 701 CFDataRef element; 702 hdb_keyset_aapl key; 703 size_t size = 0; 704 705 /* if this is a change password operation, the keys have already been updated with ->hdb_password */ 706 if (flags & HDB_F_CHANGE_PASSWORD) 707 return 0; 708 709 /* this overwrites other keys that are stored in the special "alias" keyset for server */ 710 711 key.kvno = entry->entry.kvno; 712 key.keys.len = entry->entry.keys.len; 713 key.keys.val = entry->entry.keys.val; 714 key.principal = NULL; 715 716 array = CFArrayCreateMutable(kCFAllocatorDefault, 1, 717 &kCFTypeArrayCallBacks); 718 if (array == NULL) { 719 ret = ENOMEM; 720 goto out; 721 } 722 723 ASN1_MALLOC_ENCODE(hdb_keyset_aapl, data.data, data.length, &key, &size, ret); 724 if (ret) 725 goto out; 726 if (data.length != size) 727 krb5_abortx(context, "internal asn.1 encoder error"); 728 729 element = CFDataCreate(kCFAllocatorDefault, data.data, data.length); 730 if (element == NULL) { 731 ret = ENOMEM; 732 goto out; 733 } 734 735 CFArrayAppendValue(array, element); 736 CFRelease(element); 737 738 bool r = ODRecordSetValue(record, type, array, NULL); 739 if (!r) 740 ret = HDB_ERR_UK_SERROR; 741 742 out: 743 if (array) 744 CFRelease(array); 745 746 return ret; 747} 748 749/* 750 * 751 */ 752 753static krb5_error_code 754od2hdb_srp(krb5_context context, hdb_od d, 755 CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry) 756{ 757 CFIndex i, count = CFArrayGetCount(akeys); 758 krb5_error_code ret; 759 HDB_extension ext; 760 hdb_srp srp; 761 CFIndex len; 762 763 if (count == 0) 764 return ENOMEM; 765 766 memset(&ext, 0, sizeof(ext)); 767 ext.mandatory = FALSE; 768 ext.data.element = choice_HDB_extension_data_srp; 769 770 ext.data.u.srp.len = 0; 771 ext.data.u.srp.val = NULL; 772 773 ret = 0; 774 for (i = 0; i < count; i++) { 775 CFTypeRef type = CFArrayGetValueAtIndex(akeys, i); 776 void *ptr; 777 778 if (CFGetTypeID(type) == CFDataGetTypeID()) { 779 CFDataRef data = (CFDataRef) type; 780 781 len = CFDataGetLength(data); 782 ptr = malloc(len); 783 if (ptr == NULL) { 784 ret = ENOMEM; 785 goto out; 786 } 787 memcpy(ptr, CFDataGetBytePtr(data), len); 788 } else if (CFGetTypeID(type) == CFStringGetTypeID()) { 789 CFStringRef cfstr = (CFStringRef)type; 790 char *str; 791 792 str = rk_cfstring2cstring(cfstr); 793 if (str == NULL) { 794 ret = ENOMEM; 795 goto out; 796 } 797 ptr = malloc(strlen(str)); 798 if (ptr == NULL) { 799 free(str); 800 ret = ENOMEM; 801 goto out; 802 } 803 804 ret = base64_decode(str, ptr); 805 free(str); 806 if (ret < 0) { 807 free(ptr); 808 ret = EINVAL; 809 goto out; 810 } 811 812 len = ret; 813 814 } else { 815 ret = EINVAL; /* XXX */ 816 goto out; 817 } 818 819 ret = decode_hdb_srp(ptr, len, &srp, NULL); 820 free(ptr); 821 if (ret) 822 goto out; 823 824 ret = add_hdb_srp_set(&ext.data.u.srp, &srp); 825 free_hdb_srp(&srp); 826 if (ret) { 827 goto out; 828 } 829 } 830 831 ret = hdb_replace_extension(context, &entry->entry, &ext); 832 833 out: 834 free_HDB_extension(&ext); 835 836 return ret; 837} 838 839static krb5_error_code 840hdb2od_srp(krb5_context context, hdb_od d, ODAttributeType type, 841 unsigned flags, hdb_entry_ex *entry, ODRecordRef record) 842{ 843 CFMutableArrayRef array; 844 heim_octet_string data; 845 krb5_error_code ret; 846 CFDataRef element; 847 hdb_srp_set key; 848 size_t size = 0; 849 850 /* XXX find key */ 851 memset(&key, 0, sizeof(key)); 852 853 /* if this is a change password operation, the keys have already been updated with ->hdb_password */ 854 if (flags & HDB_F_CHANGE_PASSWORD) 855 return 0; 856 857 /* this overwrites other keys that are stored in the special "alias" keyset for server */ 858 859 array = CFArrayCreateMutable(kCFAllocatorDefault, 1, 860 &kCFTypeArrayCallBacks); 861 if (array == NULL) { 862 ret = ENOMEM; 863 goto out; 864 } 865 866 ASN1_MALLOC_ENCODE(hdb_srp_set, data.data, data.length, &key, &size, ret); 867 if (ret) 868 goto out; 869 if (data.length != size) 870 krb5_abortx(context, "internal asn.1 encoder error"); 871 872 element = CFDataCreate(kCFAllocatorDefault, data.data, data.length); 873 if (element == NULL) { 874 ret = ENOMEM; 875 goto out; 876 } 877 878 CFArrayAppendValue(array, element); 879 CFRelease(element); 880 881 bool r = ODRecordSetValue(record, type, array, NULL); 882 if (!r) 883 ret = HDB_ERR_UK_SERROR; 884 885 out: 886 if (array) 887 CFRelease(array); 888 889 return ret; 890} 891 892/* 893 * 894 */ 895 896static krb5_error_code 897od2hdb_flags(krb5_context context, hdb_od d, 898 CFArrayRef data, unsigned qflags, hdb_entry_ex *entry) 899{ 900 int flags; 901 char *str; 902 903 str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0)); 904 if (str == NULL) 905 return ENOMEM; 906 907 flags = atoi(str); 908 free(str); 909 entry->entry.flags = int2HDBFlags(flags); 910 return 0; 911} 912 913static krb5_error_code 914od2hdb_def_flags(krb5_context context, hdb_od d, 915 int flags, hdb_entry_ex *entry) 916{ 917 entry->entry.flags.client = 1; 918 entry->entry.flags.forwardable = 1; 919 entry->entry.flags.renewable = 1; 920 entry->entry.flags.proxiable = 1; 921 922 return 0; 923} 924 925static krb5_error_code 926hdb2od_flags(krb5_context context, hdb_od d, ODAttributeType type, 927 unsigned flags, hdb_entry_ex *entry, ODRecordRef record) 928{ 929 int eflags = HDBFlags2int(entry->entry.flags); 930 CFStringRef value; 931 932 value = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, 933 CFSTR("%d"), eflags); 934 if (value == NULL) 935 return ENOMEM; 936 937 bool r = ODRecordSetValue(record, type, value, NULL); 938 CFRelease(value); 939 if (!r) 940 return HDB_ERR_UK_SERROR; 941 942 return 0; 943} 944 945static krb5_error_code 946od2hdb_altsecids(krb5_context context, hdb_od d, 947 CFArrayRef akeys, unsigned flags, hdb_entry_ex *entry) 948{ 949 CFIndex i, count = CFArrayGetCount(akeys); 950 krb5_error_code ret; 951 952 if (count == 0) 953 return ENOMEM; 954 955 for (i = 0; i < count; i++) { 956 CFStringRef name = CFArrayGetValueAtIndex(akeys, i); 957 char *str, *str0, *subj; 958 959 if (CFGetTypeID(name) != CFStringGetTypeID()) 960 continue; 961 962 if (!CFStringHasPrefix(name, CFSTR("X509:<T>"))) 963 continue; 964 965 /* split out X509:<T>(.*)<S>(.*) from the string and store */ 966 967 str0 = str = rk_cfstring2cstring(name); 968 str += 8; /* skip X509:<T> */ 969 970 subj = strstr(str, "<S>"); 971 if (subj == NULL) { 972 free(str0); 973 continue; 974 } 975 *subj = '\0'; 976 subj += 3; /* skip <S> */ 977 978 /* make sure that there is no <T> or <S> in the string */ 979 if (strstr(subj, "<T>") || strstr(subj, "<S>") || 980 strstr(str, "<T>") || strstr(str, "<S>")) 981 { 982 free(str0); 983 continue; 984 } 985 986 ret = hdb_entry_set_pkinit_acl(&entry->entry, subj, NULL, str); 987 free(str0); 988 if (ret) 989 return ret; 990 } 991 return 0; 992} 993 994/* 995 * 996 */ 997 998static krb5_error_code 999od2hdb_acl_rights(krb5_context context, hdb_od d, 1000 CFArrayRef data, unsigned flags, hdb_entry_ex *entry) 1001{ 1002 char *str; 1003 1004 str = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0)); 1005 if (str == NULL) 1006 return ENOMEM; 1007 1008 entry->entry.acl_rights = malloc(sizeof(*entry->entry.acl_rights)); 1009 if (entry->entry.acl_rights == NULL) { 1010 free(str); 1011 return ENOMEM; 1012 } 1013 1014 *entry->entry.acl_rights = atoi(str); 1015 free(str); 1016 1017 return 0; 1018} 1019 1020/* 1021 * 1022 */ 1023 1024typedef krb5_error_code 1025(*od2hdb_func)(krb5_context, hdb_od, CFArrayRef, unsigned flags, hdb_entry_ex *); 1026 1027typedef krb5_error_code 1028(*od2hdb_default)(krb5_context, hdb_od, int, hdb_entry_ex *); 1029 1030typedef krb5_error_code 1031(*hdb2od_func)(krb5_context, hdb_od, ODAttributeType, 1032 unsigned flags, hdb_entry_ex *, ODRecordRef); 1033 1034 1035static const struct key { 1036 const char *desc; 1037 CFStringRef name; 1038 int flags; 1039#define MULTI_VALUE 1 1040#define OPTIONAL_VALUE 2 1041#define SERVER_VALUE 4 1042#define DELETE_KEY 8 1043 od2hdb_func od2hdb; 1044 od2hdb_default od2hdb_def; 1045 hdb2od_func hdb2od; 1046} keys[] = { 1047 { 1048 "keyset", 1049 CFSTR("dsAttrTypeNative:draft-krbKeySet"), OPTIONAL_VALUE | MULTI_VALUE | SERVER_VALUE | DELETE_KEY, 1050 od2hdb_keys, 1051 NULL, 1052 hdb2od_keys 1053 }, 1054 { 1055 "srp-keyset", 1056 CFSTR("dsAttrTypeNative:HeimdalSRPKey"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY, 1057 od2hdb_srp, 1058 NULL, 1059 hdb2od_srp 1060 }, 1061 { 1062 "KerberosKeys", 1063 CFSTR("dsAttrTypeNative:KerberosKeys"), OPTIONAL_VALUE | MULTI_VALUE | DELETE_KEY, 1064 od2hdb_keys, 1065 NULL, 1066 hdb2od_keys 1067 }, 1068 { 1069 "username", 1070 CFSTR("dsAttrTypeNative:KerberosUserName"), OPTIONAL_VALUE | DELETE_KEY, 1071 od2hdb_principal, 1072 od2hdb_def_principal, 1073 hdb2od_principal_lkdc 1074 }, 1075 { 1076 "principalName", 1077 CFSTR("dsAttrTypeNative:draft-krbPrincipalName"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY, 1078 od2hdb_principal, 1079 NULL, 1080 hdb2od_principal_server 1081 }, 1082 { 1083 "principalAlias", 1084 CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases"), MULTI_VALUE | OPTIONAL_VALUE | DELETE_KEY, 1085 od2hdb_alias, 1086 NULL, 1087 NULL 1088 }, 1089 { 1090 "KerberosFlags", 1091 CFSTR("dsAttrTypeNative:KerberosFlags"), DELETE_KEY, 1092 od2hdb_flags, 1093 od2hdb_def_flags, 1094 hdb2od_flags 1095 }, 1096 { 1097 "KerberosPolicy", 1098 CFSTR("dsAttrTypeNative:draft-krbTicketPolicy"), OPTIONAL_VALUE | SERVER_VALUE | DELETE_KEY, 1099 od2hdb_flags, 1100 NULL, 1101 hdb2od_flags 1102 }, 1103 { 1104 "MaxLife", 1105 CFSTR("dsAttrTypeNative:KerberosMaxLife"), OPTIONAL_VALUE | DELETE_KEY, 1106 od2hdb_null, 1107 NULL, 1108 NULL 1109 }, 1110 { 1111 "MaxRenewLife", 1112 CFSTR("dsAttrTypeNative:KerberosMaxRenew"), OPTIONAL_VALUE | DELETE_KEY, 1113 od2hdb_null, 1114 NULL, 1115 NULL 1116 }, 1117 { 1118 "UserCertificate", 1119 CFSTR("dsAttrTypeStandard:UserCertificate"), MULTI_VALUE | OPTIONAL_VALUE, 1120 od2hdb_usercert, 1121 NULL, 1122 NULL 1123 }, 1124 { 1125 "AltSecurityIdentities", 1126 CFSTR("dsAttrTypeStandard:AltSecurityIdentities"), MULTI_VALUE | OPTIONAL_VALUE, 1127 od2hdb_altsecids, 1128 NULL, 1129 NULL 1130 }, 1131 { 1132 "principalACL", 1133 CFSTR("dsAttrTypeNative:draft-krbPrincipalACL"), OPTIONAL_VALUE, 1134 od2hdb_acl_rights, 1135 NULL, 1136 NULL 1137 } 1138 1139}; 1140static const int num_keys = sizeof(keys)/sizeof(keys[0]); 1141 1142/* 1143 * Policy mappings 1144 */ 1145 1146static krb5_error_code 1147updateTimePolicy(int64_t reltime, time_t **t) 1148{ 1149 if (reltime == kODExpirationTimeNeverExpires) { 1150 if (*t) { 1151 free(*t); 1152 *t = NULL; 1153 } 1154 } else if (reltime == kODExpirationTimeExpired || reltime < 0) { 1155 if (*t == NULL) { 1156 *t = malloc(sizeof(**t)); 1157 if (*t == NULL) 1158 return ENOMEM; 1159 } 1160 **t = time(NULL) - 60; /* expired on 60s ago */ 1161 } else { 1162 if (*t == NULL) { 1163 *t = malloc(sizeof(**t)); 1164 if (*t == NULL) 1165 return ENOMEM; 1166 } 1167 time_t truncated = (time_t)reltime; 1168 if ((int64_t)truncated < reltime) { 1169 /* We can't really express this expiration time. so just 1170 * move it 2 years into the future, hopefully we wont get 1171 * tickets that live that long. 1172 */ 1173 truncated = 3600 * 24 * 365 * 2; 1174 } 1175 time_t now = time(NULL); 1176 time_t expire = now + truncated; 1177 if (expire < now) { 1178 /* same goes for time overrun */ 1179 expire = now + (3600 * 24 * 365 * 2); 1180 } 1181 **t = expire; 1182 } 1183 return 0; 1184} 1185 1186/* 1187 * HDB entry points 1188 */ 1189 1190static krb5_error_code 1191hod_close(krb5_context context, HDB *db) 1192{ 1193 return 0; 1194} 1195 1196static krb5_error_code 1197hod_destroy(krb5_context context, HDB *db) 1198{ 1199 hdb_od d = (hdb_od)db->hdb_db; 1200 krb5_error_code ret; 1201 1202 if (d->rootNode) 1203 CFRelease(d->rootNode); 1204 if (d->inNodeAttributes) 1205 CFRelease(d->inNodeAttributes); 1206 if (d->inKDCAttributes) 1207 CFRelease(d->inKDCAttributes); 1208 if (d->LKDCRealm) 1209 free(d->LKDCRealm); 1210 1211 ret = hdb_clear_master_key (context, db); 1212 free(db->hdb_name); 1213 free(db); 1214 return ret; 1215} 1216 1217static krb5_error_code 1218hod_lock(krb5_context context, HDB *db, int operation) 1219{ 1220 return 0; 1221} 1222 1223static krb5_error_code 1224hod_unlock(krb5_context context, HDB *db) 1225{ 1226 return 0; 1227} 1228 1229static krb5_error_code 1230od_record2entry(krb5_context context, HDB * db, ODRecordRef cfRecord, 1231 unsigned flags, hdb_entry_ex * entry) 1232{ 1233 hdb_od d = (hdb_od)db->hdb_db; 1234 krb5_error_code ret = HDB_ERR_NOENTRY; 1235 int i; 1236 1237 for (i = 0; i < num_keys; i++) { 1238 CFArrayRef data; 1239 1240 data = ODRecordCopyValues(cfRecord, keys[i].name, NULL); 1241 if (data == NULL) { 1242 if (keys[i].flags & OPTIONAL_VALUE) 1243 continue; 1244 if (keys[i].od2hdb_def) { 1245 ret = (*keys[i].od2hdb_def)(context, d, flags, entry); 1246 if (ret) 1247 goto out; 1248 continue; 1249 } 1250 krb5_warnx(context, "Failed to copy non-optional value %s", keys[i].desc); 1251 ret = HDB_ERR_NOENTRY; 1252 goto out; 1253 } 1254 1255 if ((keys[i].flags & MULTI_VALUE) && CFArrayGetCount(data) < 1) { 1256 krb5_warnx(context, "Expected multivalue got zero %s for", keys[i].desc); 1257 CFRelease(data); 1258 ret = HDB_ERR_NOENTRY; 1259 goto out; 1260 } else if ((keys[i].flags & MULTI_VALUE) == 0 && CFArrayGetCount(data) != 1) { 1261 krb5_warnx(context, "Expected single-value got non zero %d for %s", 1262 (int)CFArrayGetCount(data), keys[i].desc); 1263 CFRelease(data); 1264 ret = HDB_ERR_NOENTRY; 1265 goto out; 1266 } 1267 1268 ret = (*keys[i].od2hdb)(context, d, data, flags, entry); 1269 CFRelease(data); 1270 if (ret) { 1271 krb5_warn(context, ret, "od2hdb failed for %s", keys[i].desc); 1272 goto out; 1273 } 1274 } 1275 1276 /* 1277 * If entry didn't contain a principal, something is wrong with 1278 * the entry, lets skip it. 1279 */ 1280 if (entry->entry.principal == NULL) { 1281 krb5_warnx(context, "principal missing"); 1282 ret = HDB_ERR_NOENTRY; 1283 goto out; 1284 } 1285 1286 /* Not recorded in the OD backend, make something up */ 1287 ret = krb5_parse_name(context, "hdb/od@WELL-KNOWN:OD-BACKEND", 1288 &entry->entry.created_by.principal); 1289 if (ret) 1290 goto out; 1291 1292 entry->entry.created_by.time = time(NULL); 1293 1294 if ((flags & HDB_F_GET_KRBTGT) == 0 && !krb5_principal_is_krbtgt(context, entry->entry.principal)) { 1295 ODRecordRef userrecord; 1296 1297 userrecord = HODODNodeCopyLinkageRecordFromAuthenticationData(context, d->rootNode, cfRecord); 1298 if (userrecord) { 1299 int64_t reltime; 1300 CFIndex errorcode = 0; 1301 CFErrorRef error = NULL; 1302 1303 if (!ODRecordAuthenticationAllowed(userrecord, &error)) { 1304 if (error) { 1305 errorcode = CFErrorGetCode(error); 1306 CFRelease(error); 1307 } 1308 } 1309 1310 if (errorcode == kODErrorCredentialsPasswordChangeRequired || errorcode == kODErrorCredentialsPasswordExpired) { 1311 reltime = kODExpirationTimeExpired; 1312 } else { 1313 reltime = ODRecordSecondsUntilPasswordExpires(userrecord); 1314 } 1315 ret = updateTimePolicy(reltime, &entry->entry.pw_end); 1316 if (ret) { 1317 CFRelease(userrecord); 1318 goto out; 1319 } 1320 1321 if (errorcode == kODErrorCredentialsAccountDisabled) { 1322 reltime = kODExpirationTimeExpired; 1323 entry->entry.flags.invalid = 1; 1324 } else if (errorcode == kODErrorCredentialsAccountExpired) { 1325 reltime = kODExpirationTimeExpired; 1326 } else { 1327 reltime = ODRecordSecondsUntilAuthenticationsExpire(userrecord); 1328 } 1329 ret = updateTimePolicy(reltime, &entry->entry.valid_end); 1330 CFRelease(userrecord); 1331 if (ret) 1332 goto out; 1333 } 1334 } 1335 1336 if (db->hdb_master_key_set && (flags & HDB_F_DECRYPT)) { 1337 ret = hdb_unseal_keys(context, db, &entry->entry); 1338 if(ret) { 1339 krb5_warn(context, ret, "unseal keys failed"); 1340 goto out; 1341 } 1342 } 1343 1344out: 1345 return ret; 1346} 1347 1348static krb5_error_code 1349hod_nextkey(krb5_context context, HDB * db, unsigned flags, 1350 hdb_entry_ex * entry) 1351{ 1352 ODRecordRef cfRecord = NULL; 1353 hdb_od d = (hdb_od)db->hdb_db; 1354 krb5_error_code ret; 1355 1356 memset(entry, 0, sizeof(*entry)); 1357 1358 heim_assert(d->iter.odRecordArray != NULL, "hdb: hod_next w/o hod_first"); 1359 1360 while (d->iter.idx < CFArrayGetCount(d->iter.odRecordArray)) { 1361 cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(d->iter.odRecordArray, d->iter.idx); 1362 d->iter.idx++; 1363 if (cfRecord == NULL) { 1364 ret = HDB_ERR_NOENTRY; 1365 goto out; 1366 } 1367 1368 CFArrayRef kerberosKeysArray = ODRecordCopyValues(cfRecord, CFSTR("dsAttrTypeNative:KerberosKeys"), NULL); 1369 if (kerberosKeysArray == NULL) { 1370 cfRecord = NULL; 1371 continue; 1372 } 1373 if (CFArrayGetCount(kerberosKeysArray) == 0) { 1374 CFRelease(kerberosKeysArray); 1375 cfRecord = NULL; 1376 continue; 1377 } 1378 CFRelease(kerberosKeysArray); 1379 1380 break; 1381 } 1382 1383 if (cfRecord) { 1384 CFStringRef name = ODRecordGetRecordName(cfRecord); 1385 char *str; 1386 1387 str = rk_cfstring2cstring(name); 1388 if (str == NULL) { 1389 ret = ENOENT; 1390 goto out; 1391 } 1392 1393 ret = krb5_make_principal(context, &entry->entry.principal, 1394 d->LKDCRealm, str, NULL); 1395 free(str); 1396 if (ret) 1397 goto out; 1398 1399 ret = od_record2entry(context, db, cfRecord, flags, entry); 1400 } else 1401 ret = HDB_ERR_NOENTRY; 1402 1403out: 1404 if (ret) { 1405 free_hdb_entry(&entry->entry); 1406 1407 if (d->iter.odRecordArray) { 1408 CFRelease(d->iter.odRecordArray); 1409 d->iter.odRecordArray = NULL; 1410 d->iter.idx = 0; 1411 } 1412 } 1413 1414 return ret; 1415} 1416 1417static krb5_error_code 1418hod_firstkey(krb5_context context, HDB *db, 1419 unsigned flags, hdb_entry_ex *entry) 1420{ 1421 int ret = 0; 1422 ODQueryRef query = NULL; 1423 hdb_od d = (hdb_od)db->hdb_db; 1424 int tryAgain = MAX_TRIES; 1425 1426 if (d->LKDCRealm == NULL) { 1427 krb5_set_error_message(context, EINVAL, "iteration over database only supported for DSLocal"); 1428 return EINVAL; 1429 } 1430 1431 heim_assert(d->iter.odRecordArray == NULL, "hdb: more then one iteration at the same time"); 1432 1433 do { 1434 CFErrorRef error = NULL; 1435 1436 query = ODQueryCreateWithNode(NULL, d->rootNode, 1437 kODRecordTypeUsers, NULL, 1438 kODMatchAny, 1439 NULL, 1440 kODAttributeTypeAllAttributes, 1441 0, 1442 &error); 1443 if (query == NULL) { 1444 ret = HDB_ERR_NOENTRY; 1445 goto out; 1446 } 1447 1448 d->iter.odRecordArray = ODQueryCopyResults(query, FALSE, &error); 1449 CFRelease(query); 1450 if (d->iter.odRecordArray == NULL && error == NULL) 1451 tryAgain = 0; 1452 if (error) 1453 CFRelease(error); 1454 } while(d->iter.odRecordArray == NULL && tryAgainP(context, &tryAgain, NULL)); 1455 1456 if (d->iter.odRecordArray == NULL) { 1457 ret = HDB_ERR_NOENTRY; 1458 goto out; 1459 } 1460 d->iter.idx = 0; 1461 1462 return hod_nextkey(context, db, flags, entry); 1463out: 1464 return ret; 1465} 1466 1467static krb5_error_code 1468hod_open(krb5_context context, HDB * db, int flags, mode_t mode) 1469{ 1470 hdb_od d = (hdb_od)db->hdb_db; 1471 1472 if (d->rootNode == NULL) 1473 return nodeCreateWithName(context, d, &d->rootNode); 1474 1475 return 0; 1476} 1477 1478static krb5_error_code 1479lkdc_locate_record(krb5_context context, hdb_od d, krb5_principal principal, 1480 unsigned flags, int createp, int *createdp, ODRecordRef *result) 1481{ 1482 ODMatchType matchtype = kODMatchEqualTo; 1483 CFStringRef queryString = NULL; 1484 ODRecordRef cfRecord; 1485 ODQueryRef query = NULL; 1486 CFArrayRef res = NULL; 1487 CFStringRef attr; 1488 krb5_error_code ret = 0; 1489 krb5_principal eprinc = NULL; 1490 char *kuser = NULL; 1491 const char *node = NULL; 1492 CFTypeRef rtype = NULL; 1493 int upflags = 0; 1494 int tryAgain = MAX_TRIES; 1495 1496 *result = NULL; 1497 1498 /* 1499 * Really need something like CrackName for uniform unparsing 1500 */ 1501 1502 if (principal->name.name_type == KRB5_NT_X509_GENERAL_NAME) { 1503#ifdef PKINIT 1504 char *anode = NULL; 1505 hx509_name name = NULL; 1506 GeneralName gn; 1507 ssize_t len; 1508 void *buf; 1509 Name n; 1510 1511 memset(&gn, 0, sizeof(gn)); 1512 memset(&n, 0, sizeof(n)); 1513 1514 if (krb5_principal_get_num_comp(context, principal) != 1) { 1515 krb5_warnx(context, "wrong length of x509 name"); 1516 return HDB_ERR_NOENTRY; 1517 } 1518 1519 len = strlen(principal->name.name_string.val[0]); 1520 buf = malloc(len); 1521 if (buf == NULL) 1522 return HDB_ERR_NOENTRY; 1523 1524 len = base64_decode(principal->name.name_string.val[0], buf); 1525 if (len <= 0) { 1526 free(buf); 1527 return HDB_ERR_NOENTRY; 1528 } 1529 1530 ret = decode_GeneralName(buf, len, &gn, NULL); 1531 free(buf); 1532 if (ret) { 1533 krb5_warnx(context, "x500 GeneralName malformed: %d", ret); 1534 return HDB_ERR_NOENTRY; 1535 } 1536 1537 if (gn.element != choice_GeneralName_directoryName) { 1538 krb5_warnx(context, "x500 name not directory Name"); 1539 free_GeneralName(&gn); 1540 return HDB_ERR_NOENTRY; 1541 } 1542 1543 n.element = choice_Name_rdnSequence; 1544 n.u.rdnSequence.len = gn.u.directoryName.u.rdnSequence.len; 1545 n.u.rdnSequence.val = gn.u.directoryName.u.rdnSequence.val; 1546 1547 ret = hx509_name_from_Name(&n, &name); 1548 free_GeneralName(&gn); 1549 if (ret) 1550 return HDB_ERR_NOENTRY; 1551 1552 attr = kODAttributeTypeRecordName; 1553 rtype = kODRecordTypeUsers; 1554 1555 ret = hx509_name_to_string(name, &anode); 1556 hx509_name_free(&name); 1557 if (ret) 1558 return HDB_ERR_NOENTRY; 1559 1560 queryString = CFStringCreateWithCString(NULL, anode, kCFStringEncodingUTF8); 1561 free(anode); 1562 if (queryString == NULL) { 1563 ret = HDB_ERR_NOENTRY; 1564 goto out; 1565 } 1566 createp = 0; 1567#else 1568 return HDB_ERR_NOENTRY; 1569#endif 1570 } else if (principal->name.name_type == KRB5_NT_NTLM) { 1571 attr = kODAttributeTypeRecordName; 1572 rtype = kODRecordTypeUsers; 1573 1574 if (krb5_principal_get_num_comp(context, principal) != 1) { 1575 krb5_warnx(context, "wrong length of ntlm name"); 1576 return HDB_ERR_NOENTRY; 1577 } 1578 1579 if (d->ntlmDomain == NULL) { 1580 krb5_warnx(context, "NTLM domain not configured"); 1581 return HDB_ERR_NOENTRY; 1582 } 1583 1584 krb5_principal_set_realm(context, principal, d->ntlmDomain); 1585 node = krb5_principal_get_comp_string(context, principal, 0); 1586 1587 } else if (krb5_principal_is_pku2u(context, principal)) { 1588 attr = kODAttributeTypeRecordName; 1589 if (flags & HDB_F_GET_CLIENT) { 1590 int num = krb5_principal_get_num_comp(context, principal); 1591 if (num != 1) { 1592 ret = HDB_ERR_NOENTRY; 1593 goto out; 1594 } 1595 rtype = kODRecordTypeUsers; 1596 upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM; 1597 upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY; 1598 } else { 1599 node = "localhost"; 1600 rtype = kODRecordTypeComputers; 1601 } 1602 } else if (is_lkdc(d, principal->realm)) { 1603 /* check if user is a LKDC user, just look for RecordName then */ 1604 int num = krb5_principal_get_num_comp(context, principal); 1605 const char *comp0; 1606 size_t lencomp0; 1607 1608 if (num < 1) { 1609 ret = HDB_ERR_NOENTRY; 1610 goto out; 1611 } 1612 1613 ret = map_lkdc_principal(context, principal, d->LKDCRealm, wellknown_lkdc); 1614 if (ret) 1615 goto out; 1616 1617 comp0 = krb5_principal_get_comp_string(context, principal, 0); 1618 lencomp0 = strlen(comp0); 1619 if (lencomp0 == 0) { 1620 ret = HDB_ERR_NOENTRY; 1621 goto out; 1622 } 1623 1624 if (num == 2 || (num == 1 && comp0[lencomp0 - 1] == '$')) { 1625 const char *host; 1626 1627 attr = kODAttributeTypeRecordName; 1628 1629 ret = map_service(context, principal, &eprinc); 1630 if (ret) { 1631 ret = HDB_ERR_NOENTRY; 1632 goto out; 1633 } 1634 principal = eprinc; 1635 1636 host = krb5_principal_get_comp_string(context, principal, 1); 1637 comp0 = krb5_principal_get_comp_string(context, principal, 0); 1638 1639 if (strcasecmp(KRB5_TGS_NAME, comp0) == 0) { 1640 node = "_krbtgt"; 1641 rtype = kODRecordTypeUsers; 1642 } else if (num == 2 && strcmp(KRB5_WELLKNOWN_NAME, comp0) == 0 && strcmp(KRB5_FAST_COOKIE, host) == 0) { 1643 node = "_krbfast"; 1644 rtype = kODRecordTypeUsers; 1645 } else if (num == 2 && is_lkdc(d, host)) { 1646 node = "localhost"; 1647 rtype = kODRecordTypeComputers; 1648 } else if (num == 2) { 1649 node = host; 1650 rtype = kODRecordTypeComputers; 1651 } else { 1652 node = comp0; 1653 rtype = kODRecordTypeComputers; 1654 } 1655 1656 } else if (num == 1) { 1657 attr = kODAttributeTypeRecordName; 1658 rtype = kODRecordTypeUsers; 1659 upflags = KRB5_PRINCIPAL_UNPARSE_NO_REALM; 1660 upflags |= KRB5_PRINCIPAL_UNPARSE_DISPLAY; 1661 } else { 1662 ret = HDB_ERR_NOENTRY; 1663 goto out; 1664 } 1665 } else { 1666 /* managed realm, lets check for real entries */ 1667 1668 ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser); 1669 if (ret) 1670 goto out; 1671 1672 rtype = d->inComputerOrUsers; 1673 matchtype = kODMatchCompoundExpression; 1674 attr = NULL; 1675 1676 queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"), 1677 CFSTR("dsAttrTypeNative:KerberosUserName"), 1678 kuser, 1679 CFSTR("dsAttrTypeNative:KerberosServerName"), 1680 kuser); 1681 } 1682 1683 if (queryString == NULL) { 1684 if (node) { 1685 queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8); 1686 } else { 1687 ret = krb5_unparse_name_flags(context, principal, upflags, &kuser); 1688 if (ret) 1689 goto out; 1690 1691 queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8); 1692 } 1693 } 1694 if (queryString == NULL) { 1695 ret = ENOMEM; 1696 goto out; 1697 } 1698 1699 do { 1700 CFErrorRef error = NULL; 1701 1702 query = ODQueryCreateWithNode(NULL, d->rootNode, 1703 rtype, attr, 1704 matchtype, 1705 queryString, 1706 d->inNodeAttributes, 1707 1, 1708 NULL); 1709 if (query == NULL) { 1710 CFRelease(queryString); 1711 ret = ENOMEM; 1712 goto out; 1713 } 1714 1715 res = ODQueryCopyResults(query, FALSE, &error); 1716 if (res == NULL && error == NULL) 1717 tryAgain = 0; 1718 if (error) 1719 CFRelease(error); 1720 } while(res == NULL && tryAgainP(context, &tryAgain, NULL)); 1721 1722 CFRelease(queryString); 1723 if (res == NULL) { 1724 ret = HDB_ERR_NOENTRY; 1725 goto out; 1726 } 1727 1728 if (CFArrayGetCount(res) == 0 && node && createp) { 1729 1730 queryString = CFStringCreateWithCString(NULL, node, kCFStringEncodingUTF8); 1731 1732 cfRecord = ODNodeCopyRecord(d->rootNode, rtype, queryString, d->inNodeAttributes, NULL); 1733 if (cfRecord == NULL) { 1734 CFMutableDictionaryRef attributes; 1735 1736 attributes = CFDictionaryCreateMutable(NULL, 0, 1737 &kCFTypeDictionaryKeyCallBacks, 1738 &kCFTypeDictionaryValueCallBacks); 1739 1740 cfRecord = ODNodeCreateRecord(d->rootNode, 1741 rtype, 1742 queryString, 1743 attributes, 1744 NULL); 1745 CFRelease(attributes); 1746 } 1747 CFRelease(queryString); 1748 1749 if (cfRecord == NULL) { 1750 ret = HDB_ERR_UK_RERROR; 1751 goto out; 1752 } 1753 1754 if (createdp) 1755 *createdp = TRUE; 1756 1757 } else if (CFArrayGetCount(res) == 1) { 1758 cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0); 1759 if (cfRecord == NULL) { 1760 ret = HDB_ERR_NOENTRY; 1761 goto out; 1762 } 1763 CFRetain(cfRecord); 1764 1765 } else { 1766 ret = HDB_ERR_NOENTRY; 1767 goto out; 1768 } 1769 1770 *result = cfRecord; 1771 1772 out: 1773 if (kuser) 1774 free(kuser); 1775 if (eprinc) 1776 krb5_free_principal(context, eprinc); 1777 if (res) 1778 CFRelease(res); 1779 if (query) 1780 CFRelease(query); 1781 return ret; 1782} 1783 1784static krb5_error_code 1785server_locate_record(krb5_context context, hdb_od d, krb5_principal principal, 1786 unsigned flags, int createp, int *createdp, ODRecordRef *result) 1787{ 1788 ODRecordRef cfRecord = NULL; 1789 krb5_error_code ret; 1790 ODMatchType matchtype; 1791 CFStringRef queryString = NULL; 1792 ODQueryRef query = NULL; 1793 CFArrayRef res = NULL; 1794 CFStringRef attr; 1795 char *kuser = NULL; 1796 CFStringRef client_key, server_key; 1797 int tryAgain = MAX_TRIES; 1798 1799 *result = NULL; 1800 1801 ret = krb5_unparse_name_flags(context, principal, KRB5_PRINCIPAL_UNPARSE_DISPLAY, &kuser); 1802 if (ret) 1803 goto out; 1804 1805 client_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalName"); 1806 server_key = CFSTR("dsAttrTypeNative:draft-krbPrincipalAliases"); 1807 1808 /* XXX support referrals here */ 1809 1810 if (flags & HDB_F_GET_KRBTGT) { 1811 matchtype = kODMatchEqualTo; 1812 attr = client_key; 1813 queryString = CFStringCreateWithCString(NULL, kuser, kCFStringEncodingUTF8); 1814 } else { 1815 matchtype = kODMatchCompoundExpression; 1816 attr = NULL; 1817 queryString = CFStringCreateWithFormat(NULL, NULL, CFSTR("(|(%@=%s)(%@=%s))"), 1818 server_key, 1819 kuser, 1820 client_key, 1821 kuser); 1822 } 1823 if (queryString == NULL) { 1824 ret = HDB_ERR_NOENTRY; 1825 goto out; 1826 } 1827 1828 do { 1829 CFErrorRef error = NULL; 1830 1831 query = ODQueryCreateWithNode(NULL, d->rootNode, 1832 kODRecordTypeUserAuthenticationData, 1833 attr, 1834 matchtype, 1835 queryString, 1836 d->inNodeAttributes, 1837 2, 1838 NULL); 1839 if (query == NULL) { 1840 CFRelease(queryString); 1841 ret = ENOMEM; 1842 goto out; 1843 } 1844 1845 res = ODQueryCopyResults(query, FALSE, &error); 1846 if (res == NULL && error == NULL) 1847 tryAgain = 0; 1848 if (error) 1849 CFRelease(error); 1850 } while(res == NULL && tryAgainP(context, &tryAgain, NULL)); 1851 CFRelease(queryString); 1852 1853 if (res == NULL) { 1854 ret = HDB_ERR_NOENTRY; 1855 goto out; 1856 } 1857 1858 1859 CFIndex count = CFArrayGetCount(res); 1860 if (count == 0 && createp) { 1861 CFMutableDictionaryRef attributes; 1862 CFStringRef name; 1863 CFUUIDRef uuid; 1864 1865 uuid = CFUUIDCreate(NULL); 1866 if (uuid == NULL) { 1867 ret = HDB_ERR_NOENTRY; 1868 goto out; 1869 } 1870 1871 name = CFUUIDCreateString(NULL, uuid); 1872 CFRelease(uuid); 1873 if (name == NULL) { 1874 ret = HDB_ERR_NOENTRY; 1875 goto out; 1876 } 1877 1878 attributes = CFDictionaryCreateMutable(NULL, 0, 1879 &kCFTypeDictionaryKeyCallBacks, 1880 &kCFTypeDictionaryValueCallBacks); 1881 if (attributes == NULL) { 1882 CFRelease(name); 1883 ret = HDB_ERR_NOENTRY; 1884 goto out; 1885 } 1886 1887 cfRecord = ODNodeCreateRecord(d->rootNode, 1888 kODRecordTypeUserAuthenticationData, 1889 name, 1890 attributes, 1891 NULL); 1892 CFRelease(name); 1893 CFRelease(attributes); 1894 if (cfRecord == NULL) { 1895 ret = HDB_ERR_NOENTRY; 1896 goto out; 1897 } 1898 1899 if (createdp) 1900 *createdp = 1; 1901 1902 } else if (count != 1) { 1903 ret = HDB_ERR_NOENTRY; 1904 goto out; 1905 } else { 1906 cfRecord = (ODRecordRef) CFArrayGetValueAtIndex(res, 0); 1907 if (cfRecord == NULL) { 1908 ret = HDB_ERR_NOENTRY; 1909 goto out; 1910 } 1911 CFRetain(cfRecord); 1912 } 1913 *result = cfRecord; 1914 1915out: 1916 if (res) 1917 CFRelease(res); 1918 if (query) 1919 CFRelease(query); 1920 if (kuser) 1921 free(kuser); 1922 1923 return ret; 1924} 1925 1926 1927static krb5_error_code 1928hod_lkdc_fetch(krb5_context context, HDB * db, krb5_const_principal principal, 1929 unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry) 1930{ 1931 krb5_principal eprincipal = NULL; 1932 hdb_od d = (hdb_od)db->hdb_db; 1933 ODRecordRef cfRecord = NULL; 1934 krb5_error_code ret; 1935 1936 ret = krb5_copy_principal(context, principal, &eprincipal); 1937 if (ret) 1938 goto out; 1939 1940 ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord); 1941 if (ret) 1942 goto out; 1943 1944 if (is_lkdc(d, eprincipal->realm)) 1945 map_lkdc_principal(context, eprincipal, wellknown_lkdc, d->LKDCRealm); 1946 1947 /* set the principal to the username for users in pku2u and lkdc */ 1948 if (krb5_principal_is_pku2u(context, eprincipal) || 1949 (krb5_principal_is_lkdc(context, eprincipal) && krb5_principal_get_num_comp(context, eprincipal) == 1)) 1950 { 1951 CFStringRef name = ODRecordGetRecordName(cfRecord); 1952 char *str; 1953 1954 str = rk_cfstring2cstring(name); 1955 if (str == NULL) { 1956 ret = ENOENT; 1957 goto out; 1958 } 1959 1960 ret = krb5_make_principal(context, &entry->entry.principal, 1961 eprincipal->realm, str, NULL); 1962 free(str); 1963 if (ret) 1964 goto out; 1965 } else { 1966 ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal); 1967 if (ret) 1968 goto out; 1969 } 1970 1971 ret = od_record2entry(context, db, cfRecord, flags, entry); 1972 if (ret) 1973 goto out; 1974 1975 out: 1976 if (eprincipal) 1977 krb5_free_principal(context, eprincipal); 1978 if (cfRecord) 1979 CFRelease(cfRecord); 1980 if (ret) { 1981 free_hdb_entry(&entry->entry); 1982 memset(&entry->entry, 0, sizeof(entry->entry)); 1983 } 1984 1985 return ret; 1986} 1987 1988static krb5_error_code 1989hod_server_fetch(krb5_context context, HDB * db, krb5_const_principal principal, 1990 unsigned flags, krb5_kvno kvno, hdb_entry_ex * entry) 1991{ 1992 krb5_principal eprincipal = NULL; 1993 hdb_od d = (hdb_od)db->hdb_db; 1994 ODRecordRef cfRecord = NULL; 1995 krb5_error_code ret; 1996 1997 1998 ret = krb5_copy_principal(context, principal, &eprincipal); 1999 if (ret) 2000 goto out; 2001 2002 ret = d->locate_record(context, d, eprincipal, flags, FALSE, NULL, &cfRecord); 2003 if (ret) 2004 goto out; 2005 2006 ret = krb5_copy_principal(context, eprincipal, &entry->entry.principal); 2007 if (ret) 2008 goto out; 2009 2010 ret = od_record2entry(context, db, cfRecord, flags, entry); 2011 if (ret) 2012 goto out; 2013 2014out: 2015 if (cfRecord) 2016 CFRelease(cfRecord); 2017 if (eprincipal) 2018 krb5_free_principal(context, eprincipal); 2019 2020 if (ret) { 2021 free_hdb_entry(&entry->entry); 2022 memset(&entry->entry, 0, sizeof(entry->entry)); 2023 } 2024 2025 return ret; 2026} 2027 2028 2029static krb5_error_code 2030hod_store(krb5_context context, HDB * db, unsigned flags, 2031 hdb_entry_ex * entry) 2032{ 2033 krb5_principal eprincipal = NULL; 2034 hdb_od d = (hdb_od)db->hdb_db; 2035 ODRecordRef record = NULL; 2036 krb5_error_code ret; 2037 int i, created = 0; 2038 2039 ret = krb5_copy_principal(context, entry->entry.principal, &eprincipal); 2040 if (ret) 2041 goto out; 2042 2043 ret = d->locate_record(context, d, eprincipal, flags, (flags & HDB_F_REPLACE) == 0, &created, &record); 2044 if (ret) 2045 goto out; 2046 2047 for (i = 0; i < num_keys; i++) { 2048 if (keys[i].hdb2od == NULL) 2049 continue; 2050 2051 /* logical XOR between server values and LKDC connection */ 2052 if ((!!(keys[i].flags & SERVER_VALUE)) ^ (!d->LKDCRealm)) 2053 continue; 2054 2055 ret = (*keys[i].hdb2od)(context, d, keys[i].name, flags, entry, record); 2056 if (ret) 2057 goto out; 2058 } 2059 2060 if ((flags & HDB_F_CHANGE_PASSWORD) == 0) { 2061 ret = hdb_seal_keys(context, db, &entry->entry); 2062 if(ret) 2063 goto out; 2064 } 2065 2066 ODRecordSynchronize(record, NULL); 2067 2068 out: 2069 if (record) { 2070 if (ret && created) 2071 ODRecordDelete(record, NULL); 2072 CFRelease(record); 2073 } 2074 if (eprincipal) 2075 krb5_free_principal(context, eprincipal); 2076 2077 return ret; 2078} 2079 2080static krb5_error_code 2081hod_remove(krb5_context context, HDB *db, krb5_const_principal principal) 2082{ 2083 krb5_principal eprincipal = NULL; 2084 hdb_od d = (hdb_od)db->hdb_db; 2085 ODRecordRef record = NULL; 2086 CFMutableArrayRef array = NULL; 2087 krb5_error_code ret; 2088 int i; 2089 2090 ret = krb5_copy_principal(context, principal, &eprincipal); 2091 if (ret) 2092 goto out; 2093 2094 ret = d->locate_record(context, d, eprincipal, 0, FALSE, NULL, &record); 2095 if (ret) 2096 goto out; 2097 2098 array = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); 2099 if (array == NULL) { 2100 ret = ENOMEM; 2101 goto out; 2102 } 2103 2104 for (i = 0; i < num_keys; i++) { 2105 2106 if ((keys[i].flags & DELETE_KEY) == 0) 2107 continue; 2108 2109 if (!ODRecordSetValue(record, keys[i].name, array, NULL)) { 2110 ret = HDB_ERR_UK_SERROR; 2111 break; 2112 } 2113 } 2114 2115 ODRecordSynchronize(record, NULL); 2116 2117 out: 2118 if (eprincipal) 2119 krb5_free_principal(context, eprincipal); 2120 if (array) 2121 CFRelease(array); 2122 if (record) 2123 CFRelease(record); 2124 return ret; 2125} 2126 2127 2128static krb5_error_code 2129hod_get_realms(krb5_context context, HDB *db, krb5_realm **realms) 2130{ 2131 hdb_od d = (hdb_od)db->hdb_db; 2132 2133 *realms = NULL; 2134 2135 if (d->LKDCRealm) { 2136 *realms = calloc(2, sizeof(realms[0])); 2137 if (*realms == NULL) 2138 return ENOMEM; 2139 (*realms)[0] = strdup(d->LKDCRealm); 2140 if ((*realms)[0] == NULL) { 2141 free(*realms); 2142 *realms = NULL; 2143 return ENOMEM; 2144 } 2145 (*realms)[1] = NULL; 2146 } 2147 2148 return 0; 2149} 2150 2151static void 2152update_ntlm(SCDynamicStoreRef store, CFArrayRef changedKeys, void *info) 2153{ 2154 hdb_od d = info; 2155 CFDictionaryRef settings; 2156 CFStringRef n; 2157 2158 if (store == NULL || info == NULL) 2159 return; 2160 2161 settings = (CFDictionaryRef)SCDynamicStoreCopyValue(store, CFSTR("com.apple.smb")); 2162 if (settings == NULL) 2163 return; 2164 2165 n = CFDictionaryGetValue(settings, CFSTR("NetBIOSName")); 2166 if (n == NULL || CFGetTypeID(n) != CFStringGetTypeID()) 2167 goto fin; 2168 2169 if (d->ntlmDomain) 2170 free(d->ntlmDomain); 2171 d->ntlmDomain = rk_cfstring2cstring(n); 2172 strupr(d->ntlmDomain); 2173 2174fin: 2175 CFRelease(settings); 2176 return; 2177} 2178 2179static void 2180ntlm_notification(hdb_od d) 2181{ 2182 SCDynamicStoreRef store; 2183 dispatch_queue_t queue; 2184 SCDynamicStoreContext context; 2185 2186 memset(&context, 0, sizeof(context)); 2187 context.info = (void*)d; 2188 2189 store = SCDynamicStoreCreate(kCFAllocatorDefault, CFSTR("com.apple.Kerberos.hdb-od"), update_ntlm, (void *)&context); 2190 if (store == NULL) 2191 return; 2192 2193 CFTypeRef key[] = {CFSTR("com.apple.smb")}; 2194 CFArrayRef nkeys = CFArrayCreate(kCFAllocatorDefault, key, 1, NULL); 2195 SCDynamicStoreSetNotificationKeys(store, nkeys, NULL); 2196 CFRelease(nkeys); 2197 2198 queue = dispatch_queue_create("com.apple.hdb-od.ntlm-name", NULL); 2199 if (queue == NULL) { 2200 CFRelease(store); 2201 errx(1, "dispatch_queue_create"); 2202 } 2203 2204 SCDynamicStoreSetDispatchQueue(store, queue); 2205 CFRelease(store); 2206 2207 dispatch_sync(queue, ^{ update_ntlm(store, NULL, d); }); 2208} 2209 2210static krb5_error_code 2211hod_get_ntlm_domain(krb5_context context, struct HDB *db, char **name) 2212{ 2213 hdb_od d = (hdb_od)db->hdb_db; 2214 if (d->ntlmDomain == NULL) { 2215 krb5_set_error_message(context, EINVAL, "no ntlm domain"); 2216 return EINVAL; 2217 } 2218 *name = strdup(d->ntlmDomain); 2219 return 0; 2220} 2221 2222 2223#ifdef __APPLE_PRIVATE__ 2224 2225static struct { 2226 typeof(DSChangePasswordWithPolicy) *_HDBDSChangePasswordWithPolicy; 2227 typeof(DSUpdateLoginStatus) *_HDBDSUpdateLoginStatus; 2228} ds_ptrs; 2229 2230static void 2231DirectoryServer_framework(void) 2232{ 2233 static dispatch_once_t once; 2234 2235 /* 2236 * Do softlinking to avoid having a dependency on DirectoryServer framework 2237 */ 2238 2239 dispatch_once(&once, ^{ 2240 void *ptr = dlopen("/System/Library/PrivateFrameworks/DirectoryServer.framework/DirectoryServer", RTLD_LAZY); 2241 if (ptr) { 2242 ds_ptrs._HDBDSChangePasswordWithPolicy = dlsym(ptr, "DSChangePasswordWithPolicy"); 2243 ds_ptrs._HDBDSUpdateLoginStatus = dlsym(ptr, "DSUpdateLoginStatus"); 2244 } 2245 }); 2246} 2247 2248static krb5_error_code 2249hod_password(krb5_context context, struct HDB *db, hdb_entry_ex *entry, const char *password, int flags) 2250{ 2251 char *user; 2252 int ret; 2253 2254 DirectoryServer_framework(); 2255 2256 if (ds_ptrs._HDBDSChangePasswordWithPolicy == NULL) 2257 return EINVAL; 2258 2259 if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1) 2260 return EINVAL; 2261 2262 user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0); 2263 2264 ret = ds_ptrs._HDBDSChangePasswordWithPolicy(user, password, (flags & HDB_PWD_CONDITIONAL) == 0); 2265 if (ret != 0) { 2266 krb5_set_error_message(context, EINVAL, "DSPasswordServer rejected password"); 2267 return EINVAL; 2268 } 2269 2270 return 0; 2271} 2272 2273static krb5_error_code 2274hod_auth_status(krb5_context context, struct HDB *db, hdb_entry_ex *entry, int status) 2275{ 2276 char *user; 2277 2278 DirectoryServer_framework(); 2279 2280 if (ds_ptrs._HDBDSUpdateLoginStatus == NULL) 2281 return EINVAL; 2282 2283 if (krb5_principal_get_num_comp(context, entry->entry.principal) != 1) 2284 return EINVAL; 2285 2286 user = (char *)krb5_principal_get_comp_string(context, entry->entry.principal, 0); 2287 2288 /* DS and HDB status code matches, so just pass them though */ 2289 ds_ptrs._HDBDSUpdateLoginStatus(user, status); 2290 2291 return 0; 2292} 2293 2294#endif 2295 2296 2297krb5_error_code 2298hdb_od_create(krb5_context context, HDB ** db, const char *arg) 2299{ 2300 CFMutableArrayRef attrs; 2301 ODNodeRef localRef = NULL; 2302 ODRecordRef kdcConfRef = NULL; 2303 CFArrayRef data = NULL; 2304 krb5_error_code ret; 2305 hdb_od d = NULL; 2306 char *path = NULL; 2307 int i; 2308 2309 *db = calloc(1, sizeof(**db)); 2310 if (*db == NULL) { 2311 ret = ENOMEM; 2312 krb5_set_error_message(context, ret, "malloc: out of memory"); 2313 goto out; 2314 } 2315 memset(*db, 0, sizeof(**db)); 2316 2317 path = strdup(arg); 2318 if (path == NULL) { 2319 ret = ENOMEM; 2320 krb5_set_error_message(context, ret, "malloc: out of memory"); 2321 goto out; 2322 } 2323 2324 d = calloc(1, sizeof(*d)); 2325 if (d == NULL) { 2326 ret = ENOMEM; 2327 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 2328 goto out; 2329 } 2330 2331 { 2332 char *p = strchr(path, '&'); 2333 if (p) { 2334 *p++ = '\0'; 2335 d->restoreRoot = CFStringCreateWithCString(NULL, p, kCFStringEncodingUTF8); 2336 } 2337 } 2338 2339 d->rootName = CFStringCreateWithCString(NULL, path, kCFStringEncodingUTF8); 2340 if (d == NULL) { 2341 ret = ENOMEM; 2342 krb5_set_error_message(context, ENOMEM, "malloc: out of memory"); 2343 goto out; 2344 } 2345 2346 attrs = CFArrayCreateMutable(NULL, num_keys, &kCFTypeArrayCallBacks); 2347 for (i = 0; i < num_keys; i++) 2348 CFArrayAppendValue(attrs, keys[i].name); 2349 d->inNodeAttributes = attrs; 2350 2351 attrs = CFArrayCreateMutable(NULL, 1, &kCFTypeArrayCallBacks); 2352 CFArrayAppendValue(attrs, kRealName); 2353 d->inKDCAttributes = attrs; 2354 2355 attrs = CFArrayCreateMutable(NULL, 2, &kCFTypeArrayCallBacks); 2356 CFArrayAppendValue(attrs, kODRecordTypeComputers); 2357 CFArrayAppendValue(attrs, kODRecordTypeUsers); 2358 2359 d->inComputerOrUsers = attrs; 2360 2361 (*db)->hdb_db = d; 2362 2363 (*db)->hdb_capability_flags = HDB_CAP_F_HANDLE_ENTERPRISE_PRINCIPAL; 2364 (*db)->hdb_master_key_set = 0; 2365 (*db)->hdb_openp = 0; 2366 (*db)->hdb_open = hod_open; 2367 (*db)->hdb_close = hod_close; 2368 (*db)->hdb_store = hod_store; 2369 (*db)->hdb_remove = hod_remove; 2370 (*db)->hdb_firstkey = hod_firstkey; 2371 (*db)->hdb_nextkey = hod_nextkey; 2372 (*db)->hdb_lock = hod_lock; 2373 (*db)->hdb_unlock = hod_unlock; 2374 (*db)->hdb_rename = NULL; 2375 (*db)->hdb__get = NULL; 2376 (*db)->hdb__put = NULL; 2377 (*db)->hdb__del = NULL; 2378 (*db)->hdb_destroy = hod_destroy; 2379 (*db)->hdb_get_realms = hod_get_realms; 2380 (*db)->hdb_get_ntlm_domain = hod_get_ntlm_domain; 2381 2382 2383 /* 2384 * The /Local/Default realm is the LKDC realm, so lets pick up the 2385 * LKDC configuration that we will use later. 2386 */ 2387 2388 if (strncmp(path, "/Local/", 7) == 0) { 2389 2390 /* 2391 * Pull out KerberosLocalKDC configuration 2392 */ 2393 2394 ret = nodeCreateWithName(context, d, &localRef); 2395 if (ret) 2396 goto out; 2397 2398 kdcConfRef = ODNodeCopyRecord(localRef, kODRecordTypeConfiguration, 2399 CFSTR("KerberosKDC"), 2400 d->inKDCAttributes, NULL); 2401 if (kdcConfRef == NULL) { 2402 ret = HDB_ERR_NOENTRY; 2403 krb5_set_error_message(context, ret, "Failed to find KerberosKDC node"); 2404 goto out; 2405 } 2406 2407 data = ODRecordCopyValues(kdcConfRef, kRealName, NULL); 2408 if (data == NULL) { 2409 ret = HDB_ERR_NOENTRY; 2410 krb5_set_error_message(context, ret, "Failed to copy RealName from KerberosKDC node"); 2411 goto out; 2412 } 2413 2414 if (CFArrayGetCount(data) != 1) { 2415 ret = HDB_ERR_NOENTRY; 2416 krb5_set_error_message(context, ret, "Found RealName %d from KerberosKDC", 2417 (int)CFArrayGetCount(data)); 2418 goto out; 2419 } 2420 d->LKDCRealm = rk_cfstring2cstring((CFStringRef)CFArrayGetValueAtIndex(data, 0)); 2421 if (d->LKDCRealm == NULL) { 2422 ret = HDB_ERR_NOENTRY; 2423 krb5_set_error_message(context, ret, "failed to find realm"); 2424 goto out; 2425 } 2426 2427 CFRelease(data); data = NULL; 2428 CFRelease(kdcConfRef); kdcConfRef = NULL; 2429 CFRelease(localRef); localRef = NULL; 2430 2431 ntlm_notification(d); 2432 2433 (*db)->hdb_fetch_kvno = hod_lkdc_fetch; 2434 d->locate_record = lkdc_locate_record; 2435 2436 } else { 2437 /* hod_password interface is stupid, pipe can fail and we might not expect it to */ 2438 /* Not the right place for this. */ 2439#ifdef HAVE_SIGACTION 2440 struct sigaction sa; 2441 2442 sa.sa_flags = 0; 2443 sa.sa_handler = SIG_IGN; 2444 sigemptyset(&sa.sa_mask); 2445 2446 sigaction(SIGPIPE, &sa, NULL); 2447#else 2448 signal(SIGPIPE, SIG_IGN); 2449#endif /* HAVE_SIGACTION */ 2450 2451 (*db)->hdb_capability_flags |= HDB_CAP_F_HANDLE_PASSWORDS; 2452#ifdef __APPLE_PRIVATE__ 2453 (*db)->hdb_password = hod_password; 2454#endif 2455 (*db)->hdb_fetch_kvno = hod_server_fetch; 2456 d->locate_record = server_locate_record; 2457 2458 (*db)->hdb_auth_status = hod_auth_status; 2459 } 2460 2461 2462 free(path); 2463 2464 return 0; 2465 2466 out: 2467 if (path) 2468 free(path); 2469 if (data) 2470 CFRelease(data); 2471 if (kdcConfRef) 2472 CFRelease(kdcConfRef); 2473 if (localRef) 2474 CFRelease(localRef); 2475 2476 if (*db) { 2477 free(*db); 2478 *db = NULL; 2479 } 2480 if (d) { 2481 if (d->rootName) 2482 CFRelease(d->rootName); 2483 if (d->restoreRoot) 2484 CFRelease(d->restoreRoot); 2485 free(d); 2486 } 2487 2488 return ret; 2489} 2490 2491#endif 2492