]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/DLDBListCFPref.cpp
Security-58286.251.4.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / lib / 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 // Resync from disc to make sure we don't clobber anyone elses changes.
422 // @@@ This is probably already done by the next layer up so we don't
423 // really need to do it here again.
424 loadPropertyList(true);
425
426 // Do the searchList first since it might end up invoking defaultDLDbIdentifier() which can set
427 // mLoginDLDbIdentifierSet and mDefaultDLDbIdentifierSet to true.
428 if (mSearchListSet)
429 {
430 // Make a temporary CFArray with the contents of the vector
431 if (mSearchList.size() == 1 && mSearchList[0] == defaultDLDbIdentifier() && mSearchList[0] == LoginDLDbIdentifier())
432 {
433 // The only element in the search list is the default keychain, which is a
434 // post Jaguar style login keychain, so omit the entry from the prefs file.
435 CFDictionaryRemoveValue(mPropertyList, kDefaultDLDbListKey);
436 }
437 else
438 {
439 CFMutableArrayRef searchArray = CFArrayCreateMutable(kCFAllocatorDefault, mSearchList.size(), &kCFTypeArrayCallBacks);
440 for (DLDbList::const_iterator ix=mSearchList.begin();ix!=mSearchList.end();ix++)
441 {
442 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(*ix);
443 CFArrayAppendValue(searchArray, aDict);
444 CFRelease(aDict);
445 }
446
447 CFDictionarySetValue(mPropertyList, kDefaultDLDbListKey, searchArray);
448 CFRelease(searchArray);
449 }
450 }
451
452 if (mLoginDLDbIdentifierSet)
453 {
454 // Make a temporary CFArray with the login keychain
455 CFArrayRef loginArray = NULL;
456 if (!mLoginDLDbIdentifier)
457 {
458 loginArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks);
459 }
460 else if (!(mLoginDLDbIdentifier == LoginDLDbIdentifier()))
461 {
462 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mLoginDLDbIdentifier);
463 const void *value = reinterpret_cast<const void *>(aDict);
464 loginArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks);
465 CFRelease(aDict);
466 }
467
468 if (loginArray)
469 {
470 CFDictionarySetValue(mPropertyList, kLoginKeychainKey, loginArray);
471 CFRelease(loginArray);
472 }
473 else
474 CFDictionaryRemoveValue(mPropertyList, kLoginKeychainKey);
475 }
476
477 if (mDefaultDLDbIdentifierSet)
478 {
479 // Make a temporary CFArray with the default keychain
480 CFArrayRef defaultArray = NULL;
481 if (!mDefaultDLDbIdentifier)
482 {
483 defaultArray = CFArrayCreate(kCFAllocatorDefault, NULL, 0, &kCFTypeArrayCallBacks);
484 }
485 else if (!(mDefaultDLDbIdentifier == LoginDLDbIdentifier()))
486 {
487 CFDictionaryRef aDict = dlDbIdentifierToCFDictionaryRef(mDefaultDLDbIdentifier);
488 const void *value = reinterpret_cast<const void *>(aDict);
489 defaultArray = CFArrayCreate(kCFAllocatorDefault, &value, 1, &kCFTypeArrayCallBacks);
490 CFRelease(aDict);
491 }
492
493 if (defaultArray)
494 {
495 CFDictionarySetValue(mPropertyList, kDefaultKeychainKey, defaultArray);
496 CFRelease(defaultArray);
497 }
498 else
499 CFDictionaryRemoveValue(mPropertyList, kDefaultKeychainKey);
500 }
501
502 writePropertyList();
503 changed(false);
504 }
505
506
507 //----------------------------------------------------------------------
508 // Conversions
509 //----------------------------------------------------------------------
510
511 DLDbIdentifier DLDbListCFPref::LoginDLDbIdentifier()
512 {
513 CSSM_VERSION theVersion={};
514 CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP);
515 CssmNetAddress *dbLocation=NULL;
516
517 switch (mDomain) {
518 case kSecPreferencesDomainUser:
519 return DLDbIdentifier(ssuid, ExpandTildesInPath(kUserLoginKeychainPath).c_str(), dbLocation);
520 default:
521 assert(false);
522 case kSecPreferencesDomainSystem:
523 case kSecPreferencesDomainCommon:
524 return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation);
525 }
526 }
527
528 DLDbIdentifier DLDbListCFPref::JaguarLoginDLDbIdentifier()
529 {
530 CSSM_VERSION theVersion={};
531 CssmSubserviceUid ssuid(gGuidAppleCSPDL,&theVersion,0,CSSM_SERVICE_DL|CSSM_SERVICE_CSP);
532 CssmNetAddress *dbLocation=NULL;
533
534 switch (mDomain) {
535 case kSecPreferencesDomainUser:
536 {
537 string basepath = ExpandTildesInPath(kLoginKeychainPathPrefix) + getPwInfo(kUsername);
538 return DLDbIdentifier(ssuid,basepath.c_str(),dbLocation);
539 }
540 case kSecPreferencesDomainSystem:
541 case kSecPreferencesDomainCommon:
542 return DLDbIdentifier(ssuid, kSystemLoginKeychainPath, dbLocation);
543 default:
544 assert(false);
545 return DLDbIdentifier();
546 }
547 }
548
549 DLDbIdentifier DLDbListCFPref::makeDLDbIdentifier (const CSSM_GUID &guid, const CSSM_VERSION &version,
550 uint32 subserviceId, CSSM_SERVICE_TYPE subserviceType,
551 const char* dbName, CSSM_NET_ADDRESS *dbLocation)
552 {
553 CssmSubserviceUid ssuid (guid, &version, subserviceId, subserviceType);
554 return DLDbIdentifier (ssuid, ExpandTildesInPath (dbName).c_str (), dbLocation);
555 }
556
557 DLDbIdentifier DLDbListCFPref::cfDictionaryRefToDLDbIdentifier(CFDictionaryRef theDict)
558 {
559 // We must get individual values from the dictionary and store in basic types
560 if (CFGetTypeID(theDict) != CFDictionaryGetTypeID())
561 throw std::logic_error("wrong type in property list");
562
563 // GUID
564 CCFValue vGuid(::CFDictionaryGetValue(theDict,kKeyGUID));
565 string guidStr=vGuid;
566 const Guid guid(guidStr.c_str());
567
568 //CSSM_VERSION
569 CSSM_VERSION theVersion={0,};
570 CCFValue vMajor(::CFDictionaryGetValue(theDict,kKeyMajorVersion));
571 theVersion.Major = vMajor;
572 CCFValue vMinor(::CFDictionaryGetValue(theDict,kKeyMinorVersion));
573 theVersion.Minor = vMinor;
574
575 //subserviceId
576 CCFValue vSsid(::CFDictionaryGetValue(theDict,kKeySubserviceId));
577 uint32 subserviceId=sint32(vSsid);
578
579 //CSSM_SERVICE_TYPE
580 CSSM_SERVICE_TYPE subserviceType=CSSM_SERVICE_DL;
581 CCFValue vSsType(::CFDictionaryGetValue(theDict,kKeySubserviceType));
582 subserviceType=vSsType;
583
584 // Get DbName from dictionary
585 CCFValue vDbName(::CFDictionaryGetValue(theDict,kKeyDbName));
586 string dbName=vDbName;
587
588 // jch Get DbLocation from dictionary
589 CssmNetAddress *dbLocation=NULL;
590
591 return makeDLDbIdentifier (guid, theVersion, subserviceId, subserviceType, dbName.c_str (), dbLocation);
592 }
593
594 void DLDbListCFPref::clearPWInfo ()
595 {
596 if (mPdbLookup != NULL)
597 {
598 delete mPdbLookup;
599 mPdbLookup = NULL;
600 }
601 }
602
603 string DLDbListCFPref::getPwInfo(PwInfoType type)
604 {
605 const char *value;
606 switch (type)
607 {
608 case kHomeDir:
609 if (KeychainHomeFromXPC) {
610 value = xpc_string_get_string_ptr(KeychainHomeFromXPC);
611 } else {
612 value = getenv("HOME");
613 }
614 if (value)
615 return value;
616 break;
617 case kUsername:
618 value = getenv("USER");
619 if (value)
620 return value;
621 break;
622 }
623
624 // Get our effective uid
625 uid_t uid = geteuid();
626 // If we are setuid root use the real uid instead
627 if (!uid) uid = getuid();
628
629 // get the password entries
630 if (mPdbLookup == NULL)
631 {
632 mPdbLookup = new PasswordDBLookup ();
633 }
634
635 mPdbLookup->lookupInfoOnUID (uid);
636
637 string result;
638 switch (type)
639 {
640 case kHomeDir:
641 result = mPdbLookup->getDirectory ();
642 break;
643 case kUsername:
644 result = mPdbLookup->getName ();
645 break;
646 }
647
648 return result;
649 }
650
651 static void check_app_sandbox()
652 {
653 if (!_xpc_runtime_is_app_sandboxed()) {
654 // We are not in a sandbox, no work to do here
655 return;
656 }
657
658 extern xpc_object_t xpc_create_with_format(const char * format, ...);
659 xpc_connection_t con = xpc_connection_create("com.apple.security.XPCKeychainSandboxCheck", NULL);
660 xpc_connection_set_event_handler(con, ^(xpc_object_t event) {
661 xpc_type_t xtype = xpc_get_type(event);
662 if (XPC_TYPE_ERROR == xtype) {
663 syslog(LOG_ERR, "Keychain sandbox connection error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
664 } else {
665 syslog(LOG_ERR, "Keychain sandbox unexpected connection event %p\n", event);
666 }
667 });
668 xpc_connection_resume(con);
669
670 xpc_object_t message = xpc_create_with_format("{op: GrantKeychainPaths}");
671 xpc_object_t reply = xpc_connection_send_message_with_reply_sync(con, message);
672 xpc_type_t xtype = xpc_get_type(reply);
673 if (XPC_TYPE_DICTIONARY == xtype) {
674 #if 0
675 // This is useful for debugging.
676 char *debug = xpc_copy_description(reply);
677 syslog(LOG_ERR, "DEBUG (KCsandbox) %s\n", debug);
678 free(debug);
679 #endif
680
681 xpc_object_t extensions_array = xpc_dictionary_get_value(reply, "extensions");
682 xpc_array_apply(extensions_array, ^(size_t index, xpc_object_t extension) {
683 char pbuf[MAXPATHLEN];
684 char *path = pbuf;
685 int status = sandbox_consume_fs_extension(xpc_string_get_string_ptr(extension), &path);
686 if (status) {
687 syslog(LOG_ERR, "Keychain sandbox consume extension error: s=%d p=%s %m\n", status, path);
688 }
689 status = sandbox_release_fs_extension(xpc_string_get_string_ptr(extension));
690 if (status) {
691 syslog(LOG_ERR, "Keychain sandbox release extension error: s=%d p=%s %m\n", status, path);
692 }
693
694 return (bool)true;
695 });
696
697 KeychainHomeFromXPC = xpc_dictionary_get_value(reply, "keychain-home");
698 xpc_retain(KeychainHomeFromXPC);
699 xpc_release(con);
700 } else if (XPC_TYPE_ERROR == xtype) {
701 syslog(LOG_ERR, "Keychain sandbox message error: %s\n", xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION));
702 } else {
703 syslog(LOG_ERR, "Keychain sandbox unexpected message reply type %p\n", xtype);
704 }
705 xpc_release(message);
706 xpc_release(reply);
707 }
708
709
710
711 string DLDbListCFPref::ExpandTildesInPath(const string &inPath)
712 {
713 dispatch_once(&AppSandboxChecked, ^{
714 check_app_sandbox();
715 });
716
717 if ((short)inPath.find("~/",0,2) == 0)
718 return getPwInfo(kHomeDir) + inPath.substr(1, inPath.length() - 1);
719 else
720 return inPath;
721 }
722
723 string DLDbListCFPref::StripPathStuff(const string &inPath)
724 {
725 if (inPath.find("/private/var/automount/Network/",0,31) == 0)
726 return inPath.substr(22);
727 if (inPath.find("/private/automount/Servers/",0,27) == 0)
728 return "/Network" + inPath.substr(18);
729 if (inPath.find("/automount/Servers/",0,19) == 0)
730 return "/Network" + inPath.substr(10);
731 if (inPath.find("/private/automount/Network/",0,27) == 0)
732 return inPath.substr(18);
733 if (inPath.find("/automount/Network/",0,19) == 0)
734 return inPath.substr(10);
735 if (inPath.find("/private/Network/",0,17) == 0)
736 return inPath.substr(8);
737 return inPath;
738 }
739
740 string DLDbListCFPref::AbbreviatedPath(const string &inPath)
741 {
742 string path = StripPathStuff(inPath);
743 string home = StripPathStuff(getPwInfo(kHomeDir) + "/");
744 size_t homeLen = home.length();
745
746 if (homeLen > 1 && path.find(home.c_str(), 0, homeLen) == 0)
747 return "~" + path.substr(homeLen - 1);
748 else
749 return path;
750 }
751
752 CFDictionaryRef DLDbListCFPref::dlDbIdentifierToCFDictionaryRef(const DLDbIdentifier& dldbIdentifier)
753 {
754 CFRef<CFMutableDictionaryRef> aDict(CFDictionaryCreateMutable(kCFAllocatorDefault,0,
755 &kCFTypeDictionaryKeyCallBacks,&kCFTypeDictionaryValueCallBacks));
756 if (!aDict)
757 throw ::std::bad_alloc();
758
759 // Put SUBSERVICE_UID in dictionary
760 char buffer[Guid::stringRepLength+1];
761 const CssmSubserviceUid& ssuid=dldbIdentifier.ssuid();
762 const Guid &theGuid = Guid::overlay(ssuid.Guid);
763 CFRef<CFStringRef> stringGuid(::CFStringCreateWithCString(kCFAllocatorDefault,
764 theGuid.toString(buffer),kCFStringEncodingMacRoman));
765 if (stringGuid)
766 ::CFDictionarySetValue(aDict,kKeyGUID,stringGuid);
767
768 if (ssuid.SubserviceId!=0)
769 {
770 CFRef<CFNumberRef> subserviceId(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceId));
771 if (subserviceId)
772 ::CFDictionarySetValue(aDict,kKeySubserviceId,subserviceId);
773 }
774 if (ssuid.SubserviceType!=0)
775 {
776 CFRef<CFNumberRef> subserviceType(CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.SubserviceType));
777 if (subserviceType)
778 ::CFDictionarySetValue(aDict,kKeySubserviceType,subserviceType);
779 }
780 if (ssuid.Version.Major!=0 && ssuid.Version.Minor!=0)
781 {
782 CFRef<CFNumberRef> majorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Major));
783 if (majorVersion)
784 ::CFDictionarySetValue(aDict,kKeyMajorVersion,majorVersion);
785 CFRef<CFNumberRef> minorVersion(::CFNumberCreate(kCFAllocatorDefault,kCFNumberSInt32Type,&ssuid.Version.Minor));
786 if (minorVersion)
787 ::CFDictionarySetValue(aDict,kKeyMinorVersion,minorVersion);
788 }
789
790 // Put DbName in dictionary
791 const char *dbName=dldbIdentifier.dbName();
792 if (dbName)
793 {
794 CFRef<CFStringRef> theDbName(::CFStringCreateWithCString(kCFAllocatorDefault,AbbreviatedPath(dbName).c_str(),kCFStringEncodingUTF8));
795 ::CFDictionarySetValue(aDict,kKeyDbName,theDbName);
796 }
797 // Put DbLocation in dictionary
798 const CSSM_NET_ADDRESS *dbLocation=dldbIdentifier.dbLocation();
799 if (dbLocation!=NULL && dbLocation->AddressType!=CSSM_ADDR_NONE)
800 {
801 CFRef<CFDataRef> theData(::CFDataCreate(kCFAllocatorDefault,dbLocation->Address.Data,dbLocation->Address.Length));
802 if (theData)
803 ::CFDictionarySetValue(aDict,kKeyDbLocation,theData);
804 }
805
806 ::CFRetain(aDict);
807 return aDict;
808 }
809
810 bool DLDbListCFPref::revert(bool force)
811 {
812 // If the prefs have not been refreshed in the last kDLDbListCFPrefRevertInterval
813 // seconds or we are asked to force a reload, then reload.
814 if (!loadPropertyList(force))
815 return false;
816
817 resetCachedValues();
818 return true;
819 }
820
821 void
822 DLDbListCFPref::add(const DLDbIdentifier &dldbIdentifier)
823 {
824 // convert the location specified in dldbIdentifier to a standard form
825 // make a canonical form of the database name
826 std::string canon = ExpandTildesInPath(AbbreviatedPath(dldbIdentifier.dbName()).c_str());
827
828 DLDbIdentifier localIdentifier (dldbIdentifier.ssuid(), canon.c_str(), dldbIdentifier.dbLocation ());
829
830 if (member(localIdentifier))
831 return;
832
833 mSearchList.push_back(localIdentifier);
834 changed(true);
835 }
836
837 void
838 DLDbListCFPref::remove(const DLDbIdentifier &dldbIdentifier)
839 {
840 // Make sure mSearchList is set
841 searchList();
842 for (vector<DLDbIdentifier>::iterator ix = mSearchList.begin(); ix != mSearchList.end(); ++ix)
843 {
844 if (*ix==dldbIdentifier) // found in list
845 {
846 mSearchList.erase(ix);
847 changed(true);
848 break;
849 }
850 }
851 }
852
853 void
854 DLDbListCFPref::rename(const DLDbIdentifier &oldId, const DLDbIdentifier &newId)
855 {
856 // Make sure mSearchList is set
857 searchList();
858 for (vector<DLDbIdentifier>::iterator ix = mSearchList.begin();
859 ix != mSearchList.end(); ++ix)
860 {
861 if (*ix==oldId)
862 {
863 // replace oldId with newId
864 *ix = newId;
865 changed(true);
866 }
867 else if (*ix==newId)
868 {
869 // remove newId except where we just inserted it
870 mSearchList.erase(ix);
871 changed(true);
872 }
873 }
874 }
875
876 bool
877 DLDbListCFPref::member(const DLDbIdentifier &dldbIdentifier)
878 {
879 if (dldbIdentifier.IsImplEmpty())
880 {
881 return false;
882 }
883
884 for (vector<DLDbIdentifier>::const_iterator ix = searchList().begin(); ix != mSearchList.end(); ++ix)
885 {
886 if (ix->mImpl == NULL)
887 {
888 continue;
889 }
890
891 // compare the dldbIdentifiers based on the full, real path to the keychain
892 if (ix->ssuid() == dldbIdentifier.ssuid())
893 {
894 char localPath[PATH_MAX],
895 inPath[PATH_MAX];
896
897 // try to resolve these down to a canonical form
898 const char* localPathPtr = cached_realpath(ix->dbName(), localPath);
899 const char* inPathPtr = cached_realpath(dldbIdentifier.dbName(), inPath);
900
901 // if either of the paths didn't resolve for some reason, use the originals
902 if (localPathPtr == NULL)
903 {
904 localPathPtr = ix->dbName();
905 }
906
907 if (inPathPtr == NULL)
908 {
909 inPathPtr = dldbIdentifier.dbName();
910 }
911
912 if (strcmp(localPathPtr, inPathPtr) == 0)
913 {
914 return true;
915 }
916 }
917 }
918
919 return false;
920 }
921
922 const vector<DLDbIdentifier> &
923 DLDbListCFPref::searchList()
924 {
925 if (!mSearchListSet)
926 {
927 CFArrayRef searchList = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kDefaultDLDbListKey));
928 if (searchList && CFGetTypeID(searchList) != CFArrayGetTypeID())
929 searchList = NULL;
930
931 if (searchList)
932 {
933 CFIndex top = CFArrayGetCount(searchList);
934 // Each entry is a CFDictionary; peel it off & add it to the array
935 for (CFIndex idx = 0; idx < top; ++idx)
936 {
937 CFDictionaryRef theDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(searchList, idx));
938 try
939 {
940 mSearchList.push_back(cfDictionaryRefToDLDbIdentifier(theDict));
941 }
942 catch (...)
943 {
944 // Drop stuff that doesn't parse on the floor.
945 }
946 }
947
948 // If there were entries specified, but they were invalid revert to using the
949 // default keychain in the searchlist.
950 if (top > 0 && mSearchList.size() == 0)
951 searchList = NULL;
952 }
953
954 // The default when no search list is specified is to only search the
955 // default keychain.
956 if (!searchList && static_cast<bool>(defaultDLDbIdentifier()))
957 mSearchList.push_back(mDefaultDLDbIdentifier);
958
959 mSearchListSet = true;
960 }
961
962 return mSearchList;
963 }
964
965 void
966 DLDbListCFPref::searchList(const vector<DLDbIdentifier> &searchList)
967 {
968 if(searchList.size() == 0) {
969 mSearchList.clear();
970 mSearchListSet = false;
971 changed(true);
972 return;
973 }
974
975 vector<DLDbIdentifier> newList(searchList);
976 mSearchList.swap(newList);
977 mSearchListSet = true;
978 changed(true);
979 }
980
981 void
982 DLDbListCFPref::defaultDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier)
983 {
984 if (!(defaultDLDbIdentifier() == dlDbIdentifier))
985 {
986 mDefaultDLDbIdentifier = dlDbIdentifier;
987 changed(true);
988 }
989 }
990
991 // Caution: if the backing file for the defaultDLDbIdentifier doesn't exist (or if the plist file is corrupt),
992 // this will return a DLDbIdentifier with a NULL impl
993 const DLDbIdentifier &
994 DLDbListCFPref::defaultDLDbIdentifier()
995 {
996
997 if (!mDefaultDLDbIdentifierSet)
998 {
999 CFArrayRef defaultArray = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kDefaultKeychainKey));
1000 if (defaultArray && CFGetTypeID(defaultArray) != CFArrayGetTypeID())
1001 defaultArray = NULL;
1002
1003 if (defaultArray && CFArrayGetCount(defaultArray) > 0)
1004 {
1005 CFDictionaryRef defaultDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(defaultArray, 0));
1006 try
1007 {
1008 secinfo("secpref", "getting default DLDbIdentifier from defaultDict");
1009 mDefaultDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(defaultDict);
1010 secinfo("secpref", "now we think the default keychain is %s", (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "<NULL>");
1011 }
1012 catch (...)
1013 {
1014 // If defaultArray doesn't parse fall back on the default way of getting the default keychain
1015 defaultArray = NULL;
1016 }
1017 }
1018
1019 if (!defaultArray)
1020 {
1021 // If the Panther style login keychain actually exists we use that otherwise no
1022 // default is set.
1023 mDefaultDLDbIdentifier = loginDLDbIdentifier();
1024
1025 //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".
1026 DLDbIdentifier actualIdentifier = KeychainCore::StorageManager::mungeDLDbIdentifier(mDefaultDLDbIdentifier, false);
1027 secinfo("secpref", "now we think the default keychain is: %s (actual: %s)",
1028 (mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "Name doesn't exist",
1029 (actualIdentifier) ? actualIdentifier.dbName() : "Name doesn't exist");
1030
1031 struct stat st;
1032 int st_result = -1;
1033
1034 if (mDefaultDLDbIdentifier.mImpl != NULL && actualIdentifier.mImpl != NULL)
1035 {
1036 st_result = stat(actualIdentifier.dbName(), &st);
1037
1038 // Always claim that the system keychain exists for purposes of the search list
1039 if (st_result && 0 == strncmp(actualIdentifier.dbName(), kSystemKeychainPath, strlen(kSystemKeychainPath))) {
1040 secnotice("secpref", "System keychain (%s) does not exist. Continuing as if it does...", actualIdentifier.dbName());
1041 st_result = 0;
1042 }
1043 }
1044
1045 if (st_result)
1046 {
1047 secinfo("secpref", "stat(%s) -> %d", actualIdentifier.dbName(), st_result);
1048 mDefaultDLDbIdentifier = DLDbIdentifier(); // initialize a NULL keychain
1049 secinfo("secpref", "after DLDbIdentifier(), we think the default keychain is %s", static_cast<bool>(mDefaultDLDbIdentifier) ? mDefaultDLDbIdentifier.dbName() : "<NULL>");
1050 }
1051 }
1052
1053 mDefaultDLDbIdentifierSet = true;
1054 }
1055
1056
1057 return mDefaultDLDbIdentifier;
1058 }
1059
1060 void
1061 DLDbListCFPref::loginDLDbIdentifier(const DLDbIdentifier &dlDbIdentifier)
1062 {
1063 if (!(loginDLDbIdentifier() == dlDbIdentifier))
1064 {
1065 mLoginDLDbIdentifier = dlDbIdentifier;
1066 changed(true);
1067 }
1068 }
1069
1070 const DLDbIdentifier &
1071 DLDbListCFPref::loginDLDbIdentifier()
1072 {
1073 if (!mLoginDLDbIdentifierSet)
1074 {
1075 CFArrayRef loginArray = reinterpret_cast<CFArrayRef>(CFDictionaryGetValue(mPropertyList, kLoginKeychainKey));
1076 if (loginArray && CFGetTypeID(loginArray) != CFArrayGetTypeID())
1077 loginArray = NULL;
1078
1079 if (loginArray && CFArrayGetCount(loginArray) > 0)
1080 {
1081 CFDictionaryRef loginDict = reinterpret_cast<CFDictionaryRef>(CFArrayGetValueAtIndex(loginArray, 0));
1082 try
1083 {
1084 secinfo("secpref", "Getting login DLDbIdentifier from loginDict");
1085 mLoginDLDbIdentifier = cfDictionaryRefToDLDbIdentifier(loginDict);
1086 secinfo("secpref", "we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>");
1087 }
1088 catch (...)
1089 {
1090 // If loginArray doesn't parse fall back on the default way of getting the login keychain.
1091 loginArray = NULL;
1092 }
1093 }
1094
1095 if (!loginArray)
1096 {
1097 mLoginDLDbIdentifier = LoginDLDbIdentifier();
1098 secinfo("secpref", "after LoginDLDbIdentifier(), we think the login keychain is %s", static_cast<bool>(mLoginDLDbIdentifier) ? mLoginDLDbIdentifier.dbName() : "<NULL>");
1099 }
1100
1101 mLoginDLDbIdentifierSet = true;
1102 }
1103
1104 return mLoginDLDbIdentifier;
1105 }