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