DLDBListCFPref.cpp
1 /* 2 * Copyright (c) 2000-2004,2011-2014 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 25 /* 26 DLDBListCFPref.cpp 27 */ 28 29 #include "DLDBListCFPref.h" 30 #include <Security/cssmapple.h> 31 #include <security_utilities/debugging.h> 32 #include <security_utilities/utilities.h> 33 #include <memory> 34 #include <fcntl.h> 35 #include <sys/types.h> 36 #include <sys/stat.h> 37 #include <unistd.h> 38 #include <pwd.h> 39 #include <sys/param.h> 40 #include <copyfile.h> 41 #include <xpc/private.h> 42 #include <syslog.h> 43 #include <sandbox.h> 44 #include <security_keychain/StorageManager.h> 45 46 dispatch_once_t AppSandboxChecked; 47 xpc_object_t KeychainHomeFromXPC; 48 49 using namespace CssmClient; 50 51 static const double kDLDbListCFPrefRevertInterval = 30.0; 52 53 // normal debug calls, which get stubbed out for deployment builds 54 55 #define kKeyGUID CFSTR("GUID") 56 #define kKeySubserviceId CFSTR("SubserviceId") 57 #define kKeySubserviceType CFSTR("SubserviceType") 58 #define kKeyDbName CFSTR("DbName") 59 #define kKeyDbLocation CFSTR("DbLocation") 60 #define kKeyActive CFSTR("Active") 61 #define kKeyMajorVersion CFSTR("MajorVersion") 62 #define kKeyMinorVersion CFSTR("MinorVersion") 63 #define kDefaultDLDbListKey CFSTR("DLDBSearchList") 64 #define kDefaultKeychainKey CFSTR("DefaultKeychain") 65 #define kLoginKeychainKey CFSTR("LoginKeychain") 66 #define kUserDefaultPath "~/Library/Preferences/com.apple.security.plist" 67 #define kSystemDefaultPath "/Library/Preferences/com.apple.security.plist" 68 #define kCommonDefaultPath "/Library/Preferences/com.apple.security-common.plist" 69 #define kLoginKeychainPathPrefix "~/Library/Keychains/" 70 #define kUserLoginKeychainPath "~/Library/Keychains/login.keychain" 71 #define kSystemLoginKeychainPath "/Library/Keychains/System.keychain" 72 73 74 // A utility class for managing password database lookups 75 76 const time_t kPasswordCacheExpire = 30; // number of seconds cached password db info is valid 77 78 PasswordDBLookup::PasswordDBLookup () : mValid (false), mCurrent (0), mTime (0) 79 { 80 } 81 82 void PasswordDBLookup::lookupInfoOnUID (uid_t uid) 83 { 84 time_t currentTime = time (NULL); 85 86 if (!mValid || uid != mCurrent || currentTime - mTime >= kPasswordCacheExpire) 87 { 88 struct passwd* pw = getpwuid(uid); 89 if (pw == NULL) 90 { 91 UnixError::throwMe (EPERM); 92 } 93 94 mDirectory = pw->pw_dir; 95 mName = pw->pw_name; 96 mValid = true; 97 mCurrent = uid; 98 mTime = currentTime; 99 100 secinfo("secpref", "uid=%d caching home=%s", uid, pw->pw_dir); 101 102 endpwent(); 103 } 104 } 105 106 PasswordDBLookup *DLDbListCFPref::mPdbLookup = NULL; 107 108 //------------------------------------------------------------------------------------- 109 // 110 // Lists of DL/DBs, with CFPreferences backing store 111 // 112 //------------------------------------------------------------------------------------- 113 114 DLDbListCFPref::DLDbListCFPref(SecPreferencesDomain domain) : mDomain(domain), mPropertyList(NULL), mChanged(false), 115 mSearchListSet(false), mDefaultDLDbIdentifierSet(false), mLoginDLDbIdentifierSet(false) 116 { 117 secinfo("secpref", "New DLDbListCFPref %p for domain %d", this, domain); 118 loadPropertyList(true); 119 } 120 121 void DLDbListCFPref::set(SecPreferencesDomain domain) 122 { 123 save(); 124 125 mDomain = domain; 126 127 secinfo("secpref", "DLDbListCFPref %p domain set to %d", this, domain); 128 129 if (loadPropertyList(true)) 130 resetCachedValues(); 131 } 132 133 DLDbListCFPref::~DLDbListCFPref() 134 { 135 save(); 136 137 if (mPropertyList) 138 CFRelease(mPropertyList); 139 } 140 141 void 142 DLDbListCFPref::forceUserSearchListReread() 143 { 144 // set mPrefsTimeStamp so that it will "expire" the next time loadPropertyList is called 145 mPrefsTimeStamp = CFAbsoluteTimeGetCurrent() - kDLDbListCFPrefRevertInterval; 146 } 147 148 bool 149 DLDbListCFPref::loadPropertyList(bool force) 150 { 151 string prefsPath; 152 153 switch (mDomain) 154 { 155 case kSecPreferencesDomainUser: 156 prefsPath = ExpandTildesInPath(kUserDefaultPath); 157 break; 158 case kSecPreferencesDomainSystem: 159 prefsPath = kSystemDefaultPath; 160 break; 161 case kSecPreferencesDomainCommon: 162 prefsPath = kCommonDefaultPath; 163 break; 164 default: 165 MacOSError::throwMe(errSecInvalidPrefsDomain); 166 } 167 168 secinfo("secpref", "force=%s prefsPath=%s", force ? "true" : "false", 169 prefsPath.c_str()); 170 171 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); 172 173 // If for some reason the prefs file path has changed, blow away the old plist and force an update 174 if (mPrefsPath != prefsPath) 175 { 176 mPrefsPath = prefsPath; 177 if (mPropertyList) 178 { 179 CFRelease(mPropertyList); 180 mPropertyList = NULL; 181 } 182 183 mPrefsTimeStamp = now; 184 } 185 else if (!force) 186 { 187 if (now - mPrefsTimeStamp < kDLDbListCFPrefRevertInterval) 188 return false; 189 190 mPrefsTimeStamp = now; 191 } 192 193 struct stat st; 194 if (stat(mPrefsPath.c_str(), &st)) 195 { 196 if (errno == ENOENT) 197 { 198 if (mPropertyList) 199 { 200 if (CFDictionaryGetCount(mPropertyList) == 0) 201 return false; 202 CFRelease(mPropertyList); 203 } 204 205 mPropertyList = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 206 return true; 207 } 208 } 209 else 210 { 211 if (mPropertyList) 212 { 213 if (mTimespec.tv_sec == st.st_mtimespec.tv_sec 214 && mTimespec.tv_nsec == st.st_mtimespec.tv_nsec) 215 return false; 216 } 217 218 mTimespec = st.st_mtimespec; 219 } 220 221 CFMutableDictionaryRef thePropertyList = NULL; 222 CFMutableDataRef xmlData = NULL; 223 CFStringRef errorString = NULL; 224 int fd = -1; 225 226 do 227 { 228 fd = open(mPrefsPath.c_str(), O_RDONLY, 0); 229 if (fd < 0) 230 break; 231 232 off_t theSize = lseek(fd, 0, SEEK_END); 233 if (theSize <= 0) 234 break; 235 236 if (lseek(fd, 0, SEEK_SET)) 237 break; 238 239 xmlData = CFDataCreateMutable(NULL, CFIndex(theSize)); 240 if (!xmlData) 241 break; 242 CFDataSetLength(xmlData, CFIndex(theSize)); 243 void *buffer = reinterpret_cast<void *>(CFDataGetMutableBytePtr(xmlData)); 244 if (!buffer) 245 break; 246 ssize_t bytesRead = read(fd, buffer, (size_t)theSize); 247 if (bytesRead != theSize) 248 break; 249 250 thePropertyList = CFMutableDictionaryRef(CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListMutableContainers, &errorString)); 251 if (!thePropertyList) 252 break; 253 254 if (CFGetTypeID(thePropertyList) != CFDictionaryGetTypeID()) 255 { 256 CFRelease(thePropertyList); 257 thePropertyList = NULL; 258 break; 259 } 260 } while (0); 261 262 if (fd >= 0) 263 close(fd); 264 if (xmlData) 265 CFRelease(xmlData); 266 if (errorString) 267 CFRelease(errorString); 268 269 if (!thePropertyList) 270 { 271 thePropertyList = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); 272 } 273 274 if (mPropertyList) 275 { 276 if (CFEqual(mPropertyList, thePropertyList)) 277 { 278 // The new property list is the same as the old one, so nothing has changed. 279 CFRelease(thePropertyList); 280 return false; 281 } 282 CFRelease(mPropertyList); 283 } 284 285 mPropertyList = thePropertyList; 286 return true; 287 } 288 289 void 290 DLDbListCFPref::writePropertyList() 291 { 292 if (!mPropertyList || CFDictionaryGetCount(mPropertyList) == 0) 293 { 294 // There is nothing in the mPropertyList dictionary, 295 // so we don't need a prefs file. 296 unlink(mPrefsPath.c_str()); 297 } 298 else 299 { 300 if(testAndFixPropertyList()) 301 return; 302 303 CFDataRef xmlData = CFPropertyListCreateXMLData(NULL, mPropertyList); 304 if (!xmlData) 305 return; // Bad out of memory or something evil happened let's act like CF and do nothing. 306 307 // The prefs file should at least be made readable by user/group/other and writable by the owner. 308 // Change from euid to ruid if needed for the duration of the new prefs file creat. 309 310 mode_t mode = 0644; 311 changeIdentity(UNPRIV); 312 int fd = open(mPrefsPath.c_str(), O_WRONLY|O_CREAT|O_TRUNC, mode); 313 changeIdentity(PRIV); 314 if (fd >= 0) 315 { 316 const void *buffer = CFDataGetBytePtr(xmlData); 317 size_t toWrite = CFDataGetLength(xmlData); 318 /* ssize_t bytesWritten = */ write(fd, buffer, toWrite); 319 // Emulate CFPreferences by not checking for any errors. 320 321 fsync(fd); 322 struct stat st; 323 if (!fstat(fd, &st)) 324 mTimespec = st.st_mtimespec; 325 326 close(fd); 327 } 328 329 CFRelease(xmlData); 330 } 331 332 mPrefsTimeStamp = CFAbsoluteTimeGetCurrent(); 333 } 334 335 // This function can clean up some problems caused by setuid clients. We've had instances where the 336 // Keychain search list has become owned by root, but is still able to be re-written by the user because 337 // of the permissions on the directory above. We'll take advantage of that fact to recreate the file with 338 // the correct ownership by copying it. 339 340 int 341 DLDbListCFPref::testAndFixPropertyList() 342 { 343 char *prefsPath = (char *)mPrefsPath.c_str(); 344 345 int fd1, retval; 346 struct stat stbuf; 347 348 if((fd1 = open(prefsPath, O_RDONLY)) < 0) { 349 if (errno == ENOENT) return 0; // Doesn't exist - the default case 350 else return -1; 351 } 352 353 if((retval = fstat(fd1, &stbuf)) == -1) return -1; 354 355 if(stbuf.st_uid != getuid()) { 356 char tempfile[MAXPATHLEN+1]; 357 358 changeIdentity(UNPRIV); 359 360 snprintf(tempfile, MAXPATHLEN, "%s.XXXXXX", prefsPath); 361 int fd2 = mkstemp(tempfile); 362 if (fd2 < 0 || ::fchmod(fd2, 0644) != 0) { 363 ::unlink(tempfile); 364 retval = -1; 365 } else { 366 copyfile_state_t s = copyfile_state_alloc(); 367 retval = fcopyfile(fd1, fd2, s, COPYFILE_DATA); 368 copyfile_state_free(s); 369 if (retval) { 370 ::unlink(tempfile); 371 } else { 372 retval = ::unlink(prefsPath); 373 if(!retval) retval = ::rename(tempfile, prefsPath); 374 } 375 } 376 changeIdentity(PRIV); 377 if (fd2 >= 0) 378 close(fd2); 379 } 380 close(fd1); 381 return retval; 382 } 383 384 // Encapsulated process uid/gid change routine. 385 void 386 DLDbListCFPref::changeIdentity(ID_Direction toPriv) 387 { 388 if(toPriv == UNPRIV) { 389 savedEUID = geteuid(); 390 savedEGID = getegid(); 391 if(savedEGID != getgid()) setegid(getgid()); 392 if(savedEUID != getuid()) seteuid(getuid()); 393 } else { 394 if(savedEUID != getuid()) seteuid(savedEUID); 395 if(savedEGID != getgid()) setegid(savedEGID); 396 } 397 } 398 399 void 400 DLDbListCFPref::resetCachedValues() 401 { 402 // Unset the login and default Keychain. 403 mLoginDLDbIdentifier = mDefaultDLDbIdentifier = DLDbIdentifier(); 404 405 // Clear the searchList. 406 mSearchList.clear(); 407 408 changed(false); 409 410 // Note that none of our cached values are valid 411 mSearchListSet = mDefaultDLDbIdentifierSet = mLoginDLDbIdentifierSet = false; 412 413 mPrefsTimeStamp = CFAbsoluteTimeGetCurrent(); 414 } 415 416 void DLDbListCFPref::save() 417 { 418 if (!hasChanged()) { 419 return; 420 } 421 422 // Resync from disc to make sure we don't clobber anyone elses changes. 423 // @@@ This is probably already done by the next layer up so we don't 424 // really need to do it here again. 425 loadPropertyList(true); 426 427 // Do the searchList first since it might end up invoking defaultDLDbIdentifier() which can set 428 // mLoginDLDbIdentifierSet and mDefaultDLDbIdentifierSet to true. 429 if (mSearchListSet) 430 { 431 // Make a temporary CFArray with the contents of the vector 432 if (mSearchList.size() == 1 && mSearchList[0] == defaultDLDbIdentifier() && mSearchList[0] == LoginDLDbIdentifier()) 433 { 434 // The only element in the search list is the default keychain, which is a 435 // post Jaguar style login keychain, so omit the entry from the prefs file. 436 CFDictionaryRemoveValue(mPropertyList, kDefaultDLDbListKey); 437 } 438 else 439 { 440 CFMutableArrayRef searchArray = CFArrayCreateMutable(kCFAllocatorDefault, mSearchList.size(), &kCFTypeArrayCallBacks); 441 for (DLDbList::const_iterator ix=mSearchList.begin();ix!=mSearchList.end();ix++) 442 { 443 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(*ix); 444 CFArrayAppendValue(searchArray, aDict); 445 CFRelease(aDict); 446 } 447 448 CFDictionarySetValue(mPropertyList, kDefaultDLDbListKey, searchArray); 449 CFRelease(searchArray); 450 } 451 } 452 453 if (mLoginDLDbIdentifierSet) 454 { 455 // Make a temporary CFArray with the login keychain 456 CFArrayRef loginArray = NULL; 457 if (!mLoginDLDbIdentifier) 458 { 459 loginArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks); 460 } 461 else if (!(mLoginDLDbIdentifier == LoginDLDbIdentifier())) 462 { 463 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mLoginDLDbIdentifier); 464 const void *value = reinterpret_cast<const void *>(aDict); 465 loginArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks); 466 CFRelease(aDict); 467 } 468 469 if (loginArray) 470 { 471 CFDictionarySetValue(mPropertyList, kLoginKeychainKey, loginArray); 472 CFRelease(loginArray); 473 } 474 else 475 CFDictionaryRemoveValue(mPropertyList, kLoginKeychainKey); 476 } 477 478 if (mDefaultDLDbIdentifierSet) 479 { 480 // Make a temporary CFArray with the default keychain 481 CFArrayRef defaultArray = NULL; 482 if (!mDefaultDLDbIdentifier) 483 { 484 defaultArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks); 485 } 486 else if (!(mDefaultDLDbIdentifier == LoginDLDbIdentifier())) 487 { 488 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mDefaultDLDbIdentifier); 489 const void *value = reinterpret_cast<const void *>(aDict); 490 defaultArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks); 491 CFRelease(aDict); 492 } 493 494 if (defaultArray) 495 { 496 CFDictionarySetValue(mPropertyList, kDefaultKeychainKey, defaultArray); 497 CFRelease(defaultArray); 498 } 499 else 500 CFDictionaryRemoveValue(mPropertyList, kDefaultKeychainKey); 501 } 502 503 writePropertyList(); 504 changed(false); 505 } 506 507 508 //---------------------------------------------------------------------- 509 // Conversions 510 //---------------------------------------------------------------------- 511 512 DLDbIdentifier DLDbListCFPref::LoginDLDbIdentifier() 513 { 514 CSSM_VERSION theVersion={}; 515 CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP); 516 CssmNetAddress *dbLocation=NULL; 517 518 switch (mDomain) { 519 case kSecPreferencesDomainUser: 520 return DLDbIdentifier(ssuid, ExpandTildesInPath(kUserLoginKeychainPath).c_str(), dbLocation); 521 default: 522 assert(false); 523 case kSecPreferencesDomainSystem: 524 case kSecPreferencesDomainCommon: 525 return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation); 526 } 527 } 528 529 DLDbIdentifier DLDbListCFPref::JaguarLoginDLDbIdentifier() 530 { 531 CSSM_VERSION theVersion={}; 532 CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP); 533 CssmNetAddress *dbLocation=NULL; 534 535 switch (mDomain) { 536 case kSecPreferencesDomainUser: 537 { 538 string basepath = ExpandTildesInPath(kLoginKeychainPathPrefix) + getPwInfo(kUsername); 539 return DLDbIdentifier(ssuid,basepath.c_str(),dbLocation); 540 } 541 case kSecPreferencesDomainSystem: 542 case kSecPreferencesDomainCommon: 543 return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation); 544 default: 545 assert(false); 546 return DLDbIdentifier(); 547 } 548 } 549 550 DLDbIdentifier DLDbListCFPref::makeDLDbIdentifier (const CSSM_GUID &guid, const CSSM_VERSION &version, 551 uint32 subserviceId, CSSM_SERVICE_TYPE subserviceType, 552 const char* dbName, CSSM_NET_ADDRESS *dbLocation) 553 { 554 CssmSubserviceUid ssuid (guid, &version, subserviceId, subserviceType); 555 return DLDbIdentifier (ssuid, ExpandTildesInPath (dbName).c_str (), dbLocation); 556 } 557 558 DLDbIdentifier DLDbListCFPref::cfDictionaryRefToDLDbIdentifier(CFDictionaryRef theDict) 559 { 560 // We must get individual values from the dictionary and store in basic types 561 if (CFGetTypeID(theDict) != CFDictionaryGetTypeID()) 562 throw std::logic_error("wrong type in property list"); 563 564 // GUID 565 CCFValue vGuid(::CFDictionaryGetValue(theDict,kKeyGUID)); 566 string guidStr=vGuid; 567 const Guid guid(guidStr.c_str()); 568 569 //CSSM_VERSION 570 CSSM_VERSION theVersion={0,}; 571 CCFValue vMajor(::CFDictionaryGetValue(theDict,kKeyMajorVersion)); 572 theVersion.Major = vMajor; 573 CCFValue vMinor(::CFDictionaryGetValue(theDict,kKeyMinorVersion)); 574 theVersion.Minor = vMinor; 575 576 //subserviceId 577 CCFValue vSsid(::CFDictionaryGetValue(theDict,kKeySubserviceId)); 578 uint32 subserviceId=sint32(vSsid); 579 580 //CSSM_SERVICE_TYPE 581 CSSM_SERVICE_TYPE subserviceType=CSSM_SERVICE_DL; 582 CCFValue vSsType(::CFDictionaryGetValue(theDict,kKeySubserviceType)); 583 subserviceType=vSsType; 584 585 // Get DbName from dictionary 586 CCFValue vDbName(::CFDictionaryGetValue(theDict,kKeyDbName)); 587 string dbName=vDbName; 588 589 // jch Get DbLocation from dictionary 590 CssmNetAddress *dbLocation=NULL; 591 592 return makeDLDbIdentifier (guid, theVersion, subserviceId, subserviceType, dbName.c_str (), dbLocation); 593 } 594 595 void DLDbListCFPref::clearPWInfo () 596 { 597 if (mPdbLookup != NULL) 598 { 599 delete mPdbLookup; 600 mPdbLookup = NULL; 601 } 602 } 603 604 string DLDbListCFPref::getPwInfo(PwInfoType type) 605 { 606 const char *value; 607 switch (type) 608 { 609 case kHomeDir: 610 if (KeychainHomeFromXPC) { 611 value = xpc_string_get_string_ptr(KeychainHomeFromXPC); 612 } else { 613 value = getenv("HOME"); 614 } 615 if (value) 616 return value; 617 break; 618 case kUsername: 619 value = getenv("USER"); 620 if (value) 621 return value; 622 break; 623 } 624 625 // Get our effective uid 626 uid_t uid = geteuid(); 627 // If we are setuid root use the real uid instead 628 if (!uid) uid = getuid(); 629 630 // get the password entries 631 if (mPdbLookup == NULL) 632 { 633 mPdbLookup = new PasswordDBLookup (); 634 } 635 636 mPdbLookup->lookupInfoOnUID (uid); 637 638 string result; 639 switch (type) 640 { 641 case kHomeDir: 642 result = mPdbLookup->getDirectory (); 643 break; 644 case kUsername: 645 result = mPdbLookup->getName (); 646 break; 647 } 648 649 return result; 650 } 651 652 static void check_app_sandbox() 653 { 654 if (!_xpc_runtime_is_app_sandboxed()) { 655 // We are not in a sandbox, no work to do here 656 return; 657 } 658 659 extern xpc_object_t xpc_create_with_format(const char * format, ...); 660 xpc_connection_t con = xpc_connection_create("com.apple.security.XPCKeychainSandboxCheck", NULL); 661 xpc_connection_set_event_handler(con, ^(xpc_object_t event) { 662 xpc_type_t xtype = xpc_get_type(event); 663 if (XPC_TYPE_ERROR == xtype) { 664 syslog(LOG_ERR, "Keychain sandbox connection error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); 665 } else { 666 syslog(LOG_ERR, "Keychain sandbox unexpected connection event %p\n", event); 667 } 668 }); 669 xpc_connection_resume(con); 670 671 xpc_object_t message = xpc_create_with_format("{op: GrantKeychainPaths}"); 672 xpc_object_t reply = xpc_connection_send_message_with_reply_sync(con, message); 673 xpc_type_t xtype = xpc_get_type(reply); 674 if (XPC_TYPE_DICTIONARY == xtype) { 675 #if 0 676 // This is useful for debugging. 677 char *debug = xpc_copy_description(reply); 678 syslog(LOG_ERR, "DEBUG (KCsandbox) %s\n", debug); 679 free(debug); 680 #endif 681 682 xpc_object_t extensions_array = xpc_dictionary_get_value(reply, "extensions"); 683 xpc_array_apply(extensions_array, ^(size_t index, xpc_object_t extension) { 684 char pbuf[MAXPATHLEN]; 685 char *path = pbuf; 686 int status = sandbox_consume_fs_extension(xpc_string_get_string_ptr(extension), &path); 687 if (status) { 688 syslog(LOG_ERR, "Keychain sandbox consume extension error: s=%d p=%s %m\n", status, path); 689 } 690 status = sandbox_release_fs_extension(xpc_string_get_string_ptr(extension)); 691 if (status) { 692 syslog(LOG_ERR, "Keychain sandbox release extension error: s=%d p=%s %m\n", status, path); 693 } 694 695 return (bool)true; 696 }); 697 698 KeychainHomeFromXPC = xpc_dictionary_get_value(reply, "keychain-home"); 699 xpc_retain(KeychainHomeFromXPC); 700 xpc_release(con); 701 } else if (XPC_TYPE_ERROR == xtype) { 702 syslog(LOG_ERR, "Keychain sandbox message error: %s\n", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION)); 703 } else { 704 syslog(LOG_ERR, "Keychain sandbox unexpected message reply type %p\n", xtype); 705 } 706 xpc_release(message); 707 xpc_release(reply); 708 } 709 710 711 712 string DLDbListCFPref::ExpandTildesInPath(const string &inPath) 713 { 714 dispatch_once(&AppSandboxChecked, ^{ 715 check_app_sandbox(); 716 }); 717 718 if ((short)inPath.find("~/",0,2) == 0) 719 return getPwInfo(kHomeDir) + inPath.substr(1, inPath.length() - 1); 720 else 721 return inPath; 722 } 723 724 string DLDbListCFPref::StripPathStuff(const string &inPath) 725 { 726 if (inPath.find("/private/var/automount/Network/",0,31) == 0) 727 return inPath.substr(22); 728 if (inPath.find("/private/automount/Servers/",0,27) == 0) 729 return "/Network" + inPath.substr(18); 730 if (inPath.find("/automount/Servers/",0,19) == 0) 731 return "/Network" + inPath.substr(10); 732 if (inPath.find("/private/automount/Network/",0,27) == 0) 733 return inPath.substr(18); 734 if (inPath.find("/automount/Network/",0,19) == 0) 735 return inPath.substr(10); 736 if (inPath.find("/private/Network/",0,17) == 0) 737 return inPath.substr(8); 738 return inPath; 739 } 740 741 string DLDbListCFPref::AbbreviatedPath(const string &inPath) 742 { 743 string path = StripPathStuff(inPath); 744 string home = StripPathStuff(getPwInfo(kHomeDir) + "/"); 745 size_t homeLen = home.length(); 746 747 if (homeLen > 1 && path.find(home.c_str(), 0, homeLen) == 0) 748 return "~" + path.substr(homeLen - 1); 749 else 750 return path; 751 } 752 753 CFDictionaryRef DLDbListCFPref::dlDbIdentifierToCFDictionaryRef(const DLDbIdentifier& dldbIdentifier) 754 { 755 CFRef<CFMutableDictionaryRef> aDict(CFDictionaryCreateMutable(kCFAllocatorDefault,0, 756 &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks)); 757 if (!aDict) 758 throw ::std::bad_alloc(); 759 760 // Put SUBSERVICE_UID in dictionary 761 char buffer[Guid::stringRepLength+1]; 762 const CssmSubserviceUid& ssuid=dldbIdentifier.ssuid(); 763 const Guid &theGuid = Guid::overlay(ssuid.Guid); 764 CFRef<CFStringRef> stringGuid(::CFStringCreateWithCString(kCFAllocatorDefault, 765 theGuid.toString(buffer),kCFStringEncodingMacRoman)); 766 if (stringGuid) 767 ::CFDictionarySetValue(aDict,kKeyGUID,stringGuid); 768 769 if (ssuid.SubserviceId!=0) 770 { 771 CFRef<CFNumberRef> subserviceId(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceId)); 772 if (subserviceId) 773 ::CFDictionarySetValue(aDict,kKeySubserviceId,subserviceId); 774 } 775 if (ssuid.SubserviceType!=0) 776 { 777 CFRef<CFNumberRef> subserviceType(CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceType)); 778 if (subserviceType) 779 ::CFDictionarySetValue(aDict,kKeySubserviceType,subserviceType); 780 } 781 if (ssuid.Version.Major!=0 && ssuid.Version.Minor!=0) 782 { 783 CFRef<CFNumberRef> majorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Major)); 784 if (majorVersion) 785 ::CFDictionarySetValue(aDict,kKeyMajorVersion,majorVersion); 786 CFRef<CFNumberRef> minorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Minor)); 787 if (minorVersion) 788 ::CFDictionarySetValue(aDict,kKeyMinorVersion,minorVersion); 789 } 790 791 // Put DbName in dictionary 792 const char *dbName=dldbIdentifier.dbName(); 793 if (dbName) 794 { 795 CFRef<CFStringRef> theDbName(::CFStringCreateWithCString(kCFAllocatorDefault,AbbreviatedPath(dbName).c_str(),kCFStringEncodingUTF8)); 796 ::CFDictionarySetValue(aDict,kKeyDbName,theDbName); 797 } 798 // Put DbLocation in dictionary 799 const CSSM_NET_ADDRESS *dbLocation=dldbIdentifier.dbLocation(); 800 if (dbLocation!=NULL && dbLocation->AddressType!=CSSM_ADDR_NONE) 801 { 802 CFRef<CFDataRef> theData(::CFDataCreate(kCFAllocatorDefault,dbLocation->Address.Data,dbLocation->Address.Length)); 803 if (theData) 804 ::CFDictionarySetValue(aDict,kKeyDbLocation,theData); 805 } 806 807 ::CFRetain(aDict); 808 return aDict; 809 } 810 811 bool DLDbListCFPref::revert(bool force) 812 { 813 // If the prefs have not been refreshed in the last kDLDbListCFPrefRevertInterval 814 // seconds or we are asked to force a reload, then reload. 815 if (!loadPropertyList(force)) 816 return false; 817 818 resetCachedValues(); 819 return true; 820 } 821 822 void 823 DLDbListCFPref::add(const DLDbIdentifier &dldbIdentifier) 824 { 825 // convert the location specified in dldbIdentifier to a standard form 826 // make a canonical form of the database name 827 std::string canon = ExpandTildesInPath(AbbreviatedPath(dldbIdentifier.dbName()).c_str()); 828 829 DLDbIdentifier localIdentifier (dldbIdentifier.ssuid(), canon.c_str(), dldbIdentifier.dbLocation ()); 830 831 if (member(localIdentifier)) 832 return; 833 834 mSearchList.push_back(localIdentifier); 835 changed(true); 836 } 837 838 void 839 DLDbListCFPref::remove(const DLDbIdentifier &dldbIdentifier) 840 { 841 // Make sure mSearchList is set 842 searchList(); 843 for (vector<DLDbIdentifier>::iterator ix = mSearchList.begin(); ix != mSearchList.end(); ++ix) 844 { 845 if (*ix==dldbIdentifier) // found in list 846 { 847 mSearchList.erase(ix); 848 changed(true); 849 break; 850 } 851 } 852 } 853 854 void 855 DLDbListCFPref::rename(const DLDbIdentifier &oldId, const DLDbIdentifier &newId) 856 { 857 // Make sure mSearchList is set 858 searchList(); 859 for (vector<DLDbIdentifier>::iterator ix = mSearchList.begin(); 860 ix != mSearchList.end(); ++ix) 861 { 862 if (*ix==oldId) 863 { 864 // replace oldId with newId 865 *ix = newId; 866 changed(true); 867 } 868 else if (*ix==newId) 869 { 870 // remove newId except where we just inserted it 871 mSearchList.erase(ix); 872 changed(true); 873 } 874 } 875 } 876 877 bool 878 DLDbListCFPref::member(const DLDbIdentifier &dldbIdentifier) 879 { 880 if (dldbIdentifier.IsImplEmpty()) 881 { 882 return false; 883 } 884 885 for (vector<DLDbIdentifier>::const_iterator ix = searchList().begin(); ix != mSearchList.end(); ++ix) 886 { 887 if (ix->mImpl == NULL) 888 { 889 continue; 890 } 891 892 // compare the dldbIdentifiers based on the full, real path to the keychain 893 if (ix->ssuid() == dldbIdentifier.ssuid()) 894 { 895 char localPath[PATH_MAX], 896 inPath[PATH_MAX]; 897 898 // try to resolve these down to a canonical form 899 const char* localPathPtr = cached_realpath(ix->dbName(), localPath); 900 const char* inPathPtr = cached_realpath(dldbIdentifier.dbName(), inPath); 901 902 // if either of the paths didn't resolve for some reason, use the originals 903 if (localPathPtr == NULL) 904 { 905 localPathPtr = ix->dbName(); 906 } 907 908 if (inPathPtr == NULL) 909 { 910 inPathPtr = dldbIdentifier.dbName(); 911 } 912 913 if (strcmp(localPathPtr, inPathPtr) == 0) 914 { 915 return true; 916 } 917 } 918 } 919 920 return false; 921 } 922 923 const vector<DLDbIdentifier> & 924 DLDbListCFPref::searchList() 925 { 926 if (!mSearchListSet) 927 { 928 CFArrayRef searchList = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kDefaultDLDbListKey)); 929 if (searchList && CFGetTypeID(searchList) != CFArrayGetTypeID()) 930 searchList = NULL; 931 932 if (searchList) 933 { 934 CFIndex top = CFArrayGetCount(searchList); 935 // Each entry is a CFDictionary; peel it off & add it to the array 936 for (CFIndex idx = 0; idx < top; ++idx) 937 { 938 CFDictionaryRef theDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(searchList, idx)); 939 try 940 { 941 mSearchList.push_back(cfDictionaryRefToDLDbIdentifier(theDict)); 942 } 943 catch (...) 944 { 945 // Drop stuff that doesn't parse on the floor. 946 } 947 } 948 949 // If there were entries specified, but they were invalid revert to using the 950 // default keychain in the searchlist. 951 if (top > 0 && mSearchList.size() == 0) 952 searchList = NULL; 953 } 954 955 // The default when no search list is specified is to only search the 956 // default keychain. 957 if (!searchList && static_cast<bool>(defaultDLDbIdentifier())) 958 mSearchList.push_back(mDefaultDLDbIdentifier); 959 960 mSearchListSet = true; 961 } 962 963 return mSearchList; 964 } 965 966 void 967 DLDbListCFPref::searchList(const vector<DLDbIdentifier> &searchList) 968 { 969 if(searchList.size() == 0) { 970 mSearchList.clear(); 971 mSearchListSet = false; 972 changed(true); 973 return; 974 } 975 976 vector<DLDbIdentifier> newList(searchList); 977 mSearchList.swap(newList); 978 mSearchListSet = true; 979 changed(true); 980 } 981 982 void 983 DLDbListCFPref::defaultDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier) 984 { 985 if (!(defaultDLDbIdentifier() == dlDbIdentifier)) 986 { 987 mDefaultDLDbIdentifier = dlDbIdentifier; 988 changed(true); 989 } 990 } 991 992 // Caution: if the backing file for the defaultDLDbIdentifier doesn't exist (or if the plist file is corrupt), 993 // this will return a DLDbIdentifier with a NULL impl 994 const DLDbIdentifier & 995 DLDbListCFPref::defaultDLDbIdentifier() 996 { 997 998 if (!mDefaultDLDbIdentifierSet) 999 { 1000 CFArrayRef defaultArray = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kDefaultKeychainKey)); 1001 if (defaultArray && CFGetTypeID(defaultArray) != CFArrayGetTypeID()) 1002 defaultArray = NULL; 1003 1004 if (defaultArray && CFArrayGetCount(defaultArray) > 0) 1005 { 1006 CFDictionaryRef defaultDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(defaultArray, 0)); 1007 try 1008 { 1009 secinfo("secpref", "getting default DLDbIdentifier from defaultDict"); 1010 mDefaultDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(defaultDict); 1011 secinfo("secpref", "now we think the default keychain is %s", (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "<NULL>"); 1012 } 1013 catch (...) 1014 { 1015 // If defaultArray doesn't parse fall back on the default way of getting the default keychain 1016 defaultArray = NULL; 1017 } 1018 } 1019 1020 if (!defaultArray) 1021 { 1022 // If the Panther style login keychain actually exists we use that otherwise no 1023 // default is set. 1024 mDefaultDLDbIdentifier = loginDLDbIdentifier(); 1025 1026 //Since we might be changing the keychain filename, we have to stat the right file. Delegate the knowledge of which files to StorageManager; DLDbListCFPref should contain "login.keychain". 1027 DLDbIdentifier actualIdentifier = KeychainCore::StorageManager::mungeDLDbIdentifier(mDefaultDLDbIdentifier, false); 1028 secinfo("secpref", "now we think the default keychain is: %s (actual: %s)", 1029 (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "Name doesn't exist", 1030 (actualIdentifier) ? actualIdentifier.dbName() : "Name doesn't exist"); 1031 1032 struct stat st; 1033 int st_result = -1; 1034 1035 if (mDefaultDLDbIdentifier.mImpl != NULL && actualIdentifier.mImpl != NULL) 1036 { 1037 st_result = stat(actualIdentifier.dbName(), &st); 1038 1039 // Always claim that the system keychain exists for purposes of the search list 1040 if (st_result && 0 == strncmp(actualIdentifier.dbName(), kSystemKeychainPath, strlen(kSystemKeychainPath))) { 1041 secnotice("secpref", "System keychain (%s) does not exist. Continuing as if it does...", actualIdentifier.dbName()); 1042 st_result = 0; 1043 } 1044 } 1045 1046 if (st_result) 1047 { 1048 secinfo("secpref", "stat(%s) -> %d", actualIdentifier.dbName(), st_result); 1049 mDefaultDLDbIdentifier = DLDbIdentifier(); // initialize a NULL keychain 1050 secinfo("secpref", "after DLDbIdentifier(), we think the default keychain is %s", static_cast<bool>(mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "<NULL>"); 1051 } 1052 } 1053 1054 mDefaultDLDbIdentifierSet = true; 1055 } 1056 1057 1058 return mDefaultDLDbIdentifier; 1059 } 1060 1061 void 1062 DLDbListCFPref::loginDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier) 1063 { 1064 if (!(loginDLDbIdentifier() == dlDbIdentifier)) 1065 { 1066 mLoginDLDbIdentifier = dlDbIdentifier; 1067 changed(true); 1068 } 1069 } 1070 1071 const DLDbIdentifier & 1072 DLDbListCFPref::loginDLDbIdentifier() 1073 { 1074 if (!mLoginDLDbIdentifierSet) 1075 { 1076 CFArrayRef loginArray = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kLoginKeychainKey)); 1077 if (loginArray && CFGetTypeID(loginArray) != CFArrayGetTypeID()) 1078 loginArray = NULL; 1079 1080 if (loginArray && CFArrayGetCount(loginArray) > 0) 1081 { 1082 CFDictionaryRef loginDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(loginArray, 0)); 1083 try 1084 { 1085 secinfo("secpref", "Getting login DLDbIdentifier from loginDict"); 1086 mLoginDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(loginDict); 1087 secinfo("secpref", "we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>"); 1088 } 1089 catch (...) 1090 { 1091 // If loginArray doesn't parse fall back on the default way of getting the login keychain. 1092 loginArray = NULL; 1093 } 1094 } 1095 1096 if (!loginArray) 1097 { 1098 mLoginDLDbIdentifier = LoginDLDbIdentifier(); 1099 secinfo("secpref", "after LoginDLDbIdentifier(), we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>"); 1100 } 1101 1102 mLoginDLDbIdentifierSet = true; 1103 } 1104 1105 return mLoginDLDbIdentifier; 1106 }