/ OSX / libsecurity_keychain / lib / DLDBListCFPref.cpp
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  }