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