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