1/* 2 * Copyright (c) 2010 Apple Inc. All rights reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 */ 23 24#include "NetworkAuthenticationHelper.h" 25#include "NetworkAuthenticationHelperGSS.h" 26#include "KerberosHelper.h" 27#include "KerberosHelperContext.h" 28 29#include <Heimdal/krb5.h> 30#include <Heimdal/hx509.h> 31 32#include <GSS/gssapi.h> 33#include <GSS/gssapi_ntlm.h> 34#include <GSS/gssapi_krb5.h> 35#include <GSS/gssapi_spi.h> 36 37#include <Kernel/gssd/gssd_mach_types.h> 38 39#include <CoreFoundation/CFRuntime.h> 40#include <Security/Security.h> 41#include <Security/SecCertificatePriv.h> 42#include <CommonCrypto/CommonDigest.h> 43 44#include <CoreServices/CoreServices.h> 45#include <CoreServices/CoreServicesPriv.h> 46 47#include <asl.h> 48 49#include "DeconstructServiceName.h" 50#include "utils.h" 51 52static void add_user_selections(NAHRef na); 53 54 55const CFStringRef kNAHServiceAFPServer = CFSTR("afpserver"); 56const CFStringRef kNAHServiceCIFSServer = CFSTR("cifs"); 57const CFStringRef kNAHServiceHostServer = CFSTR("host"); 58const CFStringRef kNAHServiceVNCServer = CFSTR("vnc"); 59 60static const char *nah_created = "nah-created"; 61 62static bool nah_use_gss_uam = true; 63static bool nah_vnc_support_iakerb = true; 64static dispatch_once_t init_globals; 65 66enum NAHMechType { 67 NO_MECH = 0, 68 GSS_KERBEROS, 69 GSS_KERBEROS_U2U, 70 GSS_KERBEROS_IAKERB, 71 GSS_KERBEROS_PKU2U, 72 GSS_NTLM, 73 GSS_SPNEGO 74}; 75 76struct NAHSelectionData { 77 CFRuntimeBase base; 78 NAHRef na; 79 int have_cred; 80 81 enum NAHMechType mech; 82 CFStringRef client; 83 CFStringRef clienttype; 84 CFStringRef server; 85 CFStringRef servertype; 86 87 SecIdentityRef certificate; 88 bool spnego; 89 90 CFStringRef inferredLabel; 91 92 krb5_ccache ccache; 93}; 94 95struct NAHData { 96 CFRuntimeBase base; 97 CFAllocatorRef alloc; 98 99 /* Input */ 100 101 CFMutableStringRef hostname; 102 CFMutableStringRef lchostname; 103 CFStringRef service; 104 CFStringRef username; 105 CFStringRef specificname; 106 CFDictionaryRef servermechs; 107 108 CFStringRef spnegoServerName; 109 110 /* credentials */ 111 CFArrayRef x509identities; 112 CFStringRef password; 113 114 CFArrayRef mechs; 115 116 /* intermediate results */ 117 dispatch_queue_t q; 118 dispatch_queue_t bgq; 119 krb5_context context; 120 hx509_context hxctx; 121 122 /* final result */ 123 124 CFMutableArrayRef selections; 125}; 126 127static void nalog(int level, CFStringRef fmt, ...) 128 __attribute__((format(CFString, 2, 3))); 129 130#define CFRELEASE(x) do { if ((x)) { CFRelease((x)); (x) = NULL; } } while(0) 131 132/* 133 * 134 */ 135 136CFStringRef 137NAHCopyMMeUserNameFromCertificate(SecCertificateRef cert) 138{ 139 unsigned char digest[CC_SHA1_DIGEST_LENGTH]; 140 char str[CC_SHA1_DIGEST_LENGTH * 2 + 1]; 141 CFDataRef certData; 142 CC_SHA1_CTX ctx; 143 char *cpOut; 144 unsigned dex; 145 146 certData = SecCertificateCopyData(cert); 147 CFRelease(cert); 148 if (NULL == certData) 149 return NULL; 150 151 CC_SHA1_Init(&ctx); 152 CC_SHA1_Update(&ctx, CFDataGetBytePtr(certData), CFDataGetLength(certData)); 153 CC_SHA1_Final(digest, &ctx); 154 155 CFRelease(certData); 156 157 cpOut = str; 158 159 for(dex = 0; dex < CC_SHA1_DIGEST_LENGTH; dex++) { 160 snprintf(cpOut, 3, "%02X", (unsigned)(digest[dex])); 161 cpOut += 2; 162 } 163 164 return CFStringCreateWithCString(NULL, str, kCFStringEncodingASCII); 165} 166 167/* 168 * 169 */ 170 171static char * 172cf2cstring(CFStringRef inString) 173{ 174 char *string = NULL; 175 CFIndex length; 176 177 string = (char *) CFStringGetCStringPtr(inString, kCFStringEncodingUTF8); 178 if (string) 179 return strdup(string); 180 181 length = CFStringGetMaximumSizeForEncoding(CFStringGetLength(inString), 182 kCFStringEncodingUTF8) + 1; 183 string = malloc (length); 184 if (string == NULL) 185 return NULL; 186 if (!CFStringGetCString(inString, string, length, kCFStringEncodingUTF8)) { 187 free (string); 188 return NULL; 189 } 190 return string; 191} 192 193/* 194 * 195 */ 196 197static void 198nalog(int level, CFStringRef fmt, ...) 199{ 200 CFStringRef str; 201 va_list ap; 202 char *s; 203 204 va_start(ap, fmt); 205 str = CFStringCreateWithFormatAndArguments(NULL, 0, fmt, ap); 206 va_end(ap); 207 208 if (str == NULL) 209 return; 210 211 if (__KRBCreateUTF8StringFromCFString (str, &s) == 0) { 212 asl_log(NULL, NULL, level, "%s", s); 213 __KRBReleaseUTF8String(s); 214 } 215 CFRelease(str); 216} 217 218static void 219naselrelease(NAHSelectionRef nasel) 220{ 221 CFRELEASE(nasel->client); 222 CFRELEASE(nasel->clienttype); 223 CFRELEASE(nasel->server); 224 CFRELEASE(nasel->servertype); 225 if (nasel->ccache) 226 krb5_cc_close(nasel->na->context, nasel->ccache); 227 CFRELEASE(nasel->certificate); 228 CFRELEASE(nasel->inferredLabel); 229} 230 231static CFStringRef naseldebug(NAHSelectionRef nasel) CF_RETURNS_RETAINED; 232static CFStringRef naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions) CF_RETURNS_RETAINED; 233 234 235 236 237static CFStringRef 238naseldebug(NAHSelectionRef nasel) 239{ 240 CFStringRef mech = NAHSelectionGetInfoForKey(nasel, kNAHMechanism); 241 CFStringRef innermech = NAHSelectionGetInfoForKey(nasel, kNAHInnerMechanism); 242 243 return CFStringCreateWithFormat(NULL, NULL, 244 CFSTR("<NetworkAuthenticationSelection: " 245 "%@<%@>, %@ %@ spnego: %s>"), 246 mech, innermech, 247 nasel->client, 248 nasel->server, 249 nasel->spnego ? "yes" : "no"); 250} 251 252static CFStringRef 253naselformatting(CFTypeRef cf, CFDictionaryRef formatOptions) 254{ 255 return naseldebug((NAHSelectionRef)cf); 256} 257 258const CFStringRef kNAHErrorDomain = CFSTR("com.apple.NetworkAuthenticationHelper"); 259 260static bool 261updateError(CFAllocatorRef alloc, CFErrorRef *error, CFIndex errorcode, CFStringRef fmt, ...) 262{ 263 const void *keys[1] = { kCFErrorDescriptionKey }; 264 void *values[1]; 265 va_list va; 266 267 if (error == NULL) 268 return false; 269 270 va_start(va, fmt); 271 values[0] = (void *)CFStringCreateWithFormatAndArguments(alloc, NULL, fmt, va); 272 va_end(va); 273 if (values[0] == NULL) { 274 *error = NULL; 275 return false; 276 } 277 278 nalog(ASL_LEVEL_DEBUG, CFSTR("NAH: error: %@"), values[0]); 279 280 *error = CFErrorCreateWithUserInfoKeysAndValues(alloc, kNAHErrorDomain, errorcode, 281 (const void * const *)keys, 282 (const void * const *)values, 1); 283 CFRelease(values[0]); 284 285 return true; 286} 287 288static struct { 289 CFStringRef name; 290 enum NAHMechType mech; 291 gss_OID oid; 292} mechs[] = { 293 { CFSTR("Kerberos"), GSS_KERBEROS, GSS_KRB5_MECHANISM }, 294 { CFSTR("KerberosUser2User"), GSS_KERBEROS_U2U, NULL }, 295 { CFSTR("PKU2U"), GSS_KERBEROS_PKU2U, GSS_PKU2U_MECHANISM }, 296 { CFSTR("IAKerb"), GSS_KERBEROS_IAKERB, GSS_IAKERB_MECHANISM }, 297 { CFSTR("NTLM"), GSS_NTLM, GSS_NTLM_MECHANISM }, 298 { CFSTR("SPNEGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM }, 299 { CFSTR("SPENGO"), GSS_SPNEGO, GSS_SPNEGO_MECHANISM } 300}; 301 302static enum NAHMechType 303name2mech(CFStringRef name) 304{ 305 size_t n; 306 307 if (name == NULL) 308 return NO_MECH; 309 310 for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) { 311 if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo) 312 return mechs[n].mech; 313 } 314 return NO_MECH; 315} 316 317static gss_OID 318name2oid(CFStringRef name) 319{ 320 size_t n; 321 322 if (name == NULL) 323 return NO_MECH; 324 325 for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) { 326 if (CFStringCompare(mechs[n].name, name, kCFCompareCaseInsensitive) == kCFCompareEqualTo) 327 return mechs[n].oid; 328 } 329 return NULL; 330} 331 332static CFStringRef 333mech2name(enum NAHMechType mech) 334{ 335 size_t n; 336 for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) { 337 if (mechs[n].mech == mech) 338 return mechs[n].name; 339 } 340 return NULL; 341} 342 343 344static gss_OID 345mech2oid(enum NAHMechType mech) 346{ 347 size_t n; 348 for (n = 0; n < sizeof(mechs) / sizeof(mechs[0]); n++) { 349 if (mechs[n].mech == mech) 350 return mechs[n].oid; 351 } 352 return GSS_C_NO_OID; 353} 354 355 356static CFTypeID 357NAHSelectionGetTypeID(void) 358{ 359 static CFTypeID naselid = _kCFRuntimeNotATypeID; 360 static dispatch_once_t inited; 361 362 dispatch_once(&inited, ^{ 363 static const CFRuntimeClass naselclass = { 364 0, 365 "NetworkAuthenticationSelection", 366 NULL, 367 NULL, 368 (void(*)(CFTypeRef))naselrelease, 369 NULL, 370 NULL, 371 naselformatting, 372 (CFStringRef (*)(CFTypeRef))naseldebug 373 }; 374 naselid = _CFRuntimeRegisterClass(&naselclass); 375 }); 376 377 return naselid; 378} 379 380static NAHSelectionRef 381NAHSelectionAlloc(NAHRef na) 382{ 383 CFTypeID id = NAHSelectionGetTypeID(); 384 NAHSelectionRef nasel; 385 386 if (id == _kCFRuntimeNotATypeID) 387 return NULL; 388 389 nasel = (NAHSelectionRef)_CFRuntimeCreateInstance(na->alloc, id, sizeof(struct NAHSelectionData) - sizeof(CFRuntimeBase), NULL); 390 if (nasel == NULL) 391 return NULL; 392 393 nasel->na = na; 394 nasel->spnego = true; 395 396 return nasel; 397 398} 399 400static void 401nahrelease(NAHRef na) 402{ 403 CFRELEASE(na->hostname); 404 CFRELEASE(na->lchostname); 405 CFRELEASE(na->service); 406 CFRELEASE(na->username); 407 CFRELEASE(na->specificname); 408 CFRELEASE(na->servermechs); 409 CFRELEASE(na->spnegoServerName); 410 411 CFRELEASE(na->x509identities); 412 CFRELEASE(na->password); 413 414 CFRELEASE(na->mechs); 415 416 CFRELEASE(na->selections); 417 418 if (na->q) 419 dispatch_release(na->q); 420 if (na->context) 421 krb5_free_context(na->context); 422 if (na->hxctx) 423 hx509_context_free(&na->hxctx); 424} 425 426static CFTypeID 427NAGetTypeID(void) 428{ 429 static CFTypeID naid = _kCFRuntimeNotATypeID; 430 static dispatch_once_t inited; 431 432 dispatch_once(&inited, ^{ 433 static const CFRuntimeClass naclass = { 434 0, 435 "NetworkAuthentication", 436 NULL, 437 NULL, 438 (void(*)(CFTypeRef))nahrelease, 439 NULL, 440 NULL, 441 NULL, 442 NULL 443 }; 444 naid = _CFRuntimeRegisterClass(&naclass); 445 }); 446 447 return naid; 448} 449 450/* 451 * 452 */ 453 454static NAHRef 455NAAlloc(CFAllocatorRef alloc) 456{ 457 CFTypeID id = NAGetTypeID(); 458 NAHRef na; 459 460 if (id == _kCFRuntimeNotATypeID) 461 return NULL; 462 463 na = (NAHRef)_CFRuntimeCreateInstance(alloc, id, sizeof(struct NAHData) - sizeof(CFRuntimeBase), NULL); 464 if (na == NULL) 465 return NULL; 466 467 na->q = dispatch_queue_create("network-authentication", NULL); 468 na->bgq = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); 469 470 na->alloc = alloc; 471 472 return na; 473} 474 475static bool 476haveMech(NAHRef na, CFStringRef mech) 477{ 478 if (na->servermechs == NULL) 479 return false; 480 if (CFDictionaryGetValue(na->servermechs, mech) != NULL) 481 return true; 482 return false; 483} 484 485/* 486 * Status of selection 487 */ 488 489const CFStringRef kNAHSelectionHaveCredential = CFSTR("kNAHSelectionHaveCredential"); 490const CFStringRef kNAHSelectionUserPrintable = CFSTR("kNAHSelectionUserPrintable"); 491const CFStringRef kNAHClientPrincipal = CFSTR("kNAHClientPrincipal"); 492const CFStringRef kNAHServerPrincipal = CFSTR("kNAHServerPrincipal"); 493const CFStringRef kNAHMechanism = CFSTR("kNAHMechanism"); 494const CFStringRef kNAHInnerMechanism = CFSTR("kNAHInnerMechanism"); 495const CFStringRef kNAHCredentialType = CFSTR("kNAHCredentialType"); 496const CFStringRef kNAHUseSPNEGO = CFSTR("kNAHUseSPNEGO"); 497 498const CFStringRef kNAHClientNameType = CFSTR("kNAHClientNameType"); 499const CFStringRef kNAHClientNameTypeGSSD = CFSTR("kNAHClientNameTypeGSSD"); 500 501const CFStringRef kNAHServerNameType = CFSTR("kNAHServerNameType"); 502const CFStringRef kNAHServerNameTypeGSSD = CFSTR("kNAHServerNameTypeGSSD"); 503 504const CFStringRef kNAHNTUsername = CFSTR("kNAHNTUsername"); 505const CFStringRef kNAHNTServiceBasedName = CFSTR("kNAHNTServiceBasedName"); 506const CFStringRef kNAHNTKRB5PrincipalReferral = CFSTR("kNAHNTKRB5PrincipalReferral"); 507const CFStringRef kNAHNTKRB5Principal = CFSTR("kNAHNTKRB5Principal"); 508const CFStringRef kNAHNTUUID = CFSTR("kNAHNTUUID"); 509 510 511const CFStringRef kNAHInferredLabel = CFSTR("kNAHInferredLabel"); 512 513 514/* 515 * Add selection 516 */ 517 518enum { 519 USE_SPNEGO = 1, 520 FORCE_ADD = 2 521}; 522 523static NAHSelectionRef 524addSelection(NAHRef na, 525 CFStringRef client, 526 CFStringRef clienttype, 527 CFStringRef server, 528 CFStringRef servertype, 529 enum NAHMechType mech, 530 int *duplicate, 531 unsigned long flags) 532{ 533 NAHSelectionRef nasel; 534 int matching; 535 CFIndex n; 536 537 if (clienttype == NULL) 538 clienttype = kNAHNTUsername; 539 540 if (servertype == NULL) 541 servertype = kNAHNTServiceBasedName; 542 543 matching = (flags & FORCE_ADD) || (na->specificname == NULL) || CFStringHasPrefix(client, na->specificname); 544 545 nalog(ASL_LEVEL_DEBUG, CFSTR("addSelection: %@ (%d) %@ %@ %s %s"), 546 mech2name(mech), (int)mech, client, server, (flags & USE_SPNEGO) ? "SPNEGO" : "raw", 547 matching ? "matching" : "no-matching"); 548 549 /* If no matching, skip this credential */ 550 if (matching == 0) 551 return NULL; 552 553 /* check for dups */ 554 for (n = 0; n < CFArrayGetCount(na->selections); n++) { 555 nasel = (NAHSelectionRef)CFArrayGetValueAtIndex(na->selections, n); 556 if (nasel->mech != mech) 557 continue; 558 if (CFStringCompare(nasel->client, client, 0) != kCFCompareEqualTo) 559 continue; 560 if (nasel->server && server && CFStringCompare(nasel->server, server, 0) != kCFCompareEqualTo) 561 continue; 562 if (CFStringCompare(nasel->servertype, servertype, 0) != kCFCompareEqualTo) 563 continue; 564 if (duplicate) 565 *duplicate = 1; 566 return nasel; 567 } 568 if (duplicate) 569 *duplicate = 0; 570 571 nasel = NAHSelectionAlloc(na); 572 if (nasel == NULL) 573 return NULL; 574 575 nasel->client = CFRetain(client); 576 nasel->server = CFRetain(server); 577 nasel->clienttype = clienttype; 578 CFRetain(clienttype); 579 nasel->servertype = servertype; 580 CFRetain(servertype); 581 582 nasel->mech = mech; 583 nasel->spnego = (flags & USE_SPNEGO) ? true : false; 584 585 CFArrayAppendValue(na->selections, nasel); 586 587 CFRelease(nasel); /* referenced by array */ 588 589 return nasel; 590} 591 592static bool 593findUsername(CFAllocatorRef alloc, NAHRef na, CFDictionaryRef info) 594{ 595 char *name; 596 597 if (info) { 598 na->username = CFDictionaryGetValue(info, kNAHUserName); 599 if (na->username) { 600 CFRange range, ur; 601 602 CFRetain(na->username); 603 604 if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 605 ur.location = 0; 606 ur.length = range.location; 607 na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur); 608 } else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 609 ur.location = range.location + 1; 610 ur.length = CFStringGetLength(na->username) - ur.location; 611 na->specificname = CFStringCreateWithSubstring(na->alloc, na->username, ur); 612 } else { 613 na->specificname = na->username; 614 CFRetain(na->specificname); 615 } 616 617 nalog(ASL_LEVEL_DEBUG, CFSTR("NAH: specific name is: %@ foo"), na->specificname); 618 619 return true; 620 } 621 } 622 623 name = getlogin(); 624 if (name == NULL) 625 return false; 626 627 na->username = CFStringCreateWithCString(alloc, name, kCFStringEncodingUTF8); 628 if (na->username == NULL) 629 return false; 630 631 return true; 632} 633 634/* 635 * Returns true for those hostname that looks like those hostname 636 * where we should use LKDC hostnames instead. This check is only used 637 * for protocol where we don't know wether we should use LKDC or 638 * classic Kerberos. 639 */ 640 641static bool 642have_lkdcish_hostname(NAHRef na, bool localIsLKDC) 643{ 644 CFMutableStringRef btmmDomain = NULL; 645 CFStringRef btmmDomainData; 646 bool ret = false; 647 648 btmmDomainData = _CSBackToMyMacCopyDomain(); 649 if (btmmDomainData) { 650 btmmDomain = CFStringCreateMutableCopy(na->alloc, 0, btmmDomainData); 651 CFRELEASE(btmmDomainData); 652 if (btmmDomain) { 653 CFStringTrim(btmmDomain, CFSTR(".")); 654 nalog(ASL_LEVEL_DEBUG, CFSTR("using BTMM domain %@"), btmmDomain); 655 } 656 } 657 658 659 if (na->lchostname == NULL) { 660 na->lchostname = CFStringCreateMutableCopy(NULL, 0, na->hostname); 661 if (na->lchostname == NULL) { 662 CFRELEASE(btmmDomain); 663 return false; 664 } 665 } 666 667 CFStringLowercase(na->lchostname, CFLocaleGetSystem()); 668 669 if (localIsLKDC && CFStringHasSuffix(na->lchostname, CFSTR(".local"))) 670 ret = true; 671 else if (btmmDomain && CFStringHasSuffix(na->lchostname, btmmDomain)) 672 ret = true; 673 674 CFRELEASE(btmmDomain); 675 676 return ret; 677} 678 679static void 680add_realms(NAHRef na, char **realms, unsigned long flags) 681{ 682 CFStringRef u, s; 683 size_t n; 684 685 for (n = 0; realms[n] != NULL; n++) { 686 u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%s"), na->username, realms[n]); 687 s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname, realms[n]); 688 689 if (u && s) 690 addSelection(na, u, kNAHNTKRB5Principal, 691 s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags); 692 CFRELEASE(u); 693 CFRELEASE(s); 694 } 695} 696 697 698static void 699use_classic_kerberos(NAHRef na, unsigned long flags) 700{ 701 CFRange range, dr, ur; 702 char **realms, *str; 703 int ret; 704 705 if (have_lkdcish_hostname(na, false)) 706 return; 707 708 ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str); 709 if (ret) 710 return; 711 712 /* 713 * If user have @REALM, lets try that out 714 */ 715 716 if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 717 CFStringRef domain = NULL, s = NULL; 718 CFMutableStringRef domainm = NULL; 719 720 dr.location = range.location + 1; 721 dr.length = CFStringGetLength(na->username) - dr.location; 722 723 domain = CFStringCreateWithSubstring(na->alloc, na->username, dr); 724 if (domain) { 725 domainm = CFStringCreateMutableCopy(na->alloc, 0, domain); 726 727 if (domainm) { 728 CFStringUppercase(domainm, NULL); 729 730 s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"), 731 na->service, na->hostname, domainm); 732 733 if (s) 734 addSelection(na, na->username, kNAHNTKRB5Principal, 735 s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags); 736 } 737 } 738 CFRELEASE(domainm); 739 CFRELEASE(domain); 740 CFRELEASE(s); 741 } 742 743 if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 744 CFStringRef domain = NULL, user = NULL, user2 = NULL, s = NULL; 745 CFMutableStringRef domainm = NULL; 746 747 dr.location = 0; 748 dr.length = range.location; 749 750 ur.location = range.location + 1; 751 ur.length = CFStringGetLength(na->username) - ur.location; 752 753 user = CFStringCreateWithSubstring(na->alloc, na->username, ur); 754 domain = CFStringCreateWithSubstring(na->alloc, na->username, dr); 755 if (domain && user) { 756 domainm = CFStringCreateMutableCopy(na->alloc, 0, domain); 757 user2 = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"), user, domain); 758 759 if (domainm && user2) { 760 CFStringUppercase(domainm, NULL); 761 762 s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%@"), 763 na->service, na->hostname, domainm); 764 765 if (s) 766 addSelection(na, user2, kNAHNTKRB5Principal, 767 s, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags|FORCE_ADD); 768 } 769 } 770 CFRELEASE(domainm); 771 CFRELEASE(domain); 772 CFRELEASE(user2); 773 CFRELEASE(user); 774 CFRELEASE(s); 775 } 776 777 /* 778 * Try the host realm 779 */ 780 781 ret = krb5_get_host_realm(na->context, str, &realms); 782 __KRBReleaseUTF8String(str); 783 if (ret == 0) { 784 add_realms(na, realms, flags); 785 krb5_free_host_realm(na->context, realms); 786 } 787 788 /* 789 * Also, just for the heck of it, check default realms 790 */ 791 792 ret = krb5_get_default_realms(na->context, &realms); 793 if (ret == 0) { 794 add_realms(na, realms, flags); 795 krb5_free_host_realm(na->context, realms); 796 } 797} 798 799/* 800 * 801 */ 802 803static void 804use_existing_principals(NAHRef na, int only_lkdc, unsigned long flags) 805{ 806 krb5_cccol_cursor cursor; 807 krb5_principal client; 808 krb5_error_code ret; 809 CFStringRef server; 810 krb5_ccache id; 811 CFStringRef u; 812 char *c; 813 814 ret = krb5_cccol_cursor_new(na->context, &cursor); 815 if (ret) 816 return; 817 818 while ((ret = krb5_cccol_cursor_next(na->context, cursor, &id)) == 0 && id != NULL) { 819 NAHSelectionRef nasel; 820 int is_lkdc; 821 time_t t; 822 823 ret = krb5_cc_get_principal(na->context, id, &client); 824 if (ret) { 825 krb5_cc_close(na->context, id); 826 continue; 827 } 828 829 ret = krb5_cc_get_lifetime(na->context, id, &t); 830 if (ret || t <= 0) { 831 krb5_cc_close(na->context, id); 832 continue; 833 } 834 835 is_lkdc = krb5_principal_is_lkdc(na->context, client); 836 837 if ((only_lkdc && !is_lkdc) || (!only_lkdc && is_lkdc)) { 838 krb5_free_principal(na->context, client); 839 krb5_cc_close(na->context, id); 840 continue; 841 } 842 843 ret = krb5_unparse_name(na->context, client, &c); 844 if (ret) { 845 krb5_free_principal(na->context, client); 846 krb5_cc_close(na->context, id); 847 continue; 848 } 849 850 u = CFStringCreateWithCString(na->alloc, c, kCFStringEncodingUTF8); 851 free(c); 852 if (u == NULL) { 853 krb5_free_principal(na->context, client); 854 krb5_cc_close(na->context, id); 855 continue; 856 } 857 858 if (is_lkdc) { 859 CFStringRef cr = NULL; 860 krb5_data data; 861 862 ret = krb5_cc_get_config(na->context, id, NULL, "lkdc-hostname", &data); 863 if (ret == 0) { 864 cr = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false); 865 krb5_data_free(&data); 866 } 867 868 if (cr == NULL || CFStringCompare(na->hostname, cr, 0) != kCFCompareEqualTo) { 869 krb5_free_principal(na->context, client); 870 krb5_cc_close(na->context, id); 871 continue; 872 } 873 874 /* Create server principal */ 875 server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"), 876 na->service, client->realm, client->realm); 877 878 nalog(ASL_LEVEL_DEBUG, CFSTR("Adding existing LKDC cache: %@ -> %@"), u, server); 879 880 } else { 881 server = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), na->service, na->hostname, 882 krb5_principal_get_realm(na->context, client)); 883 nalog(ASL_LEVEL_DEBUG, CFSTR("Adding existing cache: %@ -> %@"), u, server); 884 } 885 krb5_free_principal(na->context, client); 886 887 nasel = addSelection(na, u, kNAHNTKRB5Principal, 888 server, kNAHNTKRB5PrincipalReferral, GSS_KERBEROS, NULL, flags); 889 CFRELEASE(u); 890 CFRELEASE(server); 891 if (nasel != NULL && nasel->ccache == NULL) { 892 krb5_data data; 893 nasel->ccache = id; 894 nasel->have_cred = 1; 895 896 if (nasel->inferredLabel == NULL) { 897 ret = krb5_cc_get_config(na->context, id, NULL, "FriendlyName", &data); 898 if (ret == 0) { 899 nasel->inferredLabel = CFStringCreateWithBytes(na->alloc, data.data, data.length, kCFStringEncodingUTF8, false); 900 krb5_data_free(&data); 901 } 902 } 903 904 } else 905 krb5_cc_close(na->context, id); 906 907 } 908 909 krb5_cccol_cursor_free(na->context, &cursor); 910} 911 912static CFStringRef kWELLKNOWN_LKDC = CFSTR("WELLKNOWN:COM.APPLE.LKDC"); 913 914static void 915wellknown_lkdc(NAHRef na, enum NAHMechType mechtype, unsigned long flags) 916{ 917 CFStringRef s, u; 918 CFIndex n; 919 920 u = CFStringCreateWithFormat(na->alloc, NULL, 921 CFSTR("%@@%@"), 922 na->username, kWELLKNOWN_LKDC); 923 if (u == NULL) 924 return; 925 926 s = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/localhost@%@"), 927 na->service, kWELLKNOWN_LKDC); 928 if (s == NULL) { 929 CFRELEASE(u); 930 return; 931 } 932 933 if (na->password) 934 addSelection(na, u, kNAHNTKRB5Principal, 935 s, kNAHNTKRB5Principal, mechtype, NULL, flags); 936 CFRELEASE(u); 937 938 /* if we have certs, lets push those names too */ 939 for (n = 0; na->x509identities && n < CFArrayGetCount(na->x509identities); n++) { 940 SecIdentityRef identity = (void *)CFArrayGetValueAtIndex(na->x509identities, n); 941 CFStringRef csstr; 942 SecCertificateRef cert = NULL; 943 944 if (SecIdentityCopyCertificate(identity, &cert)) 945 continue; 946 947 csstr = _CSCopyKerberosPrincipalForCertificate(cert); 948 CFRelease(cert); 949 if (csstr == NULL) { 950 hx509_cert hxcert; 951 char *str; 952 int ret; 953 954 ret = hx509_cert_init_SecFramework(na->hxctx, identity, &hxcert); 955 if (ret) 956 continue; 957 958 ret = hx509_cert_get_appleid(na->hxctx, hxcert, &str); 959 hx509_cert_free(hxcert); 960 if (ret) 961 continue; 962 963 u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%s@%@"), 964 str, kWELLKNOWN_LKDC); 965 krb5_xfree(str); 966 if (u == NULL) 967 continue; 968 } else { 969 u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@%@"), 970 csstr, kWELLKNOWN_LKDC); 971 CFRelease(csstr); 972 if (u == NULL) 973 continue; 974 } 975 976 NAHSelectionRef nasel = addSelection(na, u, kNAHNTKRB5Principal, s, kNAHNTKRB5PrincipalReferral, mechtype, NULL, flags); 977 CFRELEASE(u); 978 if (nasel) { 979 CFRetain(identity); 980 nasel->certificate = identity; 981 } 982 } 983 984 CFRELEASE(s); 985} 986 987static bool 988is_smb(NAHRef na) 989{ 990 if (CFStringCompare(na->service, kNAHServiceHostServer, 0) == kCFCompareEqualTo || 991 CFStringCompare(na->service, kNAHServiceCIFSServer, 0) == kCFCompareEqualTo) 992 return true; 993 return false; 994} 995 996static void 997guess_kerberos(NAHRef na) 998{ 999 bool try_wlkdc = false; 1000 bool try_iakerb_with_lkdc = false; 1001 bool have_kerberos = false; 1002 krb5_error_code ret; 1003 unsigned long flags = USE_SPNEGO; 1004 1005 if (nah_use_gss_uam 1006 && (na->password || na->x509identities) 1007 && haveMech(na, kGSSAPIMechIAKERB) 1008 && haveMech(na, kGSSAPIMechSupportsAppleLKDC)) 1009 { 1010 /* if we support IAKERB and AppleLDKC and is not SMB (client can't handle it, rdar://problem/8437184), let go for iakerb with */ 1011 try_iakerb_with_lkdc = true; 1012 } else if (haveMech(na, kGSSAPIMechPKU2UOID) || haveMech(na, kGSSAPIMechSupportsAppleLKDC)) { 1013 try_wlkdc = true; 1014 } else if (CFStringCompare(na->service, kNAHServiceVNCServer, 0) == kCFCompareEqualTo) { 1015 try_wlkdc = true; 1016 if (nah_vnc_support_iakerb && (na->password || na->x509identities)) 1017 try_iakerb_with_lkdc = true; 1018 } 1019 1020 /* 1021 * If we are using an old AFP server, disable SPNEGO 1022 */ 1023 if (CFStringCompare(na->service, kNAHServiceAFPServer, 0) == kCFCompareEqualTo && 1024 !haveMech(na, kGSSAPIMechSupportsAppleLKDC)) 1025 { 1026 flags &= (~USE_SPNEGO); 1027 } 1028 1029 1030 have_kerberos = (na->servermechs == NULL) || 1031 haveMech(na, kGSSAPIMechIAKERB) || 1032 haveMech(na, kGSSAPIMechKerberosOID) || 1033 haveMech(na, kGSSAPIMechKerberosMicrosoftOID) || 1034 haveMech(na, kGSSAPIMechPKU2UOID); 1035 1036 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate-krb: have_kerberos=%s try_iakerb_with_lkdc=%s try-wkdc=%s use-spnego=%s"), 1037 have_kerberos ? "yes" : "no", 1038 try_iakerb_with_lkdc ? "yes" : "no", 1039 try_wlkdc ? "yes" : "no", 1040 (flags & USE_SPNEGO) ? "yes" : "no"); 1041 1042 if (!have_kerberos) 1043 return; 1044 1045 /* 1046 * 1047 */ 1048 1049 ret = krb5_init_context(&na->context); 1050 if (ret) 1051 return; 1052 1053 ret = hx509_context_init(&na->hxctx); 1054 if (ret) 1055 return; 1056 1057 /* 1058 * We'll use matching LKDC credentials to this host since they are 1059 * faster then public key operations. 1060 */ 1061 1062 use_existing_principals(na, 1, flags); 1063 1064 /* 1065 * IAKERB with LKDC 1066 */ 1067 1068 if (try_iakerb_with_lkdc) { 1069 wellknown_lkdc(na, GSS_KERBEROS_IAKERB, flags); 1070 } 1071 1072 /* 1073 * Wellknown:LKDC 1074 */ 1075 1076 if (try_wlkdc) 1077 wellknown_lkdc(na, GSS_KERBEROS, flags); 1078 1079 /* 1080 * Do classic Kerberos too 1081 */ 1082 1083 if (na->password) 1084 use_classic_kerberos(na, flags); 1085 1086 1087 /* 1088 * We'll use existing credentials if we have them 1089 */ 1090 1091 use_existing_principals(na, 0, flags); 1092} 1093 1094static void 1095guess_ntlm(NAHRef na) 1096{ 1097 CFStringRef s; 1098 unsigned long flags = USE_SPNEGO; 1099 1100 if (!haveMech(na, kGSSAPIMechNTLMOID)) 1101 return; 1102 1103 if (na->servermechs) { 1104 CFDataRef data = CFDictionaryGetValue(na->servermechs, kGSSAPIMechNTLMOID); 1105 if (data && CFDataGetLength(data) == 3 && memcmp(CFDataGetBytePtr(data), "raw", 3) == 0) 1106 flags &= (~USE_SPNEGO); 1107 } 1108 1109 s = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), na->service, na->hostname); 1110 if (s == NULL) 1111 return; 1112 1113 if (na->password) { 1114 CFRange range, ur, dr; 1115 CFStringRef u = NULL; 1116 unsigned long flags2 = 0; 1117 1118 if (CFStringFindWithOptions(na->username, CFSTR("@"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 1119 u = na->username; 1120 CFRetain(u); 1121 1122 flags2 = FORCE_ADD; 1123 } else if (CFStringFindWithOptions(na->username, CFSTR("\\"), CFRangeMake(0, CFStringGetLength(na->username)), 0, &range)) { 1124 CFStringRef ustr, dstr; 1125 1126 dr.location = 0; 1127 dr.length = range.location; 1128 1129 ur.location = range.location + 1; 1130 ur.length = CFStringGetLength(na->username) - ur.location; 1131 1132 dstr = CFStringCreateWithSubstring(NULL, na->username, dr); 1133 ustr = CFStringCreateWithSubstring(NULL, na->username, ur); 1134 1135 if (dstr && ustr) 1136 u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), ustr, dstr); 1137 CFRELEASE(ustr); 1138 CFRELEASE(dstr); 1139 1140 flags2 = FORCE_ADD; 1141 } else { 1142 u = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@\\%@"), na->username, na->hostname); 1143 } 1144 1145 if (u) { 1146 addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags | flags2); 1147 CFRELEASE(u); 1148 } 1149 1150 if (na->specificname) { 1151 u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@@\\%@"), na->specificname, na->hostname); 1152 addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags); 1153 CFRELEASE(u); 1154 } 1155 } 1156 1157 /* pick up ntlm credentials in caches */ 1158 1159 dispatch_semaphore_t sema = dispatch_semaphore_create(0); 1160 if (sema == NULL) 1161 goto out; 1162 1163 (void)gss_iter_creds(NULL, 0, GSS_NTLM_MECHANISM, ^(gss_OID oid, gss_cred_id_t cred) { 1164 OM_uint32 min_stat; 1165 gss_name_t name = GSS_C_NO_NAME; 1166 gss_buffer_desc buffer = { 0, NULL }; 1167 1168 if (cred == NULL) { 1169 dispatch_semaphore_signal(sema); 1170 return; 1171 } 1172 1173 gss_inquire_cred(&min_stat, cred, &name, NULL, NULL, NULL); 1174 gss_display_name(&min_stat, name, &buffer, NULL); 1175 gss_release_name(&min_stat, &name); 1176 1177 CFStringRef u = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%.*s"), 1178 (int)buffer.length, buffer.value); 1179 1180 gss_release_buffer(&min_stat, &buffer); 1181 1182 if (u == NULL) 1183 return; 1184 1185 /* if we where given a name, and it matches, add it */ 1186 1187 NAHSelectionRef nasel = addSelection(na, u, kNAHNTUsername, s, NULL, GSS_NTLM, NULL, flags); 1188 CFRELEASE(u); 1189 if (nasel) { 1190 nasel->have_cred = 1; 1191 } 1192 }); 1193 1194 dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER); 1195 dispatch_release(sema); 1196 1197 out: 1198 CFRELEASE(s); 1199} 1200 1201/* 1202 * Flattern arrays and try 1203 */ 1204 1205static void 1206addSecItem(CFMutableArrayRef certs, CFTypeRef item) 1207{ 1208 CFTypeID type = CFGetTypeID(item); 1209 1210 if (CFArrayGetTypeID() == type) { 1211 CFIndex n, count = CFArrayGetCount(item); 1212 for (n = 0; n < count; n++) { 1213 CFTypeRef arrayItem = CFArrayGetValueAtIndex(item, n); 1214 addSecItem(certs, arrayItem); 1215 } 1216 } else if (SecCertificateGetTypeID() == type) { 1217 SecIdentityRef identity = NULL; 1218 SecIdentityCreateWithCertificate(NULL, (SecCertificateRef)item, &identity); 1219 if (identity) { 1220 CFArrayAppendValue(certs, identity); 1221 CFRelease(identity); 1222 } 1223 } else if (SecIdentityGetTypeID() == type) { 1224 CFArrayAppendValue(certs, item); 1225 } else { 1226 CFStringRef desc = CFCopyDescription(item); 1227 nalog(ASL_LEVEL_DEBUG, CFSTR("unknown type of certificates: %@"), desc); 1228 CFRELEASE(desc); 1229 } 1230} 1231 1232/* 1233 * 1234 */ 1235 1236const CFStringRef kNAHNegTokenInit = CFSTR("kNAHNegTokenInit"); 1237const CFStringRef kNAHUserName = CFSTR("kNAHUserName"); 1238const CFStringRef kNAHCertificates = CFSTR("kNAHCertificates"); 1239const CFStringRef kNAHPassword = CFSTR("kNAHPassword"); 1240 1241NAHRef 1242NAHCreate(CFAllocatorRef alloc, 1243 CFStringRef hostname, 1244 CFStringRef service, 1245 CFDictionaryRef info) 1246{ 1247 NAHRef na = NAAlloc(alloc); 1248 CFStringRef canonname; 1249 char *hostnamestr = NULL; 1250 1251 dispatch_once(&init_globals, ^{ 1252 Boolean have_key = false; 1253 nah_use_gss_uam = CFPreferencesGetAppBooleanValue(CFSTR("GSSEnable"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key); 1254 if (!have_key) 1255 nah_use_gss_uam = true; 1256 nah_vnc_support_iakerb = CFPreferencesGetAppBooleanValue(CFSTR("VNCSupportIAKerb"), CFSTR("com.apple.NetworkAuthenticationHelper"), &have_key); 1257 }); 1258 1259 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: hostname=%@ service=%@"), hostname, service); 1260 1261 /* first undo the damage BrowserServices have done to the hostname */ 1262 1263 if (_CFNetServiceDeconstructServiceName(hostname, &hostnamestr)) { 1264 canonname = CFStringCreateWithCString(na->alloc, hostnamestr, kCFStringEncodingUTF8); 1265 free(hostnamestr); 1266 if (canonname == NULL) 1267 return NULL; 1268 } else { 1269 canonname = hostname; 1270 CFRetain(canonname); 1271 } 1272 1273 na->hostname = CFStringCreateMutableCopy(alloc, 0, canonname); 1274 CFRelease(canonname); 1275 if (na->hostname == NULL) { 1276 CFRELEASE(na); 1277 return NULL; 1278 } 1279 CFStringTrim(na->hostname, CFSTR(".")); 1280 1281 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: will use hostname=%@"), na->hostname); 1282 1283 na->service = CFRetain(service); 1284 1285 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: will use service=%@"), na->service); 1286 1287 1288 if (!findUsername(alloc, na, info)) { 1289 CFRELEASE(na); 1290 return NULL; 1291 } 1292 1293 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: username=%@ username %s"), na->username, na->specificname ? 1294 "given" : "generated"); 1295 1296 1297 if (info) { 1298 na->password = CFDictionaryGetValue(info, kNAHPassword); 1299 if (na->password) { 1300 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: password")); 1301 CFRetain(na->password); 1302 } 1303 } 1304 1305 na->selections = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks); 1306 if (na->selections == NULL) { 1307 CFRELEASE(na); 1308 return NULL; 1309 } 1310 1311 if (info) { 1312 CFDictionaryRef nti; 1313 CFArrayRef certs; 1314 1315 nti = CFDictionaryGetValue(info, kNAHNegTokenInit); 1316 if (nti) { 1317 na->servermechs = CFDictionaryGetValue(nti, kSPNEGONegTokenInitMechs); 1318 if (na->servermechs) 1319 CFRetain(na->servermechs); 1320 na->spnegoServerName = CFDictionaryGetValue(nti, kSPNEGONegTokenInitHintsHostname); 1321 if (na->spnegoServerName) { 1322 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: SPNEGO hints name %@"), na->spnegoServerName); 1323 CFRetain(na->spnegoServerName); 1324 } 1325 } 1326 1327 certs = CFDictionaryGetValue(info, kNAHCertificates); 1328 if (certs) { 1329 CFMutableArrayRef a = CFArrayCreateMutable(na->alloc, 0, &kCFTypeArrayCallBacks); 1330 1331 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: certs %@"), certs); 1332 1333 addSecItem(a, certs); 1334 1335 if (CFArrayGetCount(a)) { 1336 na->x509identities = a; 1337 } else { 1338 CFRelease(a); 1339 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCreate: we got no certs")); 1340 } 1341 } 1342 } 1343 1344 /* here starts the guessing game */ 1345 1346 add_user_selections(na); 1347 1348 guess_kerberos(na); 1349 1350 /* only do NTLM for SMB */ 1351 if (na->x509identities == NULL && is_smb(na)) 1352 guess_ntlm(na); 1353 1354 return na; 1355} 1356 1357CFArrayRef 1358NAHGetSelections(NAHRef na) 1359{ 1360 return na->selections; 1361} 1362 1363static CFStringRef 1364copyInferedNameFromIdentity(SecIdentityRef identity) 1365{ 1366 CFStringRef inferredLabel = NULL; 1367 SecCertificateRef cert = NULL; 1368 1369 SecIdentityCopyCertificate(identity, &cert); 1370 if (cert == NULL) 1371 return NULL; 1372 1373 inferredLabel = _CSCopyAppleIDAccountForAppleIDCertificate(cert, NULL); 1374 if (inferredLabel == NULL) 1375 SecCertificateInferLabel(cert, &inferredLabel); 1376 1377 CFRELEASE(cert); 1378 1379 return inferredLabel; 1380} 1381 1382 1383static void 1384setFriendlyName(NAHRef na, 1385 NAHSelectionRef selection, 1386 SecIdentityRef cert, 1387 krb5_ccache id, 1388 int is_lkdc) 1389{ 1390 CFStringRef inferredLabel = NULL; 1391 1392 if (cert) { 1393 inferredLabel = copyInferedNameFromIdentity(cert); 1394 1395 } else if (na->specificname || is_lkdc) { 1396 inferredLabel = na->username; 1397 CFRetain(inferredLabel); 1398 } else { 1399 inferredLabel = selection->client; 1400 CFRetain(inferredLabel); 1401 } 1402 1403 if (inferredLabel) { 1404 char *label; 1405 1406 if (__KRBCreateUTF8StringFromCFString(inferredLabel, &label) == noErr) { 1407 krb5_data data; 1408 1409 data.data = label; 1410 data.length = strlen(label) + 1; 1411 1412 krb5_cc_set_config(na->context, id, NULL, "FriendlyName", &data); 1413 free(label); 1414 } 1415 selection->inferredLabel = inferredLabel; 1416 } 1417} 1418 1419static int 1420acquire_kerberos(NAHRef na, 1421 NAHSelectionRef selection, 1422 CFStringRef password, 1423 SecIdentityRef cert, 1424 CFErrorRef *error) 1425{ 1426 krb5_init_creds_context icc = NULL; 1427 krb5_get_init_creds_opt *opt = NULL; 1428 krb5_principal client = NULL; 1429 int destroy_cache = 0; 1430 krb5_ccache id = NULL; 1431 krb5_error_code ret; 1432 krb5_creds cred; 1433 char *str = NULL; 1434 int parseflags = 0; 1435 int is_lkdc = 0; 1436 1437 memset(&cred, 0, sizeof(cred)); 1438 1439 nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: %@ with pw:%s cert:%s"), 1440 selection->client, 1441 password ? "yes" : "no", 1442 cert ? "yes" : "no"); 1443 1444 ret = __KRBCreateUTF8StringFromCFString(selection->client, &str); 1445 if (ret) 1446 goto out; 1447 1448 /* 1449 * Check if this is an enterprise name 1450 * XXX horrible, caller should tell us 1451 */ 1452 { 1453 char *p = strchr(str, '@'); 1454 if (p && (p = strchr(p + 1, '@')) != NULL) 1455 parseflags |= KRB5_PRINCIPAL_PARSE_ENTERPRISE; 1456 } 1457 1458 ret = krb5_parse_name_flags(na->context, str, parseflags, &client); 1459 __KRBReleaseUTF8String(str); 1460 if (ret) 1461 goto out; 1462 1463 ret = krb5_unparse_name(na->context, client, &str); 1464 if (ret == 0) { 1465 nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: trying with %s as client principal"), str); 1466 free(str); 1467 } 1468 1469 ret = krb5_get_init_creds_opt_alloc(na->context, &opt); 1470 if (ret) 1471 goto out; 1472 1473 if (cert) { 1474 ret = krb5_get_init_creds_opt_set_pkinit(na->context, opt, client, 1475 NULL, "KEYCHAIN:", 1476 NULL, NULL, 0, 1477 NULL, NULL, NULL); 1478 if (ret) 1479 goto out; 1480 } 1481 1482 krb5_get_init_creds_opt_set_canonicalize(na->context, opt, TRUE); 1483 krb5_get_init_creds_opt_set_win2k(na->context, opt, TRUE); 1484 1485 ret = krb5_init_creds_init(na->context, client, NULL, NULL, 1486 0, opt, &icc); 1487 if (ret) 1488 goto out; 1489 1490 if (krb5_principal_is_lkdc(na->context, client)) { 1491 char *tcphostname = NULL; 1492 1493 ret = __KRBCreateUTF8StringFromCFString(na->hostname, &str); 1494 if (ret) 1495 goto out; 1496 asprintf(&tcphostname, "tcp/%s", str); 1497 __KRBReleaseUTF8String(str); 1498 if (tcphostname == NULL) { 1499 ret = ENOMEM; 1500 goto out; 1501 } 1502 krb5_init_creds_set_kdc_hostname(na->context, icc, tcphostname); 1503 free(tcphostname); 1504 } 1505 1506 if (cert) { 1507 hx509_cert hxcert; 1508 1509 ret = hx509_cert_init_SecFramework(na->hxctx, cert, &hxcert); 1510 if (ret) 1511 goto out; 1512 1513 ret = krb5_init_creds_set_pkinit_client_cert(na->context, icc, hxcert); 1514 hx509_cert_free(hxcert); 1515 if (ret) 1516 goto out; 1517 1518 } else if (password) { 1519 ret = __KRBCreateUTF8StringFromCFString(password, &str); 1520 if (ret) 1521 goto out; 1522 ret = krb5_init_creds_set_password(na->context, icc, str); 1523 __KRBReleaseUTF8String(str); 1524 if (ret) 1525 goto out; 1526 } else { 1527 abort(); 1528 } 1529 1530 ret = krb5_init_creds_get(na->context, icc); 1531 if (ret) 1532 goto out; 1533 1534 ret = krb5_init_creds_get_creds(na->context, icc, &cred); 1535 if (ret) 1536 goto out; 1537 1538 ret = krb5_cc_cache_match(na->context, cred.client, &id); 1539 if (ret) { 1540 ret = krb5_cc_new_unique(na->context, NULL, NULL, &id); 1541 if (ret) 1542 goto out; 1543 destroy_cache = 1; 1544 } 1545 1546 ret = krb5_cc_initialize(na->context, id, cred.client); 1547 if (ret) 1548 goto out; 1549 1550 ret = krb5_cc_store_cred(na->context, id, &cred); 1551 if (ret) 1552 goto out; 1553 1554 ret = krb5_init_creds_store_config(na->context, icc, id); 1555 if (ret) 1556 goto out; 1557 1558 /* the KDC might have done referrals games, let update the principals */ 1559 1560 { 1561 CFStringRef newclient, newserver; 1562 const char *realm = krb5_principal_get_realm(na->context, cred.client); 1563 1564 is_lkdc = krb5_realm_is_lkdc(realm); 1565 1566 ret = krb5_unparse_name(na->context, cred.client, &str); 1567 if (ret) 1568 goto out; 1569 1570 newclient = CFStringCreateWithCString(na->alloc, str, kCFStringEncodingUTF8); 1571 free(str); 1572 if (newclient == NULL) { 1573 ret = ENOMEM; 1574 goto out; 1575 } 1576 1577 nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos: got %@ as client principal"), newclient); 1578 1579 if (CFStringCompare(newclient, selection->client, 0) != kCFCompareEqualTo) { 1580 1581 CFRELEASE(selection->client); 1582 selection->client = newclient; 1583 1584 if (is_lkdc) { 1585 newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%s@%s"), 1586 na->service, realm, realm); 1587 } else { 1588 newserver = CFStringCreateWithFormat(na->alloc, NULL, CFSTR("%@/%@@%s"), 1589 na->service, na->hostname, realm); 1590 } 1591 if (newserver) { 1592 CFRELEASE(selection->server); 1593 selection->server = newserver; 1594 } 1595 } else { 1596 CFRELEASE(newclient); 1597 } 1598 } 1599 1600 setFriendlyName(na, selection, cert, id, is_lkdc); 1601 { 1602 krb5_data data; 1603 data.data = "1"; 1604 data.length = 1; 1605 krb5_cc_set_config(na->context, id, NULL, nah_created, &data); 1606 } 1607 1608 1609 out: 1610 if (ret) { 1611 const char *e = krb5_get_error_message(na->context, ret); 1612 updateError(NULL, error, ret, CFSTR("acquire_kerberos failed %@: %d - %s"), 1613 selection->client, ret, e); 1614 krb5_free_error_message(na->context, e); 1615 } else { 1616 nalog(ASL_LEVEL_DEBUG, CFSTR("acquire_kerberos successful")); 1617 } 1618 1619 if (opt) 1620 krb5_get_init_creds_opt_free(na->context, opt); 1621 1622 if (icc) 1623 krb5_init_creds_free(na->context, icc); 1624 1625 if (id) { 1626 if (ret != 0 && destroy_cache) 1627 krb5_cc_destroy(na->context, id); 1628 else 1629 krb5_cc_close(na->context, id); 1630 } 1631 krb5_free_cred_contents(na->context, &cred); 1632 1633 if (client) 1634 krb5_free_principal(na->context, client); 1635 1636 return ret; 1637} 1638 1639/* 1640 * 1641 */ 1642 1643Boolean 1644NAHSelectionAcquireCredentialAsync(NAHSelectionRef selection, 1645 CFDictionaryRef info, 1646 dispatch_queue_t q, 1647 void (^result)(CFErrorRef error)) 1648{ 1649 void (^r)(CFErrorRef error) = (void (^)(CFErrorRef))Block_copy(result); 1650 1651 CFRetain(selection->na); 1652 1653 dispatch_async(selection->na->bgq, ^{ 1654 CFErrorRef e = NULL; 1655 Boolean res; 1656 1657 res = NAHSelectionAcquireCredential(selection, info, &e); 1658 if (!res) { 1659 r(NULL); 1660 CFRelease(selection->na); 1661 Block_release(r); 1662 return; 1663 } 1664 1665 dispatch_async(q, ^{ 1666 r(e); 1667 if (e) 1668 CFRelease(e); 1669 CFRelease(selection->na); 1670 Block_release(r); 1671 }); 1672 }); 1673 1674 return true; 1675} 1676 1677static void 1678setGSSLabel(gss_cred_id_t cred, const char *label, CFStringRef value) 1679{ 1680 gss_buffer_desc buf; 1681 OM_uint32 junk; 1682 1683 buf.value = cf2cstring(value); 1684 if (buf.value == NULL) 1685 return; 1686 buf.length = strlen((char *)buf.value); 1687 1688 gss_cred_label_set(&junk, cred, label, &buf); 1689 free(buf.value); 1690} 1691 1692 1693const CFStringRef kNAHForceRefreshCredential = CFSTR("kNAHForceRefreshCredential"); 1694 1695Boolean 1696NAHSelectionAcquireCredential(NAHSelectionRef selection, 1697 CFDictionaryRef info, 1698 CFErrorRef *error) 1699{ 1700 if (error) 1701 *error = NULL; 1702 1703 CFRetain(selection->na); 1704 1705 if (selection->mech == GSS_KERBEROS) { 1706 1707 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: kerberos client: %@ (server %@)"), 1708 selection->client, selection->server); 1709 1710 /* if we already have a cache, skip acquire unless force */ 1711 if (selection->ccache) { 1712 nalog(ASL_LEVEL_DEBUG, CFSTR("have ccache")); 1713 KRBCredChangeReferenceCount(selection->client, 1, 1); 1714 return true; 1715 } 1716 1717 if (selection->na->password == NULL && selection->certificate == NULL) { 1718 nalog(ASL_LEVEL_DEBUG, CFSTR("krb5: no password or cert, punting")); 1719 CFRelease(selection->na); 1720 return false; 1721 } 1722 1723 int ret; 1724 1725 ret = acquire_kerberos(selection->na, 1726 selection, 1727 selection->na->password, 1728 selection->certificate, 1729 error); 1730 1731 CFRelease(selection->na); 1732 if (ret && error && *error) 1733 nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential %@"), *error); 1734 1735 return (ret == 0) ? true : false; 1736 1737 } else if (selection->mech == GSS_NTLM) { 1738 gss_auth_identity_desc identity; 1739 gss_name_t name = GSS_C_NO_NAME; 1740 gss_buffer_desc gbuf; 1741 char *password, *user; 1742 OM_uint32 major, minor, junk; 1743 dispatch_semaphore_t s; 1744 char *str; 1745 1746 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: ntlm")); 1747 1748 if (selection->have_cred) { 1749 CFRelease(selection->na); 1750 return true; 1751 } 1752 1753 if (selection->na->password == NULL) { 1754 CFRelease(selection->na); 1755 return false; 1756 } 1757 1758 __KRBCreateUTF8StringFromCFString(selection->client, &user); 1759 1760 gbuf.value = user; 1761 gbuf.length = strlen(user); 1762 1763 major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name); 1764 __KRBReleaseUTF8String(user); 1765 if (major) { 1766 CFRelease(selection->na); 1767 return false; 1768 } 1769 1770 s = dispatch_semaphore_create(0); 1771 __KRBCreateUTF8StringFromCFString(selection->client, &user); 1772 __KRBCreateUTF8StringFromCFString(selection->na->password, &password); 1773 1774 selection->inferredLabel = selection->client; 1775 CFRetain(selection->inferredLabel); 1776 1777 /* drop when name is completly supported */ 1778 str = strchr(user, '@'); 1779 if (str) 1780 *str++ = '\0'; 1781 1782 identity.username = user; 1783 if (str) 1784 identity.realm = str; 1785 else 1786 identity.realm = ""; 1787 identity.password = password; 1788 1789 major = gss_acquire_cred_ex(name, 1790 0, 1791 GSS_C_INDEFINITE, 1792 GSS_NTLM_MECHANISM, 1793 GSS_C_INITIATE, 1794 &identity, 1795 ^(gss_status_id_t status, gss_cred_id_t cred, 1796 gss_OID_set set, OM_uint32 flags) { 1797 1798 if (cred) { 1799 gss_buffer_desc buffer; 1800 OM_uint32 min_stat; 1801 1802 buffer.value = user; 1803 buffer.length = strlen(user); 1804 1805 gss_cred_label_set(&min_stat, cred, "FriendlyName", &buffer); 1806 1807 buffer.value = "1"; 1808 buffer.length = 1; 1809 gss_cred_label_set(&min_stat, cred, nah_created, &buffer); 1810 } else { 1811 updateError(NULL, error, 1, CFSTR("failed to create ntlm cred")); 1812 } 1813 1814 dispatch_semaphore_signal(s); 1815 CFRelease(selection->na); 1816 }); 1817 gss_release_name(&junk, &name); 1818 if (major == GSS_S_COMPLETE) { 1819 dispatch_semaphore_wait(s, DISPATCH_TIME_FOREVER); 1820 1821 if (error && *error) 1822 nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential ntlm %@"), *error); 1823 1824 } else { 1825 updateError(NULL, error, major, CFSTR("Failed to acquire NTLM credentials")); 1826 CFRelease(selection->na); 1827 } 1828 1829 __KRBReleaseUTF8String(user); 1830 __KRBReleaseUTF8String(password); 1831 1832 dispatch_release(s); 1833 return true; 1834 } else if (selection->mech == GSS_KERBEROS_IAKERB) { 1835 gss_name_t name = GSS_C_NO_NAME; 1836 gss_buffer_desc gbuf; 1837 char *user; 1838 OM_uint32 major, minor, junk; 1839 gss_cred_id_t cred; 1840 1841 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: iakerb %@"), selection->client); 1842 1843 if (selection->have_cred) { 1844 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: already have cred, why iakerb then ?")); 1845 CFRelease(selection->na); 1846 return false; 1847 } 1848 1849 1850 if (selection->na->password == NULL && selection->certificate == NULL) { 1851 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: no password nor cert")); 1852 CFRelease(selection->na); 1853 return false; 1854 } 1855 1856 __KRBCreateUTF8StringFromCFString(selection->client, &user); 1857 1858 gbuf.value = user; 1859 gbuf.length = strlen(user); 1860 1861 major = gss_import_name(&minor, &gbuf, GSS_C_NT_USER_NAME, &name); 1862 __KRBReleaseUTF8String(user); 1863 if (major) { 1864 CFRelease(selection->na); 1865 return false; 1866 } 1867 1868 if (selection->certificate) 1869 selection->inferredLabel = copyInferedNameFromIdentity(selection->certificate); 1870 1871 if (selection->inferredLabel == NULL) { 1872 CFMutableStringRef str = CFStringCreateMutableCopy(NULL, 0, selection->client); 1873 CFRange range = CFStringFind(str, CFSTR("@"), 0); 1874 if (range.location != kCFNotFound) 1875 CFStringPad(str, NULL, range.location, 0); 1876 selection->inferredLabel = str; 1877 } 1878 1879 CFMutableDictionaryRef dict = NULL; 1880 1881 dict = CFDictionaryCreateMutable(NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1882 1883 if (selection->na->password) 1884 CFDictionaryAddValue(dict, kGSSICPassword, selection->na->password); 1885 if (selection->certificate) 1886 CFDictionaryAddValue(dict, kGSSICCertificate, selection->certificate); 1887 1888 major = gss_aapl_initial_cred(name, GSS_IAKERB_MECHANISM, dict, &cred, error); 1889 CFRelease(dict); 1890 gss_release_name(&junk, &name); 1891 if (major) { 1892 if (error && *error) 1893 nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential iakerb %@"), *error); 1894 CFRelease(selection->na); 1895 return false; 1896 } 1897 1898 setGSSLabel(cred, "FriendlyName", selection->inferredLabel); 1899 setGSSLabel(cred, "lkdc-hostname", selection->na->hostname); 1900 1901 { 1902 gss_buffer_set_t dataset = GSS_C_NO_BUFFER_SET; 1903 1904 major = gss_inquire_cred_by_oid(&minor, cred, GSS_C_NT_UUID, &dataset); 1905 if (major || dataset->count != 1) { 1906 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: failed with no uuid")); 1907 gss_release_buffer_set(&junk, &dataset); 1908 CFRelease(selection->na); 1909 return false; 1910 } 1911 1912 CFStringRef newclient = CFStringCreateWithBytes(NULL, dataset->elements[0].value, dataset->elements[0].length, kCFStringEncodingUTF8, false); 1913 if (newclient) { 1914 CFRELEASE(selection->client); 1915 selection->client = newclient; 1916 selection->clienttype = kNAHNTUUID; 1917 } 1918 gss_release_buffer_set(&junk, &dataset); 1919 } 1920 nalog(ASL_LEVEL_NOTICE, CFSTR("NAHSelectionAcquireCredential complete: iakerb %@ - %@: %@"), selection->client, selection->inferredLabel, cred); 1921 1922 gss_release_cred(&junk, &cred); 1923 1924 CFRelease(selection->na); 1925 1926 return true; 1927 } else { 1928 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHSelectionAcquireCredential: unknown")); 1929 } 1930 1931 return false; 1932} 1933 1934/* 1935 * 1936 */ 1937 1938CFTypeRef 1939NAHSelectionGetInfoForKey(NAHSelectionRef selection, CFStringRef key) 1940{ 1941 if (CFStringCompare(kNAHSelectionHaveCredential, key, 0) == kCFCompareEqualTo) { 1942 if (selection->ccache) 1943 return kCFBooleanTrue; 1944 return kCFBooleanFalse; 1945 } else if (CFStringCompare(kNAHSelectionUserPrintable, key, 0) == kCFCompareEqualTo) { 1946 return selection->client; /* XXX make prettier ? */ 1947 } else if (CFStringCompare(kNAHServerPrincipal, key, 0) == kCFCompareEqualTo) { 1948 return selection->server; 1949 } else if (CFStringCompare(kNAHClientPrincipal, key, 0) == kCFCompareEqualTo) { 1950 return selection->client; 1951 } else if (CFStringCompare(kNAHMechanism, key, 0) == kCFCompareEqualTo) { 1952 /* if not told otherwise, wrap everything in SPNEGO wrappings */ 1953 if (!selection->spnego) 1954 return mech2name(selection->mech); 1955 return kGSSAPIMechSPNEGO; 1956 } else if (CFStringCompare(kNAHInnerMechanism, key, 0) == kCFCompareEqualTo) { 1957 return mech2name(selection->mech); 1958 } else if (CFStringCompare(kNAHUseSPNEGO, key, 0) == kCFCompareEqualTo) { 1959 return selection->spnego ? kCFBooleanTrue : kCFBooleanFalse; 1960 } else if (CFStringCompare(kNAHCredentialType, key, 0) == kCFCompareEqualTo) { 1961 return mech2name(selection->mech); 1962 } else if (CFStringCompare(kNAHInferredLabel, key, 0) == kCFCompareEqualTo) { 1963 return selection->inferredLabel; 1964 } 1965 1966 return NULL; 1967} 1968 1969/* 1970 * Returns the data for kNetFSAuthenticationInfoKey 1971 */ 1972 1973CFDictionaryRef 1974NAHSelectionCopyAuthInfo(NAHSelectionRef selection) 1975{ 1976 CFMutableDictionaryRef dict; 1977 CFStringRef string; 1978 int gssdclient, gssdserver; 1979 1980 if (selection->server == NULL) 1981 return NULL; 1982 1983 dict = CFDictionaryCreateMutable (kCFAllocatorDefault, 5, 1984 &kCFCopyStringDictionaryKeyCallBacks, 1985 &kCFTypeDictionaryValueCallBacks); 1986 if (dict == NULL) 1987 return NULL; 1988 1989 CFDictionaryAddValue(dict, kNAHMechanism, 1990 NAHSelectionGetInfoForKey(selection, kNAHMechanism)); 1991 1992 CFDictionaryAddValue(dict, kNAHCredentialType, 1993 NAHSelectionGetInfoForKey(selection, kNAHCredentialType)); 1994 1995 CFDictionaryAddValue(dict, kNAHClientNameType, selection->clienttype); 1996 1997 if (CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0) { 1998 gssdclient = GSSD_UUID; 1999 } else if (CFStringCompare(selection->clienttype, kNAHNTKRB5Principal, 0) == 0) { 2000 gssdclient = GSSD_KRB5_PRINCIPAL; 2001 } else if (CFStringCompare(selection->clienttype, kNAHNTUsername, 0) == 0) { 2002 gssdclient = GSSD_NTLM_PRINCIPAL; 2003 } else { 2004 gssdclient = GSSD_USER; 2005 } 2006 2007 CFDictionaryAddValue(dict, kNAHServerNameType, selection->servertype); 2008 2009 if (CFStringCompare(selection->servertype, kNAHNTServiceBasedName, 0) == 0) 2010 gssdserver = GSSD_HOSTBASED; 2011 else if (CFStringCompare(selection->servertype, kNAHNTKRB5PrincipalReferral, 0) == 0) 2012 gssdserver = GSSD_KRB5_REFERRAL; 2013 else if (CFStringCompare(selection->servertype, kNAHNTKRB5Principal, 0) == 0) 2014 gssdserver = GSSD_KRB5_PRINCIPAL; 2015 else 2016 gssdserver = GSSD_HOSTBASED; 2017 2018 2019 CFNumberRef num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdclient); 2020 if (num) { 2021 CFDictionaryAddValue(dict, kNAHClientNameTypeGSSD, num); 2022 CFRelease(num); 2023 } 2024 2025 num = CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &gssdserver); 2026 if (num) { 2027 CFDictionaryAddValue(dict, kNAHServerNameTypeGSSD, num); 2028 CFRelease(num); 2029 } 2030 2031 CFDictionaryAddValue(dict, kNAHClientPrincipal, 2032 NAHSelectionGetInfoForKey(selection, kNAHClientPrincipal)); 2033 CFDictionaryAddValue(dict, kNAHServerPrincipal, 2034 NAHSelectionGetInfoForKey(selection, kNAHServerPrincipal)); 2035 2036 /* add label if we have one */ 2037 if ((string = NAHSelectionGetInfoForKey(selection, kNAHInferredLabel)) != NULL) 2038 CFDictionaryAddValue(dict, kNAHInferredLabel, string); 2039 2040 CFDictionaryAddValue(dict, kNAHUseSPNEGO, NAHSelectionGetInfoForKey(selection, kNAHUseSPNEGO)); 2041 2042 return dict; 2043} 2044 2045/* 2046 * 2047 */ 2048 2049void 2050NAHCancel(NAHRef na) 2051{ 2052} 2053 2054/* 2055 * Reference counting 2056 */ 2057 2058 2059static Boolean 2060CredChange(CFStringRef referenceKey, int count, const char *label) 2061{ 2062 const char *mechname; 2063 gss_OID nametype; 2064 gss_OID oid; 2065 2066 if (referenceKey == NULL) 2067 return false; 2068 2069 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHCredChange: %@ count: %d label: %s"), 2070 referenceKey, count, label ? label : "<nolabel>"); 2071 2072 if (CFStringHasPrefix(referenceKey, CFSTR("krb5:"))) { 2073 oid = GSS_KRB5_MECHANISM; 2074 nametype = GSS_C_NT_USER_NAME; 2075 mechname = "kerberos"; 2076 } else if (CFStringHasPrefix(referenceKey, CFSTR("uuid:"))) { 2077 oid = NULL; 2078 nametype = GSS_C_NT_UUID; 2079 mechname = "uuid"; 2080 } else if (CFStringHasPrefix(referenceKey, CFSTR("ntlm:"))) { 2081 oid = GSS_NTLM_MECHANISM; 2082 nametype = GSS_C_NT_USER_NAME; 2083 mechname = "ntlm"; 2084 } else 2085 return false; 2086 2087 { 2088 gss_cred_id_t cred; 2089 gss_buffer_desc gbuf; 2090 OM_uint32 min_stat, maj_stat; 2091 CFStringRef name; 2092 gss_name_t gname; 2093 OSStatus ret; 2094 gss_OID_set_desc mechset; 2095 char *n; 2096 2097 if (oid) { 2098 mechset.elements = oid; 2099 mechset.count = 1; 2100 } 2101 2102 name = CFStringCreateWithSubstring(NULL, referenceKey, CFRangeMake(5, CFStringGetLength(referenceKey) - 5)); 2103 if (name == NULL) 2104 return false; 2105 2106 ret = __KRBCreateUTF8StringFromCFString(name, &n); 2107 CFRelease(name); 2108 if (ret) 2109 return false; 2110 2111 gbuf.value = n; 2112 gbuf.length = strlen(n); 2113 2114 maj_stat = gss_import_name(&min_stat, &gbuf, nametype, &gname); 2115 if (maj_stat != GSS_S_COMPLETE) { 2116 nalog(ASL_LEVEL_DEBUG, CFSTR("ChangeCred: name not importable %s/%s"), n, mechname); 2117 free(n); 2118 return false; 2119 } 2120 2121 maj_stat = gss_acquire_cred(&min_stat, gname, GSS_C_INDEFINITE, oid ? &mechset : NULL, GSS_C_INITIATE, &cred, NULL, NULL); 2122 gss_release_name(&min_stat, &gname); 2123 2124 if (maj_stat != GSS_S_COMPLETE) { 2125 nalog(ASL_LEVEL_DEBUG, CFSTR("ChangeCred: cred name %s/%s not found"), n, mechname); 2126 free(n); 2127 return false; 2128 } 2129 free(n); 2130 2131 /* check that the credential is refcounted */ 2132 { 2133 gss_buffer_desc buffer; 2134 maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer); 2135 if (maj_stat) { 2136 gss_release_cred(&min_stat, &cred); 2137 return false; 2138 } 2139 gss_release_buffer(&min_stat, &buffer); 2140 } 2141 2142 if (count == 0) { 2143 /* do nothing */ 2144 } else if (count > 0) { 2145 gss_cred_hold(&min_stat, cred); 2146 } else { 2147 gss_cred_unhold(&min_stat, cred); 2148 } 2149 2150 if (label) { 2151 gss_buffer_desc buffer = { 2152 .value = "1", 2153 .length = 1 2154 }; 2155 2156 gss_cred_label_set(&min_stat, cred, label, &buffer); 2157 } 2158 2159 gss_release_cred(&min_stat, &cred); 2160 return true; 2161 } 2162} 2163 2164char * 2165NAHCreateRefLabelFromIdentifier(CFStringRef identifier) 2166{ 2167 CFStringRef str = CFStringCreateWithFormat(NULL, NULL, CFSTR("reference-label:%@"), identifier); 2168 OSStatus ret; 2169 char *label; 2170 2171 if (str == NULL) 2172 return NULL; 2173 2174 ret = __KRBCreateUTF8StringFromCFString(str, &label); 2175 CFRelease(str); 2176 if (ret != noErr) 2177 return NULL; 2178 return label; 2179} 2180 2181 2182Boolean 2183NAHAddReferenceAndLabel(NAHSelectionRef selection, 2184 CFStringRef identifier) 2185{ 2186 CFStringRef ref; 2187 Boolean res; 2188 char *ident; 2189 2190 ref = NAHCopyReferenceKey(selection); 2191 if (ref == NULL) 2192 return false; 2193 2194 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHAddReferenceAndLabel: %@ label: %@"), ref, identifier); 2195 2196 ident = NAHCreateRefLabelFromIdentifier(identifier); 2197 if (ident == NULL) { 2198 CFRelease(ref); 2199 return false; 2200 } 2201 2202 res = CredChange(ref, 1, ident); 2203 CFRelease(ref); 2204 __KRBReleaseUTF8String(ident); 2205 2206 return res; 2207} 2208 2209CFStringRef 2210NAHCopyReferenceKey(NAHSelectionRef selection) 2211{ 2212 CFStringRef type; 2213 if (selection->client == NULL) 2214 return NULL; 2215 2216 switch (selection->mech) { 2217 case GSS_KERBEROS: 2218 type = CFSTR("krb5"); 2219 break; 2220 case GSS_KERBEROS_PKU2U: 2221 case GSS_KERBEROS_IAKERB: 2222 type = CFSTR("uuid"); 2223 break; 2224 case GSS_NTLM: 2225 type = CFSTR("ntlm"); 2226 break; 2227 default: 2228 return NULL; 2229 } 2230 2231 /* if we are using UUID name types, prefer that over mech type */ 2232 if (selection->clienttype && CFStringCompare(selection->clienttype, kNAHNTUUID, 0) == 0) 2233 type = CFSTR("uuid"); 2234 2235 return CFStringCreateWithFormat(NULL, 0, CFSTR("%@:%@"), 2236 type, selection->client); 2237} 2238 2239void 2240NAHFindByLabelAndRelease(CFStringRef identifier) 2241{ 2242 char *str; 2243 2244 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHFindByLabelAndRelease: looking for label %@"), identifier); 2245 2246 str = NAHCreateRefLabelFromIdentifier(identifier); 2247 if (str == NULL) 2248 return; 2249 2250 gss_iter_creds(NULL, 0, GSS_C_NO_OID, ^(gss_OID mech, gss_cred_id_t cred) { 2251 OM_uint32 min_stat, maj_stat; 2252 gss_buffer_desc buffer; 2253 2254 if (cred == NULL) 2255 return; 2256 2257 maj_stat = gss_cred_label_get(&min_stat, cred, nah_created, &buffer); 2258 if (maj_stat) { 2259 gss_release_cred(&min_stat, &cred); 2260 return; 2261 } 2262 gss_release_buffer(&min_stat, &buffer); 2263 2264 buffer.value = NULL; 2265 buffer.length = 0; 2266 2267 /* if there is a label, unhold */ 2268 maj_stat = gss_cred_label_get(&min_stat, cred, str, &buffer); 2269 gss_release_buffer(&min_stat, &buffer); 2270 if (maj_stat == GSS_S_COMPLETE) { 2271 nalog(ASL_LEVEL_DEBUG, CFSTR("NAHFindByLabelAndRelease: found credential unholding")); 2272 gss_cred_label_set(&min_stat, cred, str, NULL); 2273 gss_cred_unhold(&min_stat, cred); 2274 } 2275 gss_release_cred(&min_stat, &cred); 2276 }); 2277 2278 __KRBReleaseUTF8String(str); 2279} 2280 2281Boolean 2282NAHCredAddReference(CFStringRef referenceKey) 2283{ 2284 return CredChange(referenceKey, 1, NULL); 2285} 2286 2287Boolean 2288NAHCredRemoveReference(CFStringRef referenceKey) 2289{ 2290 return CredChange(referenceKey, -1, NULL); 2291} 2292 2293/* 2294 * 2295 */ 2296 2297static CFStringRef kDomainKey = CFSTR("domain"); 2298static CFStringRef kUsername = CFSTR("user"); 2299 2300static CFStringRef kMech = CFSTR("mech"); 2301static CFStringRef kClient = CFSTR("client"); 2302 2303 2304static void 2305add_user_selections(NAHRef na) 2306{ 2307 CFArrayRef array; 2308 enum NAHMechType mech = GSS_KERBEROS; 2309 CFIndex n; 2310 2311 array = CFPreferencesCopyAppValue(CFSTR("UserSelections"), 2312 CFSTR("com.apple.NetworkAuthenticationHelper")); 2313 if (array == NULL || CFGetTypeID(array) != CFArrayGetTypeID()) { 2314 CFRELEASE(array); 2315 return; 2316 } 2317 2318 2319 for (n = 0; n < CFArrayGetCount(array); n++) { 2320 CFDictionaryRef dict = CFArrayGetValueAtIndex(array, n); 2321 CFStringRef server = NULL; 2322 CFStringRef d, u, m, c; 2323 2324 if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) 2325 continue; 2326 2327 m = CFDictionaryGetValue(dict, kMech); 2328 d = CFDictionaryGetValue(dict, kDomainKey); 2329 u = CFDictionaryGetValue(dict, kUsername); 2330 c = CFDictionaryGetValue(dict, kClient); 2331 2332 if (c == NULL || CFGetTypeID(c) != CFStringGetTypeID()) 2333 continue; 2334 if (m == NULL || CFGetTypeID(m) != CFStringGetTypeID()) 2335 continue; 2336 if (d == NULL || CFGetTypeID(d) != CFStringGetTypeID()) 2337 continue; 2338 if (u == NULL && CFGetTypeID(u) != CFStringGetTypeID()) 2339 continue; 2340 2341 /* find if matching */ 2342 /* exact matching for now, should really be domain matching */ 2343 if (CFStringCompare(d, na->hostname, kCFCompareCaseInsensitive) != kCFCompareEqualTo) 2344 continue; 2345 2346 if (u == NULL) { 2347 if (CFStringCompare(d, na->username, 0) != kCFCompareEqualTo) 2348 continue; 2349 } 2350 2351 mech = name2mech(m); 2352 if (mech == NO_MECH) 2353 continue; 2354 2355 server = CFStringCreateWithFormat(na->alloc, 0, CFSTR("%@@%@"), 2356 na->service, na->hostname); 2357 2358 /* add selection */ 2359 if (server && c) 2360 addSelection(na, c, NULL, server, NULL, mech, NULL, true); 2361 CFRELEASE(server); 2362 } 2363 CFRelease(array); 2364} 2365 2366 2367 2368 2369/* 2370 * GSS-API Support 2371 */ 2372 2373static gss_OID 2374ntstring2oid(CFStringRef name) 2375{ 2376 if (CFStringCompare(name, kNAHNTServiceBasedName, 0) == 0) 2377 return GSS_C_NT_HOSTBASED_SERVICE; 2378 else if (CFStringCompare(name, kNAHNTKRB5PrincipalReferral, 0) == 0) 2379 return GSS_KRB5_NT_PRINCIPAL_NAME_REFERRAL; 2380 else if (CFStringCompare(name, kNAHNTKRB5Principal, 0) == 0) 2381 return GSS_KRB5_NT_PRINCIPAL_NAME; 2382 else if (CFStringCompare(name, kNAHNTUUID, 0) == 0) 2383 return GSS_C_NT_UUID; 2384 2385 return NULL; 2386} 2387 2388 2389 2390gss_cred_id_t 2391NAHSelectionGetGSSCredential(NAHSelectionRef selection, CFErrorRef *error) 2392{ 2393 gss_buffer_desc buffer; 2394 OM_uint32 minor_status, major_status, junk; 2395 gss_name_t name; 2396 gss_cred_id_t cred = NULL; 2397 gss_OID nt; 2398 2399 if (error) 2400 *error = NULL; 2401 2402 if (selection->client == NULL) 2403 return NULL; 2404 2405 nt = ntstring2oid(selection->clienttype); 2406 if (nt == NULL) 2407 nt = GSS_C_NT_USER_NAME; 2408 2409 buffer.value = cf2cstring(selection->client); 2410 if (buffer.value == NULL) 2411 return NULL; 2412 buffer.length = strlen((char *)buffer.value); 2413 2414 major_status = gss_import_name(&minor_status, &buffer, nt, &name); 2415 free(buffer.value); 2416 if (major_status) { 2417 updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server); 2418 return NULL; 2419 } 2420 2421 major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL); 2422 gss_release_name(&junk, &name); 2423 if (major_status) { 2424 updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), selection->server); 2425 return NULL; 2426 } 2427 2428 return cred; 2429} 2430 2431gss_name_t 2432NAHSelectionGetGSSAcceptorName(NAHSelectionRef selection, CFErrorRef *error) 2433{ 2434 gss_buffer_desc buffer; 2435 OM_uint32 minor_status, major_status; 2436 gss_name_t name; 2437 gss_OID nt; 2438 2439 if (error) 2440 *error = NULL; 2441 2442 if (selection->server == NULL) 2443 return GSS_C_NO_NAME; 2444 2445 buffer.value = cf2cstring(selection->server); 2446 if (buffer.value == NULL) 2447 return NULL; 2448 buffer.length = strlen((char *)buffer.value); 2449 2450 nt = ntstring2oid(selection->servertype); 2451 if (nt == NULL) 2452 nt = GSS_C_NT_HOSTBASED_SERVICE; 2453 2454 major_status = gss_import_name(&minor_status, &buffer, nt, &name); 2455 free(buffer.value); 2456 if (major_status) 2457 updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), selection->server); 2458 2459 return name; 2460} 2461 2462gss_OID 2463NAHSelectionGetGSSMech(NAHSelectionRef selection) 2464{ 2465 return mech2oid(selection->mech); 2466} 2467 2468/* 2469 * Same again, but for AuthenticationInfo dictionary 2470 */ 2471 2472gss_cred_id_t 2473NAHAuthenticationInfoCopyClientCredential(CFDictionaryRef authInfo, CFErrorRef *error) 2474{ 2475 gss_buffer_desc buffer; 2476 OM_uint32 minor_status, major_status, junk; 2477 gss_name_t name; 2478 gss_cred_id_t cred = NULL; 2479 gss_OID nt, mech; 2480 2481 if (error) 2482 *error = NULL; 2483 2484 CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHCredentialType); 2485 CFStringRef clientName = CFDictionaryGetValue(authInfo, kNAHClientPrincipal); 2486 CFStringRef clientNameType = CFDictionaryGetValue(authInfo, kNAHClientNameType); 2487 2488 if (mechanism == NULL || clientName == NULL || clientNameType == NULL) { 2489 updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo")); 2490 return NULL; 2491 } 2492 2493 mech = name2oid(mechanism); 2494 if (mech == NULL) { 2495 updateError(NULL, error, EINVAL, CFSTR("unknown mech")); 2496 return NULL; 2497 } 2498 2499 nt = ntstring2oid(clientNameType); 2500 if (nt == NULL) 2501 nt = GSS_C_NT_USER_NAME; 2502 2503 buffer.value = cf2cstring(clientName); 2504 if (buffer.value == NULL) 2505 return NULL; 2506 buffer.length = strlen((char *)buffer.value); 2507 2508 major_status = gss_import_name(&minor_status, &buffer, nt, &name); 2509 free(buffer.value); 2510 if (major_status) { 2511 updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), clientName); 2512 return NULL; 2513 } 2514 2515 major_status = gss_acquire_cred(&minor_status, name, GSS_C_INDEFINITE, NULL, GSS_C_INITIATE, &cred, NULL, NULL); 2516 gss_release_name(&junk, &name); 2517 if (major_status) { 2518 updateError(NULL, error, major_status, CFSTR("Failed create credential for %@"), clientName); 2519 return NULL; 2520 } 2521 2522 return cred; 2523} 2524 2525gss_name_t 2526NAHAuthenticationInfoCopyServerName(CFDictionaryRef authInfo, CFErrorRef *error) 2527{ 2528 gss_buffer_desc buffer; 2529 OM_uint32 minor_status, major_status; 2530 gss_name_t name; 2531 gss_OID nt; 2532 2533 if (error) 2534 *error = NULL; 2535 2536 CFStringRef serverName = CFDictionaryGetValue(authInfo, kNAHServerPrincipal); 2537 CFStringRef serverNameType = CFDictionaryGetValue(authInfo, kNAHServerNameType); 2538 2539 if (serverName == NULL || serverNameType == NULL) { 2540 updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo")); 2541 return NULL; 2542 } 2543 2544 nt = ntstring2oid(serverNameType); 2545 if (nt == NULL) 2546 nt = GSS_C_NT_HOSTBASED_SERVICE; 2547 2548 buffer.value = cf2cstring(serverName); 2549 if (buffer.value == NULL) 2550 return NULL; 2551 buffer.length = strlen((char *)buffer.value); 2552 2553 major_status = gss_import_name(&minor_status, &buffer, nt, &name); 2554 free(buffer.value); 2555 if (major_status) { 2556 updateError(NULL, error, major_status, CFSTR("Failed create name for %@"), serverName); 2557 return NULL; 2558 } 2559 2560 return name; 2561} 2562 2563gss_OID 2564NAHAuthenticationInfoGetGSSMechanism(CFDictionaryRef authInfo, CFErrorRef *error) 2565{ 2566 CFStringRef mechanism = CFDictionaryGetValue(authInfo, kNAHMechanism); 2567 2568 if (mechanism == NULL) { 2569 updateError(NULL, error, EINVAL, CFSTR("key missing from AuthenticationInfo")); 2570 return NULL; 2571 } 2572 2573 return name2oid(mechanism); 2574} 2575 2576/* 2577 * These are the maching the OID version kGSSAPIMech<name>OID 2578 */ 2579 2580CFStringRef kGSSAPIMechNTLM = CFSTR("NTLM"); 2581CFStringRef kGSSAPIMechKerberos = CFSTR("Kerberos"); 2582CFStringRef kGSSAPIMechKerberosU2U = CFSTR("KerberosUser2User"); 2583CFStringRef kGSSAPIMechKerberosMicrosoft = CFSTR("KerberosMicrosoft"); 2584CFStringRef kGSSAPIMechIAKerb = CFSTR("IAKerb"); 2585CFStringRef kGSSAPIMechPKU2U = CFSTR("PKU2U"); 2586CFStringRef kGSSAPIMechSPNEGO = CFSTR("SPNEGO"); 2587