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