X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/Security/libsecurity_keychain/lib/DLDBListCFPref.cpp?ds=sidebyside diff --git a/Security/libsecurity_keychain/lib/DLDBListCFPref.cpp b/Security/libsecurity_keychain/lib/DLDBListCFPref.cpp new file mode 100644 index 00000000..4836f4dc --- /dev/null +++ b/Security/libsecurity_keychain/lib/DLDBListCFPref.cpp @@ -0,0 +1,1079 @@ +/* + * Copyright (c) 2000-2004,2011-2014 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_LICENSE_HEADER_END@ + */ + + +/* + DLDBListCFPref.cpp +*/ + +#include "DLDBListCFPref.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +dispatch_once_t AppSandboxChecked; +xpc_object_t KeychainHomeFromXPC; + +using namespace CssmClient; + +static const double kDLDbListCFPrefRevertInterval = 30.0; + +// normal debug calls, which get stubbed out for deployment builds + +#define kKeyGUID CFSTR("GUID") +#define kKeySubserviceId CFSTR("SubserviceId") +#define kKeySubserviceType CFSTR("SubserviceType") +#define kKeyDbName CFSTR("DbName") +#define kKeyDbLocation CFSTR("DbLocation") +#define kKeyActive CFSTR("Active") +#define kKeyMajorVersion CFSTR("MajorVersion") +#define kKeyMinorVersion CFSTR("MinorVersion") +#define kDefaultDLDbListKey CFSTR("DLDBSearchList") +#define kDefaultKeychainKey CFSTR("DefaultKeychain") +#define kLoginKeychainKey CFSTR("LoginKeychain") +#define kUserDefaultPath "~/Library/Preferences/com.apple.security.plist" +#define kSystemDefaultPath "/Library/Preferences/com.apple.security.plist" +#define kCommonDefaultPath "/Library/Preferences/com.apple.security-common.plist" +#define kLoginKeychainPathPrefix "~/Library/Keychains/" +#define kUserLoginKeychainPath "~/Library/Keychains/login.keychain" +#define kSystemLoginKeychainPath "/Library/Keychains/System.keychain" + + +// A utility class for managing password database lookups + +const time_t kPasswordCacheExpire = 30; // number of seconds cached password db info is valid + +PasswordDBLookup::PasswordDBLookup () : mValid (false), mCurrent (0), mTime (0) +{ +} + +void PasswordDBLookup::lookupInfoOnUID (uid_t uid) +{ + time_t currentTime = time (NULL); + + if (!mValid || uid != mCurrent || currentTime - mTime >= kPasswordCacheExpire) + { + struct passwd* pw = getpwuid(uid); + if (pw == NULL) + { + UnixError::throwMe (EPERM); + } + + mDirectory = pw->pw_dir; + mName = pw->pw_name; + mValid = true; + mCurrent = uid; + mTime = currentTime; + + secdebug("secpref", "uid=%d caching home=%s", uid, pw->pw_dir); + + endpwent(); + } +} + +PasswordDBLookup *DLDbListCFPref::mPdbLookup = NULL; + +//------------------------------------------------------------------------------------- +// +// Lists of DL/DBs, with CFPreferences backing store +// +//------------------------------------------------------------------------------------- + +DLDbListCFPref::DLDbListCFPref(SecPreferencesDomain domain) : mDomain(domain), mPropertyList(NULL), mChanged(false), + mSearchListSet(false), mDefaultDLDbIdentifierSet(false), mLoginDLDbIdentifierSet(false) +{ + secdebug("secpref", "New DLDbListCFPref %p for domain %d", this, domain); + loadPropertyList(true); +} + +void DLDbListCFPref::set(SecPreferencesDomain domain) +{ + save(); + + mDomain = domain; + + secdebug("secpref", "DLDbListCFPref %p domain set to %d", this, domain); + + if (loadPropertyList(true)) + resetCachedValues(); +} + +DLDbListCFPref::~DLDbListCFPref() +{ + save(); + + if (mPropertyList) + CFRelease(mPropertyList); +} + +void +DLDbListCFPref::forceUserSearchListReread() +{ + // set mPrefsTimeStamp so that it will "expire" the next time loadPropertyList is called + mPrefsTimeStamp = CFAbsoluteTimeGetCurrent() - kDLDbListCFPrefRevertInterval; +} + +bool +DLDbListCFPref::loadPropertyList(bool force) +{ + string prefsPath; + + switch (mDomain) + { + case kSecPreferencesDomainUser: + prefsPath = ExpandTildesInPath(kUserDefaultPath); + break; + case kSecPreferencesDomainSystem: + prefsPath = kSystemDefaultPath; + break; + case kSecPreferencesDomainCommon: + prefsPath = kCommonDefaultPath; + break; + default: + MacOSError::throwMe(errSecInvalidPrefsDomain); + } + + secdebug("secpref", "force=%s prefsPath=%s", force ? "true" : "false", + prefsPath.c_str()); + + CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); + + // If for some reason the prefs file path has changed, blow away the old plist and force an update + if (mPrefsPath != prefsPath) + { + mPrefsPath = prefsPath; + if (mPropertyList) + { + CFRelease(mPropertyList); + mPropertyList = NULL; + } + + mPrefsTimeStamp = now; + } + else if (!force) + { + if (now - mPrefsTimeStamp < kDLDbListCFPrefRevertInterval) + return false; + + mPrefsTimeStamp = now; + } + + struct stat st; + if (stat(mPrefsPath.c_str(), &st)) + { + if (errno == ENOENT) + { + if (mPropertyList) + { + if (CFDictionaryGetCount(mPropertyList) == 0) + return false; + CFRelease(mPropertyList); + } + + mPropertyList = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + return true; + } + } + else + { + if (mPropertyList) + { + if (mTimespec.tv_sec == st.st_mtimespec.tv_sec + && mTimespec.tv_nsec == st.st_mtimespec.tv_nsec) + return false; + } + + mTimespec = st.st_mtimespec; + } + + CFMutableDictionaryRef thePropertyList = NULL; + CFMutableDataRef xmlData = NULL; + CFStringRef errorString = NULL; + int fd = -1; + + do + { + fd = open(mPrefsPath.c_str(), O_RDONLY, 0); + if (fd < 0) + break; + + off_t theSize = lseek(fd, 0, SEEK_END); + if (theSize <= 0) + break; + + if (lseek(fd, 0, SEEK_SET)) + break; + + xmlData = CFDataCreateMutable(NULL, CFIndex(theSize)); + if (!xmlData) + break; + CFDataSetLength(xmlData, CFIndex(theSize)); + void *buffer = reinterpret_cast(CFDataGetMutableBytePtr(xmlData)); + if (!buffer) + break; + ssize_t bytesRead = read(fd, buffer, (size_t)theSize); + if (bytesRead != theSize) + break; + + thePropertyList = CFMutableDictionaryRef(CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListMutableContainers, &errorString)); + if (!thePropertyList) + break; + + if (CFGetTypeID(thePropertyList) != CFDictionaryGetTypeID()) + { + CFRelease(thePropertyList); + thePropertyList = NULL; + break; + } + } while (0); + + if (fd >= 0) + close(fd); + if (xmlData) + CFRelease(xmlData); + if (errorString) + CFRelease(errorString); + + if (!thePropertyList) + { + thePropertyList = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); + } + + if (mPropertyList) + { + if (CFEqual(mPropertyList, thePropertyList)) + { + // The new property list is the same as the old one, so nothing has changed. + CFRelease(thePropertyList); + return false; + } + CFRelease(mPropertyList); + } + + mPropertyList = thePropertyList; + return true; +} + +void +DLDbListCFPref::writePropertyList() +{ + if (!mPropertyList || CFDictionaryGetCount(mPropertyList) == 0) + { + // There is nothing in the mPropertyList dictionary, + // so we don't need a prefs file. + unlink(mPrefsPath.c_str()); + } + else + { + if(testAndFixPropertyList()) + return; + + CFDataRef xmlData = CFPropertyListCreateXMLData(NULL, mPropertyList); + if (!xmlData) + return; // Bad out of memory or something evil happened let's act like CF and do nothing. + + // The prefs file should at least be made readable by user/group/other and writable by the owner. + // Change from euid to ruid if needed for the duration of the new prefs file creat. + + mode_t mode = 0666; + changeIdentity(UNPRIV); + int fd = open(mPrefsPath.c_str(), O_WRONLY|O_CREAT|O_TRUNC, mode); + changeIdentity(PRIV); + if (fd >= 0) + { + const void *buffer = CFDataGetBytePtr(xmlData); + size_t toWrite = CFDataGetLength(xmlData); + /* ssize_t bytesWritten = */ write(fd, buffer, toWrite); + // Emulate CFPreferences by not checking for any errors. + + fsync(fd); + struct stat st; + if (!fstat(fd, &st)) + mTimespec = st.st_mtimespec; + + close(fd); + } + + CFRelease(xmlData); + } + + mPrefsTimeStamp = CFAbsoluteTimeGetCurrent(); +} + +// This function can clean up some problems caused by setuid clients. We've had instances where the +// Keychain search list has become owned by root, but is still able to be re-written by the user because +// of the permissions on the directory above. We'll take advantage of that fact to recreate the file with +// the correct ownership by copying it. + +int +DLDbListCFPref::testAndFixPropertyList() +{ + char *prefsPath = (char *)mPrefsPath.c_str(); + + int fd1, fd2, retval; + struct stat stbuf; + + if((fd1 = open(prefsPath, O_RDONLY)) < 0) { + if (errno == ENOENT) return 0; // Doesn't exist - the default case + else return -1; + } + + if((retval = fstat(fd1, &stbuf)) == -1) return -1; + + if(stbuf.st_uid != getuid()) { + char tempfile[MAXPATHLEN+1]; + + snprintf(tempfile, MAXPATHLEN, "%s.XXXXX", prefsPath); + mktemp(tempfile); + changeIdentity(UNPRIV); + if((fd2 = open(tempfile, O_RDWR | O_CREAT | O_EXCL, 0666)) < 0) { + retval = -1; + } else { + copyfile_state_t s = copyfile_state_alloc(); + retval = fcopyfile(fd1, fd2, s, COPYFILE_DATA); + copyfile_state_free(s); + if(!retval) retval = ::unlink(prefsPath); + if(!retval) retval = ::rename(tempfile, prefsPath); + } + changeIdentity(PRIV); + close(fd2); + } + close(fd1); + return retval; +} + +// Encapsulated process uid/gid change routine. +void +DLDbListCFPref::changeIdentity(ID_Direction toPriv) +{ + if(toPriv == UNPRIV) { + savedEUID = geteuid(); + savedEGID = getegid(); + if(savedEGID != getgid()) setegid(getgid()); + if(savedEUID != getuid()) seteuid(getuid()); + } else { + if(savedEUID != getuid()) seteuid(savedEUID); + if(savedEGID != getgid()) setegid(savedEGID); + } +} + +void +DLDbListCFPref::resetCachedValues() +{ + // Unset the login and default Keychain. + mLoginDLDbIdentifier = mDefaultDLDbIdentifier = DLDbIdentifier(); + + // Clear the searchList. + mSearchList.clear(); + + changed(false); + + // Note that none of our cached values are valid + mSearchListSet = mDefaultDLDbIdentifierSet = mLoginDLDbIdentifierSet = false; + + mPrefsTimeStamp = CFAbsoluteTimeGetCurrent(); +} + +void DLDbListCFPref::save() +{ + if (!hasChanged()) + return; + + // Resync from disc to make sure we don't clobber anyone elses changes. + // @@@ This is probably already done by the next layer up so we don't + // really need to do it here again. + loadPropertyList(true); + + // Do the searchList first since it might end up invoking defaultDLDbIdentifier() which can set + // mLoginDLDbIdentifierSet and mDefaultDLDbIdentifierSet to true. + if (mSearchListSet) + { + // Make a temporary CFArray with the contents of the vector + if (mSearchList.size() == 1 && mSearchList[0] == defaultDLDbIdentifier() && mSearchList[0] == LoginDLDbIdentifier()) + { + // The only element in the search list is the default keychain, which is a + // post Jaguar style login keychain, so omit the entry from the prefs file. + CFDictionaryRemoveValue(mPropertyList, kDefaultDLDbListKey); + } + else + { + CFMutableArrayRef searchArray = CFArrayCreateMutable(kCFAllocatorDefault, mSearchList.size(), &kCFTypeArrayCallBacks); + for (DLDbList::const_iterator ix=mSearchList.begin();ix!=mSearchList.end();ix++) + { + CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(*ix); + CFArrayAppendValue(searchArray, aDict); + CFRelease(aDict); + } + + CFDictionarySetValue(mPropertyList, kDefaultDLDbListKey, searchArray); + CFRelease(searchArray); + } + } + + if (mLoginDLDbIdentifierSet) + { + // Make a temporary CFArray with the login keychain + CFArrayRef loginArray = NULL; + if (!mLoginDLDbIdentifier) + { + loginArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks); + } + else if (!(mLoginDLDbIdentifier == LoginDLDbIdentifier())) + { + CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mLoginDLDbIdentifier); + const void *value = reinterpret_cast(aDict); + loginArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks); + CFRelease(aDict); + } + + if (loginArray) + { + CFDictionarySetValue(mPropertyList, kLoginKeychainKey, loginArray); + CFRelease(loginArray); + } + else + CFDictionaryRemoveValue(mPropertyList, kLoginKeychainKey); + } + + if (mDefaultDLDbIdentifierSet) + { + // Make a temporary CFArray with the default keychain + CFArrayRef defaultArray = NULL; + if (!mDefaultDLDbIdentifier) + { + defaultArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks); + } + else if (!(mDefaultDLDbIdentifier == LoginDLDbIdentifier())) + { + CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mDefaultDLDbIdentifier); + const void *value = reinterpret_cast(aDict); + defaultArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks); + CFRelease(aDict); + } + + if (defaultArray) + { + CFDictionarySetValue(mPropertyList, kDefaultKeychainKey, defaultArray); + CFRelease(defaultArray); + } + else + CFDictionaryRemoveValue(mPropertyList, kDefaultKeychainKey); + } + + writePropertyList(); + changed(false); +} + + +//---------------------------------------------------------------------- +// Conversions +//---------------------------------------------------------------------- + +DLDbIdentifier DLDbListCFPref::LoginDLDbIdentifier() +{ + CSSM_VERSION theVersion={}; + CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP); + CssmNetAddress *dbLocation=NULL; + + switch (mDomain) { + case kSecPreferencesDomainUser: + return DLDbIdentifier(ssuid, ExpandTildesInPath(kUserLoginKeychainPath).c_str(), dbLocation); + default: + assert(false); + case kSecPreferencesDomainSystem: + case kSecPreferencesDomainCommon: + return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation); + } +} + +DLDbIdentifier DLDbListCFPref::JaguarLoginDLDbIdentifier() +{ + CSSM_VERSION theVersion={}; + CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP); + CssmNetAddress *dbLocation=NULL; + + switch (mDomain) { + case kSecPreferencesDomainUser: + { + string basepath = ExpandTildesInPath(kLoginKeychainPathPrefix) + getPwInfo(kUsername); + return DLDbIdentifier(ssuid,basepath.c_str(),dbLocation); + } + case kSecPreferencesDomainSystem: + case kSecPreferencesDomainCommon: + return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation); + default: + assert(false); + return DLDbIdentifier(); + } +} + +DLDbIdentifier DLDbListCFPref::makeDLDbIdentifier (const CSSM_GUID &guid, const CSSM_VERSION &version, + uint32 subserviceId, CSSM_SERVICE_TYPE subserviceType, + const char* dbName, CSSM_NET_ADDRESS *dbLocation) +{ + CssmSubserviceUid ssuid (guid, &version, subserviceId, subserviceType); + return DLDbIdentifier (ssuid, ExpandTildesInPath (dbName).c_str (), dbLocation); +} + +DLDbIdentifier DLDbListCFPref::cfDictionaryRefToDLDbIdentifier(CFDictionaryRef theDict) +{ + // We must get individual values from the dictionary and store in basic types + if (CFGetTypeID(theDict) != CFDictionaryGetTypeID()) + throw std::logic_error("wrong type in property list"); + + // GUID + CCFValue vGuid(::CFDictionaryGetValue(theDict,kKeyGUID)); + string guidStr=vGuid; + const Guid guid(guidStr.c_str()); + + //CSSM_VERSION + CSSM_VERSION theVersion={0,}; + CCFValue vMajor(::CFDictionaryGetValue(theDict,kKeyMajorVersion)); + theVersion.Major = vMajor; + CCFValue vMinor(::CFDictionaryGetValue(theDict,kKeyMinorVersion)); + theVersion.Minor = vMinor; + + //subserviceId + CCFValue vSsid(::CFDictionaryGetValue(theDict,kKeySubserviceId)); + uint32 subserviceId=sint32(vSsid); + + //CSSM_SERVICE_TYPE + CSSM_SERVICE_TYPE subserviceType=CSSM_SERVICE_DL; + CCFValue vSsType(::CFDictionaryGetValue(theDict,kKeySubserviceType)); + subserviceType=vSsType; + + // Get DbName from dictionary + CCFValue vDbName(::CFDictionaryGetValue(theDict,kKeyDbName)); + string dbName=vDbName; + + // jch Get DbLocation from dictionary + CssmNetAddress *dbLocation=NULL; + + return makeDLDbIdentifier (guid, theVersion, subserviceId, subserviceType, dbName.c_str (), dbLocation); +} + +void DLDbListCFPref::clearPWInfo () +{ + if (mPdbLookup != NULL) + { + delete mPdbLookup; + mPdbLookup = NULL; + } +} + +string DLDbListCFPref::getPwInfo(PwInfoType type) +{ + const char *value; + switch (type) + { + case kHomeDir: + if (KeychainHomeFromXPC) { + value = xpc_string_get_string_ptr(KeychainHomeFromXPC); + } else { + value = getenv("HOME"); + } + if (value) + return value; + break; + case kUsername: + value = getenv("USER"); + if (value) + return value; + break; + } + + // Get our effective uid + uid_t uid = geteuid(); + // If we are setuid root use the real uid instead + if (!uid) uid = getuid(); + + // get the password entries + if (mPdbLookup == NULL) + { + mPdbLookup = new PasswordDBLookup (); + } + + mPdbLookup->lookupInfoOnUID (uid); + + string result; + switch (type) + { + case kHomeDir: + result = mPdbLookup->getDirectory (); + break; + case kUsername: + result = mPdbLookup->getName (); + break; + } + + return result; +} + +static void check_app_sandbox() +{ + if (!_xpc_runtime_is_app_sandboxed()) { + // We are not in a sandbox, no work to do here + return; + } + + extern xpc_object_t xpc_create_with_format(const char * format, ...); + xpc_connection_t con = xpc_connection_create("com.apple.security.XPCKeychainSandboxCheck", NULL); + xpc_connection_set_event_handler(con, ^(xpc_object_t event) { + xpc_type_t xtype = xpc_get_type(event); + if (XPC_TYPE_ERROR == xtype) { + syslog(LOG_ERR, "Keychain sandbox connection error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); + } else { + syslog(LOG_ERR, "Keychain sandbox unexpected connection event %p\n", event); + } + }); + xpc_connection_resume(con); + + xpc_object_t message = xpc_create_with_format("{op: GrantKeychainPaths}"); + xpc_object_t reply = xpc_connection_send_message_with_reply_sync(con, message); + xpc_type_t xtype = xpc_get_type(reply); + if (XPC_TYPE_DICTIONARY == xtype) { +#if 0 + // This is useful for debugging. + char *debug = xpc_copy_description(reply); + syslog(LOG_ERR, "DEBUG (KCsandbox) %s\n", debug); + free(debug); +#endif + + xpc_object_t extensions_array = xpc_dictionary_get_value(reply, "extensions"); + xpc_array_apply(extensions_array, ^(size_t index, xpc_object_t extension) { + char pbuf[MAXPATHLEN]; + char *path = pbuf; + int status = sandbox_consume_fs_extension(xpc_string_get_string_ptr(extension), &path); + if (status) { + syslog(LOG_ERR, "Keychain sandbox consume extension error: s=%d p=%s %m\n", status, path); + } + status = sandbox_release_fs_extension(xpc_string_get_string_ptr(extension)); + if (status) { + syslog(LOG_ERR, "Keychain sandbox release extension error: s=%d p=%s %m\n", status, path); + } + + return (bool)true; + }); + + KeychainHomeFromXPC = xpc_dictionary_get_value(reply, "keychain-home"); + xpc_retain(KeychainHomeFromXPC); + xpc_release(con); + } else if (XPC_TYPE_ERROR == xtype) { + syslog(LOG_ERR, "Keychain sandbox message error: %s\n", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION)); + } else { + syslog(LOG_ERR, "Keychain sandbox unexpected message reply type %p\n", xtype); + } + xpc_release(message); + xpc_release(reply); +} + + + +string DLDbListCFPref::ExpandTildesInPath(const string &inPath) +{ + dispatch_once(&AppSandboxChecked, ^{ + check_app_sandbox(); + }); + + if ((short)inPath.find("~/",0,2) == 0) + return getPwInfo(kHomeDir) + inPath.substr(1, inPath.length() - 1); + else + return inPath; +} + +string DLDbListCFPref::StripPathStuff(const string &inPath) +{ + if (inPath.find("/private/var/automount/Network/",0,31) == 0) + return inPath.substr(22); + if (inPath.find("/private/automount/Servers/",0,27) == 0) + return "/Network" + inPath.substr(18); + if (inPath.find("/automount/Servers/",0,19) == 0) + return "/Network" + inPath.substr(10); + if (inPath.find("/private/automount/Network/",0,27) == 0) + return inPath.substr(18); + if (inPath.find("/automount/Network/",0,19) == 0) + return inPath.substr(10); + if (inPath.find("/private/Network/",0,17) == 0) + return inPath.substr(8); + return inPath; +} + +string DLDbListCFPref::AbbreviatedPath(const string &inPath) +{ + string path = StripPathStuff(inPath); + string home = StripPathStuff(getPwInfo(kHomeDir) + "/"); + size_t homeLen = home.length(); + + if (homeLen > 1 && path.find(home.c_str(), 0, homeLen) == 0) + return "~" + path.substr(homeLen - 1); + else + return path; +} + +CFDictionaryRef DLDbListCFPref::dlDbIdentifierToCFDictionaryRef(const DLDbIdentifier& dldbIdentifier) +{ + CFRef aDict(CFDictionaryCreateMutable(kCFAllocatorDefault,0, + &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks)); + if (!aDict) + throw ::std::bad_alloc(); + + // Put SUBSERVICE_UID in dictionary + char buffer[Guid::stringRepLength+1]; + const CssmSubserviceUid& ssuid=dldbIdentifier.ssuid(); + const Guid &theGuid = Guid::overlay(ssuid.Guid); + CFRef stringGuid(::CFStringCreateWithCString(kCFAllocatorDefault, + theGuid.toString(buffer),kCFStringEncodingMacRoman)); + if (stringGuid) + ::CFDictionarySetValue(aDict,kKeyGUID,stringGuid); + + if (ssuid.SubserviceId!=0) + { + CFRef subserviceId(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceId)); + if (subserviceId) + ::CFDictionarySetValue(aDict,kKeySubserviceId,subserviceId); + } + if (ssuid.SubserviceType!=0) + { + CFRef subserviceType(CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceType)); + if (subserviceType) + ::CFDictionarySetValue(aDict,kKeySubserviceType,subserviceType); + } + if (ssuid.Version.Major!=0 && ssuid.Version.Minor!=0) + { + CFRef majorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Major)); + if (majorVersion) + ::CFDictionarySetValue(aDict,kKeyMajorVersion,majorVersion); + CFRef minorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Minor)); + if (minorVersion) + ::CFDictionarySetValue(aDict,kKeyMinorVersion,minorVersion); + } + + // Put DbName in dictionary + const char *dbName=dldbIdentifier.dbName(); + if (dbName) + { + CFRef theDbName(::CFStringCreateWithCString(kCFAllocatorDefault,AbbreviatedPath(dbName).c_str(),kCFStringEncodingUTF8)); + ::CFDictionarySetValue(aDict,kKeyDbName,theDbName); + } + // Put DbLocation in dictionary + const CSSM_NET_ADDRESS *dbLocation=dldbIdentifier.dbLocation(); + if (dbLocation!=NULL && dbLocation->AddressType!=CSSM_ADDR_NONE) + { + CFRef theData(::CFDataCreate(kCFAllocatorDefault,dbLocation->Address.Data,dbLocation->Address.Length)); + if (theData) + ::CFDictionarySetValue(aDict,kKeyDbLocation,theData); + } + + ::CFRetain(aDict); + return aDict; +} + +bool DLDbListCFPref::revert(bool force) +{ + // If the prefs have not been refreshed in the last kDLDbListCFPrefRevertInterval + // seconds or we are asked to force a reload, then reload. + if (!loadPropertyList(force)) + return false; + + resetCachedValues(); + return true; +} + +void +DLDbListCFPref::add(const DLDbIdentifier &dldbIdentifier) +{ + // convert the location specified in dldbIdentifier to a standard form + // make a canonical form of the database name + std::string canon = ExpandTildesInPath(AbbreviatedPath(dldbIdentifier.dbName()).c_str()); + + DLDbIdentifier localIdentifier (dldbIdentifier.ssuid(), canon.c_str(), dldbIdentifier.dbLocation ()); + + if (member(localIdentifier)) + return; + + mSearchList.push_back(localIdentifier); + changed(true); +} + +void +DLDbListCFPref::remove(const DLDbIdentifier &dldbIdentifier) +{ + // Make sure mSearchList is set + searchList(); + for (vector::iterator ix = mSearchList.begin(); ix != mSearchList.end(); ++ix) + { + if (*ix==dldbIdentifier) // found in list + { + mSearchList.erase(ix); + changed(true); + break; + } + } +} + +void +DLDbListCFPref::rename(const DLDbIdentifier &oldId, const DLDbIdentifier &newId) +{ + // Make sure mSearchList is set + searchList(); + for (vector::iterator ix = mSearchList.begin(); + ix != mSearchList.end(); ++ix) + { + if (*ix==oldId) + { + // replace oldId with newId + *ix = newId; + changed(true); + } + else if (*ix==newId) + { + // remove newId except where we just inserted it + mSearchList.erase(ix); + changed(true); + } + } +} + +bool +DLDbListCFPref::member(const DLDbIdentifier &dldbIdentifier) +{ + if (dldbIdentifier.IsImplEmpty()) + { + return false; + } + + for (vector::const_iterator ix = searchList().begin(); ix != mSearchList.end(); ++ix) + { + if (ix->mImpl == NULL) + { + continue; + } + + // compare the dldbIdentifiers based on the full, real path to the keychain + if (ix->ssuid() == dldbIdentifier.ssuid()) + { + char localPath[PATH_MAX], + inPath[PATH_MAX]; + + // try to resolve these down to a canonical form + const char* localPathPtr = cached_realpath(ix->dbName(), localPath); + const char* inPathPtr = cached_realpath(dldbIdentifier.dbName(), inPath); + + // if either of the paths didn't resolve for some reason, use the originals + if (localPathPtr == NULL) + { + localPathPtr = ix->dbName(); + } + + if (inPathPtr == NULL) + { + inPathPtr = dldbIdentifier.dbName(); + } + + if (strcmp(localPathPtr, inPathPtr) == 0) + { + return true; + } + } + } + + return false; +} + +const vector & +DLDbListCFPref::searchList() +{ + if (!mSearchListSet) + { + CFArrayRef searchList = reinterpret_cast(CFDictionaryGetValue(mPropertyList, kDefaultDLDbListKey)); + if (searchList && CFGetTypeID(searchList) != CFArrayGetTypeID()) + searchList = NULL; + + if (searchList) + { + CFIndex top = CFArrayGetCount(searchList); + // Each entry is a CFDictionary; peel it off & add it to the array + for (CFIndex idx = 0; idx < top; ++idx) + { + CFDictionaryRef theDict = reinterpret_cast(CFArrayGetValueAtIndex(searchList, idx)); + try + { + mSearchList.push_back(cfDictionaryRefToDLDbIdentifier(theDict)); + } + catch (...) + { + // Drop stuff that doesn't parse on the floor. + } + } + + // If there were entries specified, but they were invalid revert to using the + // default keychain in the searchlist. + if (top > 0 && mSearchList.size() == 0) + searchList = NULL; + } + + // The default when no search list is specified is to only search the + // default keychain. + if (!searchList && static_cast(defaultDLDbIdentifier())) + mSearchList.push_back(mDefaultDLDbIdentifier); + + mSearchListSet = true; + } + + return mSearchList; +} + +void +DLDbListCFPref::searchList(const vector &searchList) +{ + vector newList(searchList); + mSearchList.swap(newList); + mSearchListSet = true; + changed(true); +} + +void +DLDbListCFPref::defaultDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier) +{ + if (!(defaultDLDbIdentifier() == dlDbIdentifier)) + { + mDefaultDLDbIdentifier = dlDbIdentifier; + changed(true); + } +} + +const DLDbIdentifier & +DLDbListCFPref::defaultDLDbIdentifier() +{ + + if (!mDefaultDLDbIdentifierSet) + { + CFArrayRef defaultArray = reinterpret_cast(CFDictionaryGetValue(mPropertyList, kDefaultKeychainKey)); + if (defaultArray && CFGetTypeID(defaultArray) != CFArrayGetTypeID()) + defaultArray = NULL; + + if (defaultArray && CFArrayGetCount(defaultArray) > 0) + { + CFDictionaryRef defaultDict = reinterpret_cast(CFArrayGetValueAtIndex(defaultArray, 0)); + try + { + secdebug("secpref", "getting default DLDbIdentifier from defaultDict"); + mDefaultDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(defaultDict); + secdebug("secpref", "now we think the default keychain is %s", (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : ""); + } + catch (...) + { + // If defaultArray doesn't parse fall back on the default way of getting the default keychain + defaultArray = NULL; + } + } + + if (!defaultArray) + { + + // If the Panther style login keychain actually exists we use that otherwise no + // default is set. + mDefaultDLDbIdentifier = loginDLDbIdentifier(); + secdebug("secpref", "now we think the default keychain is: %s", (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : + "Name doesn't exist"); + + struct stat st; + int st_result = -1; + + if (mDefaultDLDbIdentifier.mImpl != NULL) + { + st_result = stat(mDefaultDLDbIdentifier.dbName(), &st); + } + + if (st_result) + { + secdebug("secpref", "stat(%s) -> %d", mDefaultDLDbIdentifier.dbName(), st_result); + mDefaultDLDbIdentifier = DLDbIdentifier(); // initialize a NULL keychain + secdebug("secpref", "after DLDbIdentifier(), we think the default keychain is %s", static_cast(mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : ""); + } + } + + mDefaultDLDbIdentifierSet = true; + } + + + return mDefaultDLDbIdentifier; +} + +void +DLDbListCFPref::loginDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier) +{ + if (!(loginDLDbIdentifier() == dlDbIdentifier)) + { + mLoginDLDbIdentifier = dlDbIdentifier; + changed(true); + } +} + +const DLDbIdentifier & +DLDbListCFPref::loginDLDbIdentifier() +{ + if (!mLoginDLDbIdentifierSet) + { + CFArrayRef loginArray = reinterpret_cast(CFDictionaryGetValue(mPropertyList, kLoginKeychainKey)); + if (loginArray && CFGetTypeID(loginArray) != CFArrayGetTypeID()) + loginArray = NULL; + + if (loginArray && CFArrayGetCount(loginArray) > 0) + { + CFDictionaryRef loginDict = reinterpret_cast(CFArrayGetValueAtIndex(loginArray, 0)); + try + { + secdebug("secpref", "Getting login DLDbIdentifier from loginDict"); + mLoginDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(loginDict); + secdebug("secpref", "we think the login keychain is %s", static_cast(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : ""); + } + catch (...) + { + // If loginArray doesn't parse fall back on the default way of getting the login keychain. + loginArray = NULL; + } + } + + if (!loginArray) + { + mLoginDLDbIdentifier = LoginDLDbIdentifier(); + secdebug("secpref", "after LoginDLDbIdentifier(), we think the login keychain is %s", static_cast(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : ""); + } + + mLoginDLDbIdentifierSet = true; + } + + return mLoginDLDbIdentifier; +}