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
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            struct stat st;
1012            int st_result = -1;
1013
1014			if (mDefaultDLDbIdentifier.mImpl != NULL)
1015			{
1016				st_result = stat(mDefaultDLDbIdentifier.dbName(), &st);
1017			}
1018
1019            if (st_result)
1020            {
1021				secdebug("secpref", "stat(%s) -> %d", mDefaultDLDbIdentifier.dbName(), st_result);
1022                mDefaultDLDbIdentifier  = DLDbIdentifier(); // initialize a NULL keychain
1023                secdebug("secpref", "after DLDbIdentifier(), we think the default keychain is %s", static_cast<bool>(mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "<NULL>");
1024            }
1025        }
1026
1027        mDefaultDLDbIdentifierSet = true;
1028    }
1029
1030
1031	return mDefaultDLDbIdentifier;
1032}
1033
1034void
1035DLDbListCFPref::loginDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier)
1036{
1037	if (!(loginDLDbIdentifier() == dlDbIdentifier))
1038	{
1039		mLoginDLDbIdentifier = dlDbIdentifier;
1040		changed(true);
1041	}
1042}
1043
1044const DLDbIdentifier &
1045DLDbListCFPref::loginDLDbIdentifier()
1046{
1047    if (!mLoginDLDbIdentifierSet)
1048    {
1049        CFArrayRef loginArray = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kLoginKeychainKey));
1050        if (loginArray && CFGetTypeID(loginArray) != CFArrayGetTypeID())
1051            loginArray = NULL;
1052
1053        if (loginArray && CFArrayGetCount(loginArray) > 0)
1054        {
1055            CFDictionaryRef loginDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(loginArray, 0));
1056            try
1057            {
1058                secdebug("secpref", "Getting login DLDbIdentifier from loginDict");
1059                mLoginDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(loginDict);
1060                secdebug("secpref", "we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>");
1061            }
1062            catch (...)
1063            {
1064                // If loginArray doesn't parse fall back on the default way of getting the login keychain.
1065                loginArray = NULL;
1066            }
1067        }
1068
1069        if (!loginArray)
1070        {
1071			mLoginDLDbIdentifier = LoginDLDbIdentifier();
1072			secdebug("secpref", "after LoginDLDbIdentifier(), we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>");
1073        }
1074
1075        mLoginDLDbIdentifierSet = true;
1076    }
1077
1078	return mLoginDLDbIdentifier;
1079}
1080