1/* 2 * Copyright (c) 2006-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 "KerberosHelper.h" 26#include "KerberosHelperContext.h" 27#include "lookupDSLocalKDC.h" 28#include "util.h" 29 30#include <Heimdal/locate_plugin.h> 31 32#include <GSS/gssapi.h> 33#include <GSS/gssapi_spi.h> 34#include <GSS/gssapi_spnego.h> 35#include <GSS/spnego_asn1.h> 36 37#include <Carbon/Carbon.h> 38#include <CoreServices/CoreServices.h> 39#include <CoreServices/CoreServicesPriv.h> 40#include <Security/Security.h> 41#include <Security/SecCertificateOIDs.h> 42#include <Security/SecCertificatePriv.h> 43#include <CommonCrypto/CommonDigest.h> 44#include <unistd.h> 45#include <sys/socket.h> 46#include <arpa/inet.h> 47#include <netdb.h> 48 49#include <asl.h> 50 51#include "DeconstructServiceName.h" 52#include "utils.h" 53 54int 55der_print_heim_oid ( 56 const heim_oid */*oid*/, 57 char /*delim*/, 58 char **/*str*/); 59 60 61#define DEBUG 0 /* Set to non-zero for more debug spew than you want. */ 62 63static krb5_error_code 64_k5_check_err(krb5_error_code error, const char *function, const char *file, int line) 65{ 66 // if (error) 67 // asl_log(NULL, NULL, ASL_LEVEL_DEBUG, " %s: krb5 call got %d (%s) on %s:%d", function, error, error_message(error), file, line); 68 return error; 69} 70 71// static krb5_error_code _k5_check_err(krb5_error_code error, const char *function, const char *file, int line); 72#define k5_ok(x) _k5_check_err (x, __func__, __FILE__, __LINE__) 73 74#define KHLog(FMT, ...) asl_log(NULL, NULL, ASL_LEVEL_DEBUG, FMT, __VA_ARGS__) 75 76static const char lkdc_prefix[] = "LKDC:"; 77 78/* 79 * 80 */ 81 82static const char wellknown_lkdc[] = "WELLKNOWN:COM.APPLE.LKDC"; 83 84static int 85is_lkdc_realm(const char *realm) 86{ 87 if (realm == NULL) 88 return 0; 89 return strncmp(realm, lkdc_prefix, sizeof(lkdc_prefix)-1) == 0 || 90 strncmp(realm, wellknown_lkdc, sizeof(wellknown_lkdc) - 1) == 0; 91} 92 93/* If realms a and b have a common subrealm, returns the number of 94 * common components. Otherwise, returns zero. 95 */ 96static int 97has_common_subrealm(const char *a, const char *b) 98{ 99 const char *ap = &a[strlen(a)]; 100 const char *bp = &b[strlen(b)]; 101 unsigned int n = 0; 102 103 if (ap == a || bp == b) 104 return 0; 105 for (--ap, --bp; ap >= a && bp >= b && *ap == *bp; --ap, --bp) 106 if ((ap == a && bp == b) || 107 (ap == a && '.' == bp[-1]) || 108 (bp == b && '.' == ap[-1]) || 109 ('.' == ap[-1] && '.' == bp[-1])) 110 ++n; 111 return n; 112} 113 114static const char * 115principal_realm(const char *princ) 116{ 117 const char *p = &princ[strlen(princ)]; 118 119 while (p > princ) { 120 if ('@' == p[0] && '\\' != p[-1]) 121 break; 122 --p; 123 } 124 if (p == princ) 125 return NULL; 126 else 127 return &p[1]; 128} 129 130static void 131add_mapping(KRBHelperContextRef hCtx, const char *hostname, const char *realm, int islkdc) 132{ 133 struct realm_mappings *p; 134 135 p = realloc(hCtx->realms.data, sizeof(hCtx->realms.data[0]) * (hCtx->realms.len + 1)); 136 if (p == NULL) 137 return; 138 hCtx->realms.data = p; 139 140 hCtx->realms.data[hCtx->realms.len].lkdc = islkdc; 141 hCtx->realms.data[hCtx->realms.len].hostname = strdup(hostname); 142 if (hCtx->realms.data[hCtx->realms.len].hostname == NULL) 143 return; 144 145 hCtx->realms.data[hCtx->realms.len].realm = strdup(realm); 146 if (hCtx->realms.data[hCtx->realms.len].realm == NULL) { 147 free(hCtx->realms.data[hCtx->realms.len].hostname); 148 return; 149 } 150 hCtx->realms.len++; 151} 152 153static void 154find_mapping(KRBHelperContextRef hCtx, const char *hostname) 155{ 156 krb5_error_code ret; 157 char **realmlist = NULL; 158 size_t i; 159 160 for (i = 0; i < hCtx->realms.len; i++) 161 if (strcasecmp(hCtx->realms.data[i].hostname, hostname) == 0) 162 return; 163 164 ret = krb5_get_host_realm(hCtx->krb5_ctx, hostname, &realmlist); 165 if (ret == 0) { 166 for (i = 0; realmlist && realmlist[i] && *(realmlist[i]); i++) 167 add_mapping(hCtx, hostname, realmlist[i], 0); 168 if (i == 0) 169 KHLog (" %s: krb5_get_host_realm returned unusable realm!", __func__); 170 } 171 172 if (realmlist) 173 krb5_free_host_realm (hCtx->krb5_ctx, realmlist); 174} 175 176static int 177parse_principal_name(krb5_context ctx, const char *princname, char **namep, char **instancep, char **realmp) 178{ 179 krb5_principal principal = NULL; 180 int err = 0; 181 int len; 182 183 KHLog ("[[[ %s () decomposing %s", __func__, princname); 184 *namep = *instancep = *realmp = NULL; 185 186 if (krb5_parse_name(ctx, princname, &principal)) { 187 err = -1; 188 goto fin; 189 } 190 191 len = krb5_principal_get_num_comp(ctx, principal); 192 if (len > 0) 193 *namep = strdup(krb5_principal_get_comp_string(ctx, principal, 0)); 194 if (len > 1) 195 *instancep = strdup(krb5_principal_get_comp_string(ctx, principal, 1)); 196 *realmp = strdup(krb5_principal_get_realm(ctx, principal)); 197 198 fin: 199 if (NULL != principal) 200 krb5_free_principal(ctx, principal); 201 KHLog ("]]] %s () - %d", __func__, err); 202 return err; 203} 204 205static int 206lookup_by_kdc(KRBhelperContext *hCtx, const char *name, char **realm) 207{ 208 krb5_error_code ret; 209 krb5_cccol_cursor cursor; 210 krb5_ccache id; 211 krb5_creds mcred, *creds; 212 213 memset(&mcred, 0, sizeof(mcred)); 214 *realm = NULL; 215 216 ret = krb5_build_principal(hCtx->krb5_ctx, &mcred.server, 217 0, "", 218 "host", name, NULL); /* XXX propper service name */ 219 if (ret) 220 return ret; 221 222 ret = krb5_cccol_cursor_new(hCtx->krb5_ctx, &cursor); 223 if (ret) 224 return ret; 225 226 while ((ret = krb5_cccol_cursor_next(hCtx->krb5_ctx, cursor, &id)) == 0) { 227 const char *errmsg = NULL; 228 char *clientname; 229 230 if (id == NULL) { 231 ret = memFullErr; /* XXX */ 232 break; 233 } 234 235 ret = krb5_cc_get_principal(hCtx->krb5_ctx, id, &mcred.client); 236 if (ret) 237 goto next; 238 239 ret = krb5_unparse_name(hCtx->krb5_ctx, mcred.client, &clientname); 240 if (ret) { 241 krb5_free_principal(hCtx->krb5_ctx, mcred.client); 242 KHLog ("Failed to unparse name %s () - %d", __func__, ret); 243 goto next; 244 } 245 246 ret = krb5_principal_set_realm(hCtx->krb5_ctx, mcred.server, mcred.client->realm); 247 if (ret) { 248 free(clientname); 249 krb5_free_principal(hCtx->krb5_ctx, mcred.client); 250 goto next; 251 } 252 253 ret = krb5_get_credentials(hCtx->krb5_ctx, 0, id, &mcred, &creds); 254 krb5_free_principal(hCtx->krb5_ctx, mcred.client); 255 if (ret) 256 errmsg = krb5_get_error_message(hCtx->krb5_ctx, ret); 257 KHLog ("krb5_get_credentials(%s): referrals %s () - %s (%d)", 258 clientname, __func__, errmsg ? errmsg : "success", ret); 259 if (errmsg) 260 krb5_free_error_message(hCtx->krb5_ctx, errmsg); 261 free(clientname); 262 if (ret == 0) { 263 *realm = strdup(creds->server->realm); 264 krb5_free_creds(hCtx->krb5_ctx, creds); 265 krb5_cc_close(hCtx->krb5_ctx, id); 266 break; 267 } 268 next: 269 krb5_cc_close(hCtx->krb5_ctx, id); 270 } 271 272 krb5_free_principal(hCtx->krb5_ctx, mcred.server); 273 krb5_cccol_cursor_free(hCtx->krb5_ctx, &cursor); 274 275 return ret; 276} 277 278static int 279strcmp_trailer(const char *f, const char *p) 280{ 281 size_t flen = strlen(f), plen = strlen(p); 282 283 if (f[flen - 1] == '.') 284 flen--; 285 286 if (flen > plen) 287 return flen - plen; 288 return strcasecmp(&f[flen - plen], p); 289} 290 291static int 292is_local_hostname(const char *host) 293{ 294 static dispatch_once_t once; 295 static char *btmmDomain; 296 297 dispatch_once(&once, ^{ 298 CFStringRef d = _CSBackToMyMacCopyDomain(); 299 if (d) { 300 __KRBCreateUTF8StringFromCFString(d, &btmmDomain); 301 CFRelease(d); 302 if (btmmDomain) { 303 size_t len = strlen(btmmDomain); 304 if (len > 0 && btmmDomain[len - 1] == '.') 305 btmmDomain[len - 1] = '\0'; 306 } 307 } 308 }); 309 310 311 if (strcmp_trailer(host, ".local") == 0) 312 return 1; 313 if (btmmDomain && strcmp_trailer(host, btmmDomain) == 0) 314 return 1; 315 if (strchr(host, '.')) 316 return 1; 317 return 0; 318} 319 320/* 321 * 322 */ 323 324OSStatus 325KRBCreateSession(CFStringRef inHostName, CFStringRef inAdvertisedPrincipal, 326 void **outKerberosSession) 327{ 328 KRBHelperContextRef session = NULL; 329 CFMutableDictionaryRef inInfo; 330 OSStatus error; 331 332 *outKerberosSession = NULL; 333 334 inInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 335 if (inInfo == NULL) 336 return memFullErr; 337 338 if (inHostName) 339 CFDictionarySetValue (inInfo, kKRBHostnameKey, inHostName); 340 if (inAdvertisedPrincipal) 341 CFDictionarySetValue (inInfo, kKRBAdvertisedPrincipalKey, inAdvertisedPrincipal); 342 343 error = KRBCreateSessionInfo(inInfo, &session); 344 CFRelease(inInfo); 345 if (error == noErr) 346 *outKerberosSession = session; 347 348 return error; 349} 350 351 352OSStatus 353KRBCreateSessionInfo (CFDictionaryRef inDict, KRBHelperContextRef *outKerberosSession) 354{ 355 CFStringRef inHostName, inAdvertisedPrincipal, noLocalKDC; 356 char hbuf[NI_MAXHOST]; 357 struct addrinfo hints, *aip = NULL; 358 KRBhelperContext *hCtx = NULL; 359 char *tmp = NULL; 360 char *hintname = NULL, *hinthost = NULL, *hintrealm = NULL; 361 char *localname = NULL, *hostname = NULL; 362 struct realm_mappings *selected_mapping = NULL; 363 OSStatus err = noErr; 364 int avoidDNSCanonicalizationBug = 0; 365 size_t i; 366 367 *outKerberosSession = NULL; 368 369 if (NULL == outKerberosSession) 370 return paramErr; 371 372 inHostName = CFDictionaryGetValue(inDict, kKRBHostnameKey); 373 inAdvertisedPrincipal = CFDictionaryGetValue(inDict, kKRBAdvertisedPrincipalKey); 374 noLocalKDC = CFDictionaryGetValue(inDict, kKRBNoLKDCKey); 375 376 KHLog ("[[[ %s () - required parameters okay: %s %s %s", __func__, 377 inHostName ? "iHN" : "-", 378 inAdvertisedPrincipal ? "iAP" : "-", 379 noLocalKDC ? "nLK" : "-"); 380 381 /* 382 * Create the context that we will return on success. 383 * We almost always require a Kerberos context and the default 384 * realm, so populate those up front. 385 */ 386 if (NULL == (hCtx = calloc(1, sizeof(*hCtx)))) 387 return memFullErr; 388 389 if (0 != k5_ok( krb5_init_context (&hCtx->krb5_ctx) )) { 390 err = memFullErr; 391 goto out; 392 } 393 394 if (0 != hx509_context_init(&hCtx->hx_ctx)) { 395 err = memFullErr; 396 goto out; 397 } 398 399 if (0 == k5_ok( krb5_get_default_realm (hCtx->krb5_ctx, &tmp) ) && NULL != tmp) { 400 if (NULL == (hCtx->defaultRealm = strdup (tmp))) { 401 err = memFullErr; 402 goto out; 403 } 404 krb5_xfree(tmp); 405 } 406 407 /* If no host name was given, then the caller is inquiring 408 * about the Local KDC realm. Our work here is done. 409 */ 410 if (NULL == inHostName) { 411 err = DSCopyLocalKDC (&hCtx->realm); 412 KHLog (" %s: LocalKDC realm lookup only", __func__); 413 goto out; 414 } 415 416 /* Retain any given advertised principal name in the context 417 * and break it down into service, instance, and realm. These 418 * will be used as hints when better information is not 419 * available. 420 */ 421 if (NULL != inAdvertisedPrincipal) { 422 char *s = NULL; 423 hCtx->inAdvertisedPrincipal = CFRetain (inAdvertisedPrincipal); 424 if (0 != __KRBCreateUTF8StringFromCFString (inAdvertisedPrincipal, &s)) 425 KHLog (" %s: __KRBCreateUTF8StringFromCFString failed", __func__); 426 else { 427 (void)parse_principal_name (hCtx->krb5_ctx, s, &hintname, &hinthost, &hintrealm); 428 __KRBReleaseUTF8String (s); 429 } 430 } 431 432 /* Decode the given host name with _CFNetServiceDeconstructServiceName before proceeding. */ 433 434 if (! _CFNetServiceDeconstructServiceName (inHostName, &hostname)) 435 __KRBCreateUTF8StringFromCFString (inHostName, &hostname); 436 else 437 avoidDNSCanonicalizationBug = 1; 438 439 /* remove trailing dot */ 440 i = strlen(hostname); 441 if (hostname[i - 1] == '.') { 442 hostname[i - 1] = '\0'; 443 444 /* Stuff it back into context */ 445 hCtx->inHostName = CFStringCreateWithCString (NULL, hostname, kCFStringEncodingUTF8); 446 if (hCtx->inHostName == NULL) { 447 err = memFullErr; 448 goto out; 449 } 450 } else { 451 hCtx->inHostName = CFRetain (inHostName); 452 } 453 454 KHLog (" %s: processed host name = %s", __func__, hostname); 455 456 /* 457 * Try find name by asking the KDC first 458 */ 459 460 if (lookup_by_kdc(hCtx, hostname, &tmp) == 0) { 461 add_mapping(hCtx, hostname, tmp, 0); 462 free(tmp); 463 err = noErr; 464 hCtx->noGuessing = 1; 465 goto done; 466 } 467 468 469 /* 470 * If the given name is a bare name (i.e. no dots), we may need 471 * to attempt to look it up as `<name>.local' later. 472 */ 473 if (NULL == strchr(hostname, '.') && 474 (0 > asprintf(&localname, "%s.local", hostname) || NULL == localname)) { 475 err = memFullErr; 476 goto out; 477 } 478 479 /* 480 * Before we canonlize the hostname, lets find the realm. 481 */ 482 483 find_mapping(hCtx, hostname); 484 485 /* Normalize the given host name using getaddrinfo AI_CANONNAME if 486 * possible. Track the resulting host name (normalized or not) as 487 * `hostname'. 488 * 489 * Avoid canonicalization if possible because of 490 * <rdar://problem/5517187> getaddrinfo with hints.ai_flags = 491 * AI_CANONNAME mangles quoted DNS Names. 492 */ 493 { 494 memset (&hints, 0, sizeof(hints)); 495 hints.ai_flags = AI_CANONNAME; 496 err = getaddrinfo (hostname, NULL, &hints, &hCtx->addr); 497 KHLog (" %s: getaddrinfo = %s (%d)", __func__, 0 == err ? "success" : gai_strerror (err), (int)err); 498 if (0 == err && avoidDNSCanonicalizationBug == 0 && hCtx->addr->ai_canonname) { 499 if ((tmp = strdup(hCtx->addr->ai_canonname)) != NULL) { 500 free(hostname); 501 hostname = tmp; 502 } 503 KHLog (" %s: canonical host name = %s", __func__, hostname); 504 } 505 } 506 507 /* 508 * Try adding the mapping for the canonlical name if we got one 509 */ 510 511 find_mapping(hCtx, hostname); 512 513 /* 514 * If we have a hintrealm and there our initial guessing is right, 515 * lets skip the reverse lookup since that is potentially very 516 * expensive. 517 */ 518 519 if (hintrealm) { 520 for (i = 0; i < hCtx->realms.len; i++) 521 if (strcmp(hintrealm, hCtx->realms.data[i].realm) == 0) 522 goto done; 523 } 524 525 /* 526 * Try to find all name for this address, and the check if we can 527 * find a mapping. 528 */ 529 530 for (aip = hCtx->addr; NULL != aip; aip = aip->ai_next) { 531 char ipbuf[NI_MAXHOST]; 532 533 /* pretty print name first for logging */ 534 err = getnameinfo(aip->ai_addr, aip->ai_addrlen, 535 ipbuf, sizeof(ipbuf), 536 NULL, 0, NI_NUMERICHOST); 537 if (err) 538 snprintf(ipbuf, sizeof(ipbuf), "getnameinfo-%d", (int)err); 539 540 err = getnameinfo (aip->ai_addr, aip->ai_addr->sa_len, hbuf, 541 sizeof(hbuf), NULL, 0, NI_NAMEREQD); 542 KHLog(" %s: getnameinfo(%s) -> %s result %d %s", 543 __func__, ipbuf, hbuf, (int)err, 0 == err ? "success" : gai_strerror (err)); 544 if (err) { 545 /* This is not a fatal error. We'll keep looking for candidate host names. */ 546 continue; 547 } 548 find_mapping(hCtx, hbuf); 549 } 550 551 /* Reset err */ 552 err = noErr; 553 554 /* 555 * Also, add localname (bare name) that we turned into a .local 556 * name if we had one. 557 */ 558 559 if (localname) 560 find_mapping(hCtx, localname); 561 562 done: 563 /* 564 * Done fetching all data, will no try to find a mapping 565 */ 566 567 for (i = 0; i < hCtx->realms.len; i++) { 568 KHLog (" %s: available mappings: %s -> %s (%s)", __func__, 569 hCtx->realms.data[i].hostname, 570 hCtx->realms.data[i].realm, 571 hCtx->realms.data[i].lkdc ? "LKDC" : "managed"); 572 } 573 574 /* 575 * If we have noGuessing mapping, lets pick the first guess then. 576 */ 577 578 if (!selected_mapping && hCtx->noGuessing && hCtx->realms.len) 579 selected_mapping = &hCtx->realms.data[0]; 580 581 /* 582 * If we have "local" hostname, lets consider LKDC more aggressively. 583 */ 584 585 if (noLocalKDC == 0 && is_local_hostname(hostname)) { 586 587 if (hintrealm) { 588 /* 589 * Search for localKDC mapping when we have a hint realm. 590 */ 591 for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) 592 if (strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0) 593 selected_mapping = &hCtx->realms.data[i]; 594 } else { 595 /* 596 * If we are using have no hintrealm, just pick any LKDC realm. 597 */ 598 for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) 599 if (hCtx->realms.data[i].lkdc) 600 selected_mapping = &hCtx->realms.data[i]; 601 } 602 } 603 /* 604 * Search for managed realm, the make us prefer manged realms for 605 * no local hostnames. 606 */ 607 for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) { 608 if (hCtx->realms.data[i].lkdc) 609 continue; 610 if (hintrealm == NULL || strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0) 611 selected_mapping = &hCtx->realms.data[i]; 612 } 613 614 /* 615 * Search for LKDC again if no managed realm was found 616 */ 617 for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) 618 if (hintrealm == NULL || strcasecmp(hintrealm, hCtx->realms.data[i].realm) == 0) 619 selected_mapping = &hCtx->realms.data[i]; 620 621 /* 622 * If we still failed to find a mapping, just pick the first. 623 */ 624 for (i = 0; i < hCtx->realms.len && !selected_mapping; i++) 625 selected_mapping = &hCtx->realms.data[i]; 626 627 if (selected_mapping == NULL) { 628 KHLog (" %s: No mapping for host name = %s found", __func__, hostname); 629 err = memFullErr; 630 goto out; 631 } 632 KHLog (" %s: Using host name = %s, realm = %s (%s)", __func__, 633 selected_mapping->hostname, 634 selected_mapping->realm, selected_mapping->lkdc ? "LKDC" : "managed"); 635 636 hCtx->realm = CFStringCreateWithCString (kCFAllocatorDefault, selected_mapping->realm, kCFStringEncodingASCII); 637 if (hCtx->realm == NULL) { 638 err = memFullErr; 639 goto out; 640 } 641 642 /* If its a LKDC realm, the hostname is the LKDC realm */ 643 if (selected_mapping->lkdc) 644 hCtx->hostname = CFRetain(hCtx->realm); 645 else 646 hCtx->hostname = CFStringCreateWithCString(kCFAllocatorDefault, selected_mapping->hostname, kCFStringEncodingASCII); 647 if (hCtx->hostname == NULL) { 648 err = memFullErr; 649 goto out; 650 } 651 652 out: 653 free (hintname); 654 free (hinthost); 655 free (hintrealm); 656 free (hostname); 657 free (localname); 658 659 /* 660 * On error, free all members of the context and the context itself. 661 */ 662 if (noErr != err) { 663 for (i = 0; i < hCtx->realms.len; i++) { 664 free(hCtx->realms.data[i].hostname); 665 free(hCtx->realms.data[i].realm); 666 } 667 free(hCtx->realms.data); 668 free (hCtx->defaultRealm); 669 if (NULL != hCtx->realm) 670 CFRelease (hCtx->realm); 671 if (NULL != hCtx->inAdvertisedPrincipal) 672 CFRelease (hCtx->inAdvertisedPrincipal); 673 if (NULL != hCtx->hostname) 674 CFRelease (hCtx->hostname); 675 if (NULL != hCtx->inHostName) 676 CFRelease (hCtx->inHostName); 677 if (NULL != hCtx->krb5_ctx) 678 krb5_free_context (hCtx->krb5_ctx); 679 if (NULL != hCtx->hx_ctx) 680 hx509_context_free(&hCtx->hx_ctx); 681 if (NULL != hCtx->addr) 682 freeaddrinfo(hCtx->addr); 683 684 free (hCtx); 685 } else 686 *outKerberosSession = hCtx; 687 688 KHLog ("]]] %s () = %d", __func__, (int)err); 689 return err; 690} 691 692/* 693 694 KRBCopyREALM will return the REALM for the host that was passed to KRBCreateSession 695 inKerberosSession is the pointer returned by KRBCreateSession 696 outREALM is the REALM of the host 697*/ 698OSStatus KRBCopyRealm(KRBHelperContextRef inKerberosSession, CFStringRef *outRealm) 699{ 700 OSStatus err = noErr; 701 702 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 703 704 if (NULL == hCtx) { 705 err = paramErr; /* Invalid session context */ 706 goto Done; 707 } 708 709 KHLog ("%s", "[[[ KRBCopyRealm () - required parameters okay"); 710 711 if (NULL == hCtx->realm) { 712 err = paramErr; 713 goto Done; 714 } 715 716 *outRealm = CFRetain (hCtx->realm); 717 Done: 718 KHLog ("]]] KRBCopyRealm () = %d", (int)err); 719 720 return err; 721} 722 723/* 724 KRBCopyKeychainLookupInfo will return a dictionary containing information related to Kerberos and keychain items. 725 inKerberosSession is the pointer returned by KRBCreateSession 726 inUsername is an available and usable Username or NULL 727 outKeychainLookupInfo is a dictionary containing keychain lookup info and if it is acceptable to store a 728 password in the keychain. 729 730 outKeychainLookupInfo 731 kKRBUsernameKey : CFStringRef 732 kKRBKeychainAccountNameKey : CFStringRef 733 kKRBDisableSaveToKeychainKey : CFBooleanRef 734 735*/ 736OSStatus KRBCopyKeychainLookupInfo (KRBHelperContextRef inKerberosSession, CFStringRef inUsername, CFDictionaryRef *outKeychainLookupInfo) 737{ 738 OSStatus err = noErr; 739 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 740 CFMutableDictionaryRef outInfo = NULL; 741 CFStringRef accountName = NULL; 742 743 if (NULL == inKerberosSession || NULL == outKeychainLookupInfo) { err = paramErr; goto Error; } 744 745 *outKeychainLookupInfo = NULL; 746 747 outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, /* hint */ 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 748 749 if (NULL == outInfo) { err = memFullErr; goto Error; } 750 751 KHLog ("%s", "[[[ KRBCopyKeychainLookupInfo () - required parameters okay"); 752 753 if (NULL != inUsername) { 754 CFDictionarySetValue (outInfo, kKRBUsernameKey, inUsername); 755 accountName = hCtx->realm; 756 } else { 757 accountName = hCtx->realm; 758 } 759 760 if (NULL != accountName) { 761 CFDictionarySetValue (outInfo, kKRBKeychainAccountNameKey, accountName); 762 } 763 764 /* Can a Kerberos password be saved in the keychain? */ 765 CFDictionarySetValue (outInfo, kKRBDisableSaveToKeychainKey, kCFBooleanFalse); 766 767 CFPropertyListRef savePasswordDisabled = CFPreferencesCopyAppValue(CFSTR("SavePasswordDisabled"), kKRBAgentBundleIdentifier); 768 769 if (savePasswordDisabled != NULL) { 770 if (CFGetTypeID(savePasswordDisabled) == CFBooleanGetTypeID() && CFBooleanGetValue(savePasswordDisabled)) { 771 CFDictionarySetValue (outInfo, kKRBDisableSaveToKeychainKey, kCFBooleanTrue); 772 KHLog ("%s", " KRBCopyKeychainLookupInfo: DisableSaveToKeychainKey = TRUE"); 773 } 774 CFRelease(savePasswordDisabled); 775 } else { 776 KHLog ("%s", " KRBCopyKeychainLookupInfo: CFPreferencesCopyAppValue == NULL"); 777 } 778 779 *outKeychainLookupInfo = outInfo; 780 Error: 781 KHLog ("]]] KRBCopyKeychainLookupInfo () = %d", (int)err); 782 783 return err; 784} 785 786/* 787 KRBCopyServicePrincipal will return the service principal for a service on the host given inInstance. 788 inKerberosSession is the pointer returned by KRBCreateSession 789 inServiceName is the name of the service on the host, it can be NULL if inAdvertisedPrincipal was non-NULL. 790 However it is highly recommended that this be set as it is insecure to rely on remotely provided information 791 outServicePrincipal the service principal 792*/ 793OSStatus KRBCopyServicePrincipal (KRBHelperContextRef inKerberosSession, CFStringRef inServiceName, CFStringRef *outServicePrincipal) 794{ 795 CFDictionaryRef outDict = NULL; 796 CFStringRef outSPN; 797 OSStatus err; 798 799 KHLog ("%s", "KRBCopyServicePrincipal () - enter"); 800 801 if (NULL == inKerberosSession || NULL == outServicePrincipal) 802 return paramErr; 803 804 *outServicePrincipal = NULL; 805 806 err = KRBCopyServicePrincipalInfo(inKerberosSession, 807 inServiceName, 808 &outDict); 809 if (err) 810 return err; 811 812 outSPN = CFDictionaryGetValue(outDict, kKRBServicePrincipalKey); 813 814 *outServicePrincipal = CFRetain(outSPN); 815 CFRelease(outDict); 816 817 KHLog ("%s", "KRBCopyServicePrincipal () - return"); 818 819 return noErr; 820} 821 822 823OSStatus 824KRBCopyServicePrincipalInfo (KRBHelperContextRef inKerberosSession, CFStringRef inServiceName, CFDictionaryRef *outServiceInfo) 825{ 826 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 827 CFMutableDictionaryRef outInfo = NULL; 828 CFStringRef outString = NULL; 829 OSStatus err = noErr; 830 831 if (NULL == hCtx || NULL == outServiceInfo || NULL == inServiceName) 832 return paramErr; 833 834 *outServiceInfo = NULL; 835 836 KHLog ("%s", "[[[ KRBCopyServicePrincipalInfo () - required parameters okay"); 837 838 outString = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@/%@@%@"), inServiceName, hCtx->hostname, hCtx->realm); 839 if (outString == NULL) { 840 err = memFullErr; 841 goto out; 842 } 843 844 outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 2, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 845 if (outInfo == NULL) { 846 err = memFullErr; 847 goto out; 848 } 849 850 CFDictionarySetValue (outInfo, kKRBServicePrincipalKey, outString); 851 if (hCtx->noGuessing) 852 CFDictionarySetValue (outInfo, kKRBNoCanonKey, CFSTR("nodns")); 853 854 855 { 856 char *spn; 857 __KRBCreateUTF8StringFromCFString (outString, &spn); 858 KHLog (" KRBCopyServicePrincipalInfo: principal = \"%s\"", spn); 859 __KRBReleaseUTF8String (spn); 860 } 861 862 *outServiceInfo = outInfo; 863 864 out: 865 if (outString) CFRelease(outString); 866 867 KHLog ("]]] KRBCopyServicePrincipalInfo () = %d", (int)err); 868 869 return err; 870} 871 872static CFTypeRef 873search_array(CFArrayRef array, CFTypeRef key) 874{ 875 CFDictionaryRef dict; 876 CFIndex n; 877 878 for (n = 0 ; n < CFArrayGetCount(array); n++) { 879 dict = CFArrayGetValueAtIndex(array, n); 880 if (CFGetTypeID(dict) != CFDictionaryGetTypeID()) 881 continue; 882 CFTypeRef dictkey = CFDictionaryGetValue(dict, kSecPropertyKeyLabel); 883 if (CFEqual(dictkey, key)) 884 return CFDictionaryGetValue(dict, kSecPropertyKeyValue); 885 } 886 return NULL; 887} 888 889/* 890 KRBCopyClientPrincipalInfo will return a dictionary with the user principal and other information. 891 inKerberosSession is the pointer returned by KRBCreateSession. 892 inOptions a dictionary with options regarding the acquisition of the user principal. 893 inIdentityRef is a reference to list of usable identities 894 outClientPrincipalInfo a dictionary containing the user principal and other information necessary to get a ticket. 895 896 inOptions Dictionary Keys 897 kKRBAllowKerberosUIKey : CFStringRef [See AllowKeberosUI values] 898 kKRBServerDisplayNameKey : CFStringRef 899 kKRBUsernameKey : CFStringRef 900 kKRBClientPasswordKey : CFStringRef 901 kKRBCertificateKey : SecCertificateRef 902 903 outClientPrincipalInfo 904 kKRBClientPrincipalKey : CFStringRef 905 kKRBUsernameKey : CFStringRef 906 and private information 907*/ 908 909OSStatus KRBCopyClientPrincipalInfo (KRBHelperContextRef inKerberosSession, CFDictionaryRef inOptions, CFDictionaryRef *outClientPrincipalInfo) 910{ 911 OSStatus err = noErr; 912 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 913 914 CFIndex newCount; 915 CFMutableDictionaryRef outInfo = NULL; 916 CFStringRef clientPrincipal = NULL; 917 char *clientPrincipalString = NULL; 918 char *useRealm = NULL; 919 CFStringRef useClientName = NULL; 920 CFStringRef inferredLabel = NULL; 921 SecCertificateRef certRef = NULL; 922 CFStringRef certificateHash = NULL; 923 char *cert_hash = NULL; 924 int usingCertificate = 0; 925 int clientNameProvided = 0; 926 927 if (NULL == hCtx || NULL == outClientPrincipalInfo) { err = paramErr; goto Error; } 928 *outClientPrincipalInfo = NULL; 929 930 KHLog ("%s", "[[[ KRBCopyClientPrincipalInfo () - required parameters okay"); 931 932 if (NULL != inOptions) { 933 /* Figure out the maximum expansion we'll make */ 934 newCount = CFDictionaryGetCount (inOptions) + 3; 935 936 /* Create a mutable copy of the Dictionary. */ 937 outInfo = CFDictionaryCreateMutableCopy (kCFAllocatorDefault, newCount, inOptions); 938 939 /* Extract the certRef if there was an input dictionary */ 940 CFDictionaryGetValueIfPresent (inOptions, kKRBCertificateKey, (const void **)&certRef); 941 942 if (NULL != certRef) { 943 KHLog ("%s", " KRBCopyClientPrincipalInfo: Certificate information in dictionary"); 944 } else { 945 KHLog ("%s", " KRBCopyClientPrincipalInfo: Certificate not present in dictionary"); 946 } 947 } else { 948 outInfo = CFDictionaryCreateMutable (kCFAllocatorDefault, 3, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 949 } 950 951 if (NULL != certRef && SecCertificateGetTypeID() == CFGetTypeID (certRef)) { 952 const CFStringRef dotmac = CFSTR(".Mac Sharing Certificate"); 953 const CFStringRef mobileMe = CFSTR("MobileMe Sharing Certificate"); 954 955 useClientName = NAHCopyMMeUserNameFromCertificate(certRef); 956 if (useClientName == NULL) { goto Error; } 957 958 usingCertificate = 1; 959 960 if (NULL != useClientName) { 961 certificateHash = CFRetain (useClientName); 962 } 963 964 void *values[4] = { (void *)kSecOIDDescription, (void *)kSecOIDCommonName, (void *)kSecOIDOrganizationalUnitName, (void *)kSecOIDX509V1SubjectName }; 965 CFArrayRef attrs = CFArrayCreate(NULL, (const void **)values, sizeof(values) / sizeof(values[0]), &kCFTypeArrayCallBacks); 966 if (NULL == attrs) { goto Error; } 967 968 CFDictionaryRef certval = SecCertificateCopyValues(certRef, attrs, NULL); 969 CFRelease(attrs); 970 if (NULL == certval) { goto Error; } 971 972 err = 0; 973 974 CFDictionaryRef subject = CFDictionaryGetValue(certval, kSecOIDX509V1SubjectName); 975 if (NULL != subject) { 976 977 CFArrayRef val = CFDictionaryGetValue(subject, kSecPropertyKeyValue); 978 979 if (NULL != val) { 980 981 CFStringRef description = search_array(val, kSecOIDDescription); 982 983 if (NULL != description && 984 (kCFCompareEqualTo == CFStringCompare(description, dotmac, 0) || kCFCompareEqualTo == CFStringCompare(description, mobileMe, 0))) 985 { 986 CFStringRef commonName = search_array(val, kSecOIDCommonName); 987 CFStringRef organizationalUnit = search_array(val, kSecOIDOrganizationalUnitName); 988 989 if (NULL != commonName && NULL != organizationalUnit) { 990 inferredLabel = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%@"), commonName, organizationalUnit); 991 } 992 } 993 } 994 } 995 996 CFRelease(certval); 997 998 if (NULL == inferredLabel) 999 err = SecCertificateInferLabel (certRef, &inferredLabel); 1000 1001 if (0 != err) { goto Error; } 1002 1003 } else if (NULL != inOptions) { 1004 CFDictionaryGetValueIfPresent (inOptions, kKRBUsernameKey, (const void **)&useClientName); 1005 if (NULL != useClientName) { 1006 CFRetain(useClientName); 1007 clientNameProvided = 1; 1008 } 1009 } 1010 1011 /* The ultimate fallback is to use the same username as we have locally */ 1012 if (NULL == useClientName) { 1013 char *clientName = getlogin (); 1014 1015 if (NULL != clientName) { 1016 useClientName = CFStringCreateWithCString (NULL, clientName, kCFStringEncodingUTF8); 1017 /* free (clientName); */ 1018 KHLog (" KRBCopyClientPrincipalInfo: Using login name = \"%s\"", clientName); 1019 } 1020 } 1021 1022 /* 1023 * Check to see if the defaultRealm is heirarchically related to the realm used for 1024 * the service principal. 1025 */ 1026 if (NULL != hCtx->useRealm && NULL != hCtx->defaultRealm) { 1027 size_t useRealmLength = strlen (hCtx->useRealm); 1028 size_t defaultRealmLength = strlen (hCtx->defaultRealm); 1029 1030 if (defaultRealmLength < useRealmLength) { 1031 char *subRealm = hCtx->useRealm + (useRealmLength - defaultRealmLength); 1032 // printf ("defaultRealm = %s, subRealm = %s\n", hCtx->defaultRealm, subRealm); 1033 if (0 == strcmp (subRealm, hCtx->defaultRealm)) { 1034 useRealm = hCtx->defaultRealm; 1035 } 1036 } 1037 } 1038 1039 if (NULL != useRealm) { 1040 clientPrincipal = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%s"), useClientName, useRealm); 1041 } else { 1042 clientPrincipal = CFStringCreateWithFormat (NULL, NULL, CFSTR("%@@%@"), useClientName, hCtx->realm); 1043 } 1044 1045 if (NULL != clientPrincipal) { 1046 __KRBCreateUTF8StringFromCFString (clientPrincipal, &clientPrincipalString); 1047 KHLog (" KRBCopyClientPrincipalInfo: principal guess = \"%s\"", clientPrincipalString); 1048 } 1049 1050 /* 1051 * Look in the ccache for a TGT from a REALM that matches "useRealm". If we find one, then 1052 * use that principal. Also extract the "Username" portion just in 1053 * case a password is needed (the ticket may have expired). 1054 */ 1055 if (NULL != clientPrincipalString) { 1056 krb5_error_code krb_err = 0; 1057 krb5_cccol_cursor cursor; 1058 const char *alternateRealm = NULL; 1059 const char *clientRealm = principal_realm(clientPrincipalString); 1060 char *alternateClientPrincipal = NULL; 1061 char *bestClientPrincipal = NULL; 1062 int commonSubrealms = 1, tmp, found; 1063 1064 found = 0; 1065 1066 krb_err = krb5_cccol_cursor_new (hCtx->krb5_ctx, &cursor); 1067 if (0 != krb_err) { 1068 krb_err = memFullErr; 1069 } 1070 1071 /* We exit if we find more than one match */ 1072 while (!krb_err && !found) { 1073 krb5_ccache ccache = NULL; 1074 krb5_principal ccachePrinc = NULL; 1075 1076 if (krb5_cccol_cursor_next (hCtx->krb5_ctx, cursor, &ccache) != 0 || ccache == NULL) 1077 krb_err = ENOENT; 1078 1079 if (!krb_err) { krb_err = k5_ok (krb5_cc_get_principal (hCtx->krb5_ctx, ccache, &ccachePrinc)); } 1080 if (!krb_err) { krb_err = k5_ok (krb5_unparse_name (hCtx->krb5_ctx, ccachePrinc, &alternateClientPrincipal)); } 1081 1082 if (!krb_err) { alternateRealm = principal_realm(alternateClientPrincipal); } 1083 1084 /* If the client principal realm and the service principal 1085 * realm are an exact match or have a common subrealm with 1086 * multiple components, then in most cases it will be the 1087 * one to use. If there are multiple matches, choose the 1088 * one with the largest number of common components. The 1089 * commonSubrealms variable keeps track of the number of 1090 * components matched in the best match found so far. 1091 * Because it was initialized to 1 earlier, a realm must 1092 * have at least two common components to be considered. 1093 */ 1094 if (!krb_err && (0 == (tmp = strcmp(clientRealm, alternateRealm)) || 1095 commonSubrealms < (tmp = has_common_subrealm(clientRealm, alternateRealm)))) { 1096 if (NULL != bestClientPrincipal) { free(bestClientPrincipal); } 1097 bestClientPrincipal = strdup(alternateClientPrincipal); 1098 if (0 == tmp) { 1099 /* Exact match (strcmp set tmp to 0.) 1100 * Exit the loop. 1101 */ 1102 found = 1; 1103 } else { 1104 /* Inexact match (has_common_subrealm set tmp nonzero.) 1105 * Keep track of the number of components matched. 1106 */ 1107 commonSubrealms = tmp; 1108 } 1109 } 1110 1111 if (NULL != alternateClientPrincipal) { 1112 krb5_xfree(alternateClientPrincipal); 1113 alternateClientPrincipal = NULL; 1114 } 1115 if (NULL != ccache) { krb5_cc_close (hCtx->krb5_ctx, ccache); } 1116 if (NULL != ccachePrinc) { krb5_free_principal (hCtx->krb5_ctx, ccachePrinc); } 1117 } 1118 1119 if (NULL != cursor) { krb5_cccol_cursor_free(hCtx->krb5_ctx, &cursor); 1120 } 1121 1122 if (NULL != bestClientPrincipal) { 1123 KHLog (" KRBCopyClientPrincipalInfo: ccache principal match = \"%s\"", bestClientPrincipal); 1124 } 1125 1126 /* We only accept the principal when it doesn't match the one we computed. 1127 */ 1128 if (NULL != bestClientPrincipal && !clientNameProvided && 1129 0 != strcmp(clientPrincipalString, bestClientPrincipal)) { 1130 char *useClientNameString = NULL, *startOfRealm; 1131 1132 KHLog ("%s", " KRBCopyClientPrincipalInfo: found a single ticket for realm, replacing principal & username"); 1133 usingCertificate = 0; 1134 1135 if (NULL != clientPrincipal) { CFRelease (clientPrincipal); } 1136 if (NULL != clientPrincipalString) { __KRBReleaseUTF8String (clientPrincipalString); } 1137 1138 clientPrincipal = CFStringCreateWithCString (NULL, bestClientPrincipal, kCFStringEncodingASCII); 1139 clientPrincipalString = strdup (bestClientPrincipal); 1140 1141 /* Extract the "Username" from the principal */ 1142 useClientNameString = strdup (bestClientPrincipal); 1143 1144 /* This is an ugly loop. It does the reverse of krb5_unparse_name () in that it searches from the 1145 * end of the principal looking for an unquoted "@". This is a guaranteed way to strip the realm from 1146 * a principal with the smallest number of lines and the minimum amount of "special" quoting logic 1147 * and knowledge. The alternative would have been to copy the first part of krb5_unparse_name () and 1148 * quoting all of the "special" characters and skipping the last part which appends the realm - this 1149 * takes 95 lines of code in the MIT Kerberos library. 1150 */ 1151 1152 do { 1153 /* This will be NULL, the last '@' char or the 2nd character of useClientNameString */ 1154 startOfRealm = strrchr (&useClientNameString[1], '@'); 1155 1156 if (NULL != startOfRealm) { 1157 /* Is it an escaped realm character? */ 1158 if ('\\' == *(startOfRealm - 1)) { 1159 *(startOfRealm - 1) = '\0'; 1160 } else { 1161 *startOfRealm = '\0'; 1162 break; 1163 } 1164 } 1165 } while (NULL != startOfRealm); 1166 1167 if (NULL != useClientName) { CFRelease (useClientName); } 1168 1169 useClientName = CFStringCreateWithCString (NULL, useClientNameString, kCFStringEncodingUTF8); 1170 1171 KHLog (" KRBCopyClientPrincipalInfo: Setting found Username to = \"%s\"", useClientNameString); 1172 1173 if (NULL != useClientNameString) { free (useClientNameString); } 1174 } 1175 1176 if (NULL != bestClientPrincipal) { free (bestClientPrincipal); } 1177 if (NULL != alternateClientPrincipal) { krb5_xfree(alternateClientPrincipal); } 1178 } 1179 1180 KHLog (" KRBCopyClientPrincipalInfo: using principal = \"%s\"", clientPrincipalString); 1181 1182 CFDictionarySetValue (outInfo, kKRBClientPrincipalKey, clientPrincipal); 1183 CFDictionarySetValue (outInfo, kKRBUsernameKey, useClientName); 1184 1185 KHLog (" KRBCopyClientPrincipalInfo: usingCertificate == %d", usingCertificate); 1186 if (usingCertificate && NULL != certRef) { 1187 CFDictionarySetValue (outInfo, kKRBUsingCertificateKey, certRef); 1188 1189 if (NULL != certificateHash) 1190 CFDictionarySetValue (outInfo, kKRBCertificateHashKey, certificateHash); 1191 1192 if (NULL != inferredLabel) { 1193 CFDictionarySetValue (outInfo, kKRBCertificateInferredLabelKey, inferredLabel); 1194 1195 char *inferredLabelString = NULL; 1196 __KRBCreateUTF8StringFromCFString (inferredLabel, &inferredLabelString); 1197 KHLog (" KRBCopyClientPrincipalInfo: InferredLabel = \"%s\"", inferredLabelString); 1198 if (NULL != inferredLabelString) { __KRBReleaseUTF8String (inferredLabelString); } 1199 } 1200 } 1201 1202 *outClientPrincipalInfo = outInfo; 1203 1204 Error: 1205 if (NULL != useClientName) { CFRelease (useClientName); } 1206 if (NULL != certificateHash) { CFRelease (certificateHash); } 1207 if (NULL != inferredLabel) { CFRelease (inferredLabel); } 1208 if (NULL != cert_hash) { free (cert_hash); } 1209 if (NULL != clientPrincipal) { CFRelease (clientPrincipal); } 1210 if (NULL != clientPrincipalString) { __KRBReleaseUTF8String (clientPrincipalString); } 1211 1212 KHLog ("]]] KRBCopyClientPrincipalInfo () = %d", (int)err); 1213 1214 return err; 1215} 1216 1217/* 1218 KRBTestForExistingTicket will look for an existing ticket in the 1219 ccache. This call looks for a principal that matches the principal 1220 stored in the outClientPrincipalInfo dictionary fom the 1221 KRBCopyClientPrincipalInfo call. 1222 This call should be performed before prompting the user to enter credential 1223 information. 1224 inKerberosSession is the pointer returned by KRBCreateSession 1225 inClientPrincipalInfo the dictionary containing the 1226 kKRBClientPrincipalKey. 1227*/ 1228OSStatus KRBTestForExistingTicket (KRBHelperContextRef inKerberosSession, CFDictionaryRef inClientPrincipalInfo) 1229{ 1230 OSStatus err = noErr; 1231 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 1232 CFStringRef clientPrincipal = NULL; 1233 char *principalString = NULL; 1234 1235 if (NULL == hCtx || NULL == inClientPrincipalInfo) { err = paramErr; goto Done; } 1236 1237 KHLog ("%s", "[[[ KRBTestForExistingTicket () - required parameters okay"); 1238 1239 CFDictionaryGetValueIfPresent (inClientPrincipalInfo, kKRBClientPrincipalKey, (const void **)&clientPrincipal); 1240 1241 if (NULL != clientPrincipal) { 1242 krb5_error_code krb_err = 0; 1243 krb5_principal principal = NULL; 1244 krb5_ccache ccache = NULL; 1245 time_t lifetime; 1246 1247 err = ENOENT; 1248 1249 __KRBCreateUTF8StringFromCFString (clientPrincipal, &principalString); 1250 KHLog (" KRBTestForExistingTicket: principal = \"%s\"", principalString); 1251 1252 krb_err = krb5_parse_name(hCtx->krb5_ctx, principalString, &principal); 1253 if (0 == krb_err) { 1254 krb_err = krb5_cc_cache_match(hCtx->krb5_ctx, principal, &ccache); 1255 if (krb_err == 0) { 1256 krb_err = krb5_cc_get_lifetime(hCtx->krb5_ctx, ccache, &lifetime); 1257 if (krb_err == 0 && lifetime > 60) { 1258 KHLog (" KRBTestForExistingTicket: Valid Ticket, ccacheName = \"%s\"", krb5_cc_get_name(hCtx->krb5_ctx, ccache)); 1259 err = 0; 1260 } 1261 } 1262 } 1263 1264 if (NULL != ccache) { krb5_cc_close(hCtx->krb5_ctx, ccache); } 1265 if (NULL != principalString) { __KRBReleaseUTF8String (principalString); } 1266 if (NULL != principal) { krb5_free_principal(hCtx->krb5_ctx, principal); } 1267 } 1268 1269 Done: 1270 KHLog ("]]] KRBTestForExistingTicket () = %d", (int)err); 1271 1272 return err; 1273} 1274 1275/* 1276 KRBAcquireTicket will acquire a ticket for the user. 1277 inKerberosSession is the pointer returned by KRBCreateSession. 1278 inClientPrincipalInfo is the outClientPrincipalInfo dictionary from KRBCopyClientPrincipalInfo. 1279*/ 1280OSStatus KRBAcquireTicket(KRBHelperContextRef inKerberosSession, CFDictionaryRef inClientPrincipalInfo) 1281{ 1282 OSStatus err = noErr; 1283 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 1284 krb5_error_code krb_err = 0; 1285 krb5_principal clientPrincipal = NULL; 1286 CFStringRef principal = NULL, password = NULL; 1287 char *principalString = NULL, *passwordString = NULL; 1288 SecIdentityRef usingCertificate = NULL; 1289 CFStringRef inferredLabel = NULL; 1290 krb5_get_init_creds_opt *opt; 1291 krb5_ccache id = NULL; 1292 krb5_creds cred; 1293 int destroy_cache = 0; 1294 krb5_init_creds_context icc = NULL; 1295 1296 memset(&cred, 0, sizeof(cred)); 1297 1298 if (NULL == hCtx) { 1299 KHLog ("%s", "[[[ KRBAcquireTicket () - no context will raise() in the future"); 1300 return paramErr; 1301 } 1302 1303 KHLog ("%s", "[[[ KRBAcquireTicket () - required parameters okay"); 1304 1305 principal = CFDictionaryGetValue (inClientPrincipalInfo, kKRBClientPrincipalKey); 1306 if (NULL == principal) { err = paramErr; goto Error; } 1307 __KRBCreateUTF8StringFromCFString (principal, &principalString); 1308 1309 krb_err = krb5_parse_name(hCtx->krb5_ctx, principalString, &clientPrincipal); 1310 if (krb_err) { 1311 err = paramErr; goto Error; 1312 } 1313 1314 CFDictionaryGetValueIfPresent (inClientPrincipalInfo, kKRBUsingCertificateKey, (const void **)&usingCertificate); 1315 1316 krb_err = k5_ok(krb5_get_init_creds_opt_alloc (hCtx->krb5_ctx, &opt)); 1317 if (krb_err) { 1318 err = paramErr; goto Error; 1319 } 1320 1321 if (usingCertificate) { 1322 krb_err = k5_ok(krb5_get_init_creds_opt_set_pkinit(hCtx->krb5_ctx, opt, clientPrincipal, 1323 NULL, "KEYCHAIN:", 1324 NULL, NULL, 0, 1325 NULL, NULL, NULL)); 1326 KHLog (" KRBAcquireTicket: using a cert, GIC set pkinit returned: %d", krb_err); 1327 if (krb_err) { 1328 err = paramErr; goto Error; 1329 } 1330 } 1331 1332 krb_err = krb5_init_creds_init(hCtx->krb5_ctx, clientPrincipal, NULL, NULL, 1333 0, opt, &icc); 1334 if (krb_err) { 1335 err = paramErr; goto Error; 1336 } 1337 1338 if (is_lkdc_realm(krb5_principal_get_realm(hCtx->krb5_ctx, clientPrincipal))) { 1339 char *hostname; 1340 1341 __KRBCreateUTF8StringFromCFString (hCtx->inHostName, &hostname); 1342 krb5_init_creds_set_kdc_hostname(hCtx->krb5_ctx, icc, hostname); 1343 free(hostname); 1344 } 1345 1346 1347 if (NULL != usingCertificate) { 1348 CFStringRef certInferredLabel; 1349 hx509_cert cert; 1350 1351 krb_err = hx509_cert_init_SecFramework(hCtx->hx_ctx, usingCertificate, &cert); 1352 if (krb_err) { 1353 err = paramErr; goto Error; 1354 } 1355 1356 krb_err = krb5_init_creds_set_pkinit_client_cert(hCtx->krb5_ctx, icc, cert); 1357 if (krb_err) { 1358 err = paramErr; goto Error; 1359 } 1360 1361 passwordString = NULL; 1362 1363 certInferredLabel = CFDictionaryGetValue (inClientPrincipalInfo, kKRBCertificateInferredLabelKey); 1364 if (certInferredLabel) 1365 inferredLabel = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@ (%@)"), 1366 certInferredLabel, hCtx->inHostName); 1367 } else { 1368 1369 CFStringRef clientName; 1370 1371 KHLog (" %s: Not using certificate", __func__); 1372 1373 password = CFDictionaryGetValue (inClientPrincipalInfo, kKRBClientPasswordKey); 1374 if (NULL == password) { 1375 KHLog (" %s: No password, cant get tickets", __func__); 1376 err = paramErr; goto Error; 1377 } 1378 1379 clientName = CFDictionaryGetValue(inClientPrincipalInfo, kKRBUsernameKey); 1380 if (clientName) 1381 inferredLabel = CFStringCreateWithFormat(NULL, NULL, CFSTR("LKDC %@@%@"), clientName, hCtx->inHostName); 1382 1383 __KRBCreateUTF8StringFromCFString (password, &passwordString); 1384 1385 krb_err = krb5_init_creds_set_password(hCtx->krb5_ctx, icc, passwordString); 1386 if (krb_err) { 1387 err = paramErr; goto Error; 1388 } 1389 } 1390 1391 krb_err = krb5_init_creds_get(hCtx->krb5_ctx, icc); 1392 krb5_get_init_creds_opt_free(hCtx->krb5_ctx, opt); 1393 KHLog (" %s: krb5_get_init_creds_password: %d", __func__, krb_err); 1394 if (krb_err != 0) { 1395 err = paramErr; goto Error; 1396 } 1397 1398 krb_err = krb5_init_creds_get_creds(hCtx->krb5_ctx, icc, &cred); 1399 if (krb_err != 0) { 1400 err = paramErr; goto Error; 1401 } 1402 1403 krb_err = krb5_cc_cache_match(hCtx->krb5_ctx, clientPrincipal, &id); 1404 if (krb_err) { 1405 krb_err = krb5_cc_new_unique(hCtx->krb5_ctx, NULL, NULL, &id); 1406 if (krb_err) { 1407 err = paramErr; goto Error; 1408 } 1409 destroy_cache = 1; 1410 } 1411 1412 krb_err = krb5_cc_initialize(hCtx->krb5_ctx, id, clientPrincipal); 1413 if (krb_err) { 1414 err = paramErr; goto Error; 1415 } 1416 1417 krb_err = krb5_cc_store_cred(hCtx->krb5_ctx, id, &cred); 1418 if (krb_err) { 1419 err = paramErr; goto Error; 1420 } 1421 1422 krb_err = krb5_init_creds_store_config(hCtx->krb5_ctx, icc, id); 1423 if (krb_err) { 1424 err = paramErr; goto Error; 1425 } 1426 1427 KRBCredAddReference(principal); 1428 1429 if (inferredLabel) { 1430 char *label = NULL; 1431 1432 KHLog ("%s", " KRBAcquireTicket setting friendly name"); 1433 1434 if (__KRBCreateUTF8StringFromCFString (inferredLabel, &label) == noErr) { 1435 krb5_data data; 1436 data.data = label; 1437 data.length = strlen(label) + 1; 1438 1439 krb5_cc_set_config(hCtx->krb5_ctx, id, NULL, "FriendlyName", &data); 1440 free(label); 1441 } 1442 } 1443 { 1444 krb5_data data; 1445 data.data = "1"; 1446 data.length = 1; 1447 krb5_cc_set_config(hCtx->krb5_ctx, id, NULL, "nah-created", &data); 1448 } 1449 1450 err = noErr; 1451 1452 Error: 1453 if (icc) 1454 krb5_init_creds_free(hCtx->krb5_ctx, icc); 1455 if (id) { 1456 if (err != noErr && destroy_cache) 1457 krb5_cc_close(hCtx->krb5_ctx, id); 1458 else 1459 krb5_cc_close(hCtx->krb5_ctx, id); 1460 } 1461 krb5_free_cred_contents(hCtx->krb5_ctx, &cred); 1462 if (NULL != inferredLabel) { CFRelease(inferredLabel); } 1463 if (NULL != principalString) { __KRBReleaseUTF8String (principalString); } 1464 if (NULL != passwordString) { __KRBReleaseUTF8String (passwordString); } 1465 1466 if (NULL != clientPrincipal) { krb5_free_principal(hCtx->krb5_ctx, clientPrincipal); } 1467 1468 KHLog ("]]] KRBAcquireTicket () = %d", (int)err); 1469 1470 return err; 1471} 1472 1473 1474/* 1475 KRBCloseSession will release the kerberos session 1476 inKerberosSession is the pointer returned by KRBCreateSession. 1477*/ 1478OSStatus KRBCloseSession(KRBHelperContextRef inKerberosSession) 1479{ 1480 OSStatus err = noErr; 1481 KRBhelperContext *hCtx = (KRBhelperContext *)inKerberosSession; 1482 size_t i; 1483 1484 if (NULL == hCtx) { err = paramErr; goto Error; } 1485 1486 KHLog ("%s", "[[[ KRBCloseSession () - required parameters okay"); 1487 1488 for (i = 0; i < hCtx->realms.len; i++) { 1489 free(hCtx->realms.data[i].hostname); 1490 free(hCtx->realms.data[i].realm); 1491 } 1492 free(hCtx->realms.data); 1493 1494 if (NULL != hCtx->inAdvertisedPrincipal) { CFRelease (hCtx->inAdvertisedPrincipal); } 1495 if (NULL != hCtx->hostname) { CFRelease(hCtx->hostname); } 1496 if (NULL != hCtx->inHostName) { CFRelease(hCtx->inHostName); } 1497 if (NULL != hCtx->realm) { CFRelease (hCtx->realm); } 1498 1499 if (NULL != hCtx->defaultRealm) { free(hCtx->defaultRealm); } 1500 if (NULL != hCtx->krb5_ctx) { krb5_free_context (hCtx->krb5_ctx); } 1501 if (NULL != hCtx->hx_ctx) { hx509_context_free(&hCtx->hx_ctx); } 1502 if (NULL != hCtx->addr) { freeaddrinfo(hCtx->addr); } 1503 1504 free(hCtx); 1505 Error: 1506 KHLog ("]]] KRBCloseSession () = %d", (int)err); 1507 1508 return err; 1509} 1510 1511static OSStatus 1512findCred(CFStringRef clientPrincipal, krb5_context context, krb5_ccache *id) 1513{ 1514 krb5_principal client; 1515 krb5_error_code kret; 1516 char *str; 1517 1518 if (__KRBCreateUTF8StringFromCFString (clientPrincipal, &str) != noErr) 1519 return memFullErr; 1520 1521 kret = k5_ok(krb5_parse_name(context, str, &client)); 1522 free(str); 1523 if (0 != kret) 1524 return memFullErr; 1525 1526 kret = k5_ok(krb5_cc_cache_match(context, client, id)); 1527 krb5_free_principal(context, client); 1528 if (0 != kret) 1529 return memFullErr; 1530 1531 return noErr; 1532} 1533 1534OSStatus 1535KRBCredChangeReferenceCount(CFStringRef clientPrincipal, int change, int excl) 1536{ 1537 OSStatus ret = 0; 1538 krb5_context kcontext = NULL; 1539 krb5_ccache id = NULL; 1540 krb5_error_code kret; 1541 krb5_data data; 1542 1543 KHLog ("[[[ KRBCredChangeReferenceCount: %d", change); 1544 1545 kret = k5_ok(krb5_init_context(&kcontext)); 1546 if (0 != kret) { 1547 ret = memFullErr; 1548 goto out; 1549 } 1550 1551 ret = findCred(clientPrincipal, kcontext, &id); 1552 if (ret != noErr) 1553 goto out; 1554 1555 /* Skip SSO cred-caches */ 1556 ret = k5_ok(krb5_cc_get_config(kcontext, id, NULL, "nah-created", &data)); 1557 if (ret) { 1558 ret = 0; 1559 goto out; 1560 } 1561 krb5_data_free(&data); 1562 1563 if (change > 0) 1564 ret = krb5_cc_hold(kcontext, id); 1565 else 1566 ret = krb5_cc_unhold(kcontext, id); 1567 1568 out: 1569 if (id) 1570 krb5_cc_close(kcontext, id); 1571 1572 KHLog ("]]] KRBCredChangeReferenceCount: %d", (int)ret); 1573 1574 if (kcontext) 1575 krb5_free_context(kcontext); 1576 1577 return ret; 1578} 1579 1580OSStatus KRBCredAddReference(CFStringRef clientPrincipal) 1581{ 1582 return KRBCredChangeReferenceCount(clientPrincipal, 1, 0); 1583} 1584 1585OSStatus KRBCredRemoveReference(CFStringRef clientPrincipal) 1586{ 1587 return KRBCredChangeReferenceCount(clientPrincipal, -1, 0); 1588} 1589 1590 1591OSStatus KRBCredAddReferenceAndLabel(CFStringRef clientPrincipal, 1592 CFStringRef identifier) 1593{ 1594 OSStatus ret = 0; 1595 krb5_error_code kret; 1596 krb5_context kcontext = NULL; 1597 krb5_ccache id = NULL; 1598 krb5_data data; 1599 char *label = NULL; 1600 1601 label = NAHCreateRefLabelFromIdentifier(identifier); 1602 if (label == NULL) { 1603 ret = memFullErr; 1604 goto out; 1605 } 1606 1607 KHLog ("%s", "[[[ KRBCredAddReferenceAndLabel"); 1608 1609 kret = k5_ok(krb5_init_context(&kcontext)); 1610 if (0 != kret) { 1611 ret = memFullErr; 1612 goto out; 1613 } 1614 1615 ret = findCred(clientPrincipal, kcontext, &id); 1616 if (ret != noErr) 1617 goto out; 1618 1619 /* Skip SSO cred-caches */ 1620 kret = k5_ok(krb5_cc_get_config(kcontext, id, NULL, "nah-created", &data)); 1621 if (kret) { 1622 ret = 0; 1623 goto out; 1624 } 1625 krb5_data_free(&data); 1626 1627 data.data = (void *)"1"; 1628 data.length = 1; 1629 1630 kret = krb5_cc_set_config(kcontext, id, NULL, label, &data); 1631 if (kret) { 1632 ret = memFullErr; 1633 goto out; 1634 } 1635 1636 ret = krb5_cc_hold(kcontext, id); 1637 if (ret) 1638 goto out; 1639 1640 out: 1641 KHLog ("]]] KRBCredAddReferenceAndLabel () = %d (label %s)", (int)ret, label); 1642 if (id) 1643 krb5_cc_close(kcontext, id); 1644 if (kcontext) 1645 krb5_free_context(kcontext); 1646 if (label) 1647 free(label); 1648 1649 return ret; 1650} 1651 1652OSStatus 1653KRBCredFindByLabelAndRelease(CFStringRef identifier) 1654{ 1655 NAHFindByLabelAndRelease(identifier); 1656 return noErr; 1657} 1658 1659/* 1660 * Parses the initial request from a SMB server and constructs a 1661 * resulting CFDictionaryRef. 1662 * 1663 * 1664 */ 1665 1666CFDictionaryRef 1667KRBDecodeNegTokenInit(CFAllocatorRef alloc, CFDataRef data) 1668{ 1669 CFMutableDictionaryRef dict = NULL, mechs = NULL; 1670 union { 1671 NegotiationToken rfc2478; 1672 NegotiationTokenWin win; 1673 } u; 1674 int win = 0; 1675 MechTypeList *mechtypes; 1676 char *hintsname = NULL; 1677 gss_buffer_desc input_buffer, output_buffer = { 0, NULL }; 1678 OM_uint32 junk; 1679 int ret; 1680 1681 input_buffer.value = (void *)CFDataGetBytePtr(data); 1682 input_buffer.length = CFDataGetLength(data); 1683 1684 junk = gss_decapsulate_token(&input_buffer, GSS_SPNEGO_MECHANISM, &output_buffer); 1685 if (junk) 1686 goto out; 1687 1688 memset(&u, 0, sizeof(u)); 1689 ret = decode_NegotiationToken(output_buffer.value, output_buffer.length, &u.rfc2478, NULL); 1690 if (ret == 0) { 1691 if (u.rfc2478.element != choice_NegotiationToken_negTokenInit) 1692 goto out; 1693 1694 mechtypes = &u.rfc2478.u.negTokenInit.mechTypes; 1695 } else { 1696 win = 1; 1697 1698 memset(&u, 0, sizeof(u)); 1699 1700 ret = decode_NegotiationTokenWin(output_buffer.value, output_buffer.length, &u.win, NULL); 1701 if (ret) 1702 goto out; 1703 1704 if (u.win.element != choice_NegotiationTokenWin_negTokenInit) 1705 goto out; 1706 1707 mechtypes = &u.win.u.negTokenInit.mechTypes; 1708 1709 if (u.win.u.negTokenInit.negHints && u.win.u.negTokenInit.negHints->hintName) 1710 hintsname = *(u.win.u.negTokenInit.negHints->hintName); 1711 } 1712 1713 dict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1714 if (dict == NULL) 1715 goto out; 1716 1717 if (mechtypes) { 1718 CFDataRef empty; 1719 unsigned n; 1720 1721 mechs = CFDictionaryCreateMutable(alloc, mechtypes->len, 1722 &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1723 if (mechs == NULL) 1724 goto out; 1725 1726 CFDictionaryAddValue(dict, kSPNEGONegTokenInitMechs, mechs); 1727 CFRelease(mechs); /* dict have a ref */ 1728 1729 empty = CFDataCreateWithBytesNoCopy(alloc, NULL, 0, kCFAllocatorNull); 1730 if (empty == NULL) 1731 goto out; 1732 1733 for (n = 0; n < mechtypes->len; n++) { 1734 char *str; 1735 ret = der_print_heim_oid(&mechtypes->val[n], '.', &str); 1736 if (ret) 1737 continue; 1738 1739 CFStringRef s = CFStringCreateWithCString(alloc, str, kCFStringEncodingUTF8); 1740 free(str); 1741 if (s) { 1742 CFDictionaryAddValue(mechs, s, empty); 1743 CFRelease(s); 1744 } else { 1745 CFRelease(empty); 1746 CFRelease(dict); 1747 dict = NULL; 1748 goto out; 1749 } 1750 } 1751 CFRelease(empty); 1752 } 1753 if (hintsname) { 1754 CFStringRef s = CFStringCreateWithCString(alloc, hintsname, kCFStringEncodingUTF8); 1755 if (s) { 1756 CFDictionaryAddValue(dict, kSPNEGONegTokenInitHintsHostname, s); 1757 CFRelease(s); 1758 } else { 1759 CFRelease(dict); 1760 dict = NULL; 1761 goto out; 1762 } 1763 } 1764 1765 if (mechs) { 1766 if (CFDictionaryGetValue(mechs, kGSSAPIMechSupportsAppleLKDC)) 1767 CFDictionaryAddValue(dict, KSPNEGOSupportsLKDC, CFSTR("yes")); 1768 } 1769 1770 out: 1771 gss_release_buffer(&junk, &output_buffer); 1772 if (win) 1773 free_NegotiationTokenWin(&u.win); 1774 else 1775 free_NegotiationToken(&u.rfc2478); 1776 1777 return dict; 1778} 1779 1780static CFDictionaryRef 1781CreateNegTokenLegacyMech(CFAllocatorRef alloc, 1782 CFStringRef mech, 1783 CFDataRef value, 1784 bool support_wlkdc) 1785{ 1786 CFMutableDictionaryRef dict = NULL; 1787 CFMutableDictionaryRef mechs; 1788 1789 dict = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1790 if (dict == NULL) 1791 return NULL; 1792 1793 mechs = CFDictionaryCreateMutable(alloc, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 1794 if (mechs == NULL) { 1795 CFRelease(dict); 1796 return NULL; 1797 } 1798 1799 CFDictionaryAddValue(dict, kSPNEGONegTokenInitMechs, mechs); 1800 CFRelease(mechs); 1801 1802 CFDictionaryAddValue(mechs, mech, value); 1803 if (support_wlkdc) 1804 CFDictionaryAddValue(mechs, kGSSAPIMechSupportsAppleLKDC, CFSTR("yes")); 1805 1806 return dict; 1807} 1808 1809 1810/* 1811 * Create a NegToken reference that only contains a Kerberos mech 1812 */ 1813 1814CFDictionaryRef 1815KRBCreateNegTokenLegacyKerberos(CFAllocatorRef alloc) 1816{ 1817 CFDictionaryRef dict; 1818 CFDataRef empty; 1819 1820 empty = CFDataCreateWithBytesNoCopy(alloc, (void *)"", 0, kCFAllocatorNull); 1821 if (empty == NULL) 1822 return NULL; 1823 1824 dict = CreateNegTokenLegacyMech(alloc, kGSSAPIMechKerberosOID, empty, false); 1825 CFRelease(empty); 1826 return dict; 1827} 1828 1829/* 1830 * Create a NegToken reference that only contains a NTLM mech, also 1831 * hint that this is a raw NTLM (w/o SPNEGO wrappings). 1832 */ 1833 1834CFDictionaryRef 1835KRBCreateNegTokenLegacyNTLM(CFAllocatorRef alloc) 1836{ 1837 CFDictionaryRef dict; 1838 CFDataRef empty; 1839 1840 empty = CFDataCreateWithBytesNoCopy(alloc, (void *)"raw", 3, kCFAllocatorNull); 1841 if (empty == NULL) 1842 return NULL; 1843 1844 dict = CreateNegTokenLegacyMech(alloc, kGSSAPIMechNTLMOID, empty, false); 1845 if (empty) 1846 CFRelease(empty); 1847 return dict; 1848} 1849 1850/* 1851 ;; Local Variables: ** 1852 ;; tab-width: 8 ** 1853 ;; c-basic-offset: 4 ** 1854 ;; End: ** 1855*/ 1856