]> git.saurik.com Git - apple/security.git/blob - OSX/libsecurity_keychain/lib/StorageManager.cpp
Security-57337.50.23.tar.gz
[apple/security.git] / OSX / libsecurity_keychain / lib / StorageManager.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 File: StorageManager.cpp
27
28 Contains: Working with multiple keychains
29
30 */
31
32 #include "StorageManager.h"
33 #include "KCEventNotifier.h"
34
35 #include <Security/cssmapple.h>
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <syslog.h>
39 #include <pwd.h>
40 #include <algorithm>
41 #include <string>
42 #include <stdio.h>
43 //#include <Security/AuthorizationTags.h>
44 //#include <Security/AuthSession.h>
45 #include <security_utilities/debugging.h>
46 #include <security_keychain/SecCFTypes.h>
47 //#include <Security/SecurityAgentClient.h>
48 #include <securityd_client/ssclient.h>
49 #include <Security/AuthorizationTags.h>
50 #include <Security/AuthorizationTagsPriv.h>
51 #include <Security/SecTask.h>
52 #include <security_keychain/SecCFTypes.h>
53 #include "TrustSettingsSchema.h"
54 #include <security_cdsa_client/wrapkey.h>
55 #include <securityd_client/ssblob.h>
56
57 //%%% add this to AuthorizationTagsPriv.h later
58 #ifndef AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL
59 #define AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL "loginKCCreate:suppressResetPanel"
60 #endif
61
62 #include "KCCursor.h"
63 #include "Globals.h"
64
65
66 using namespace CssmClient;
67 using namespace KeychainCore;
68
69 #define kLoginKeychainPathPrefix "~/Library/Keychains/"
70 #define kUserLoginKeychainPath "~/Library/Keychains/login.keychain"
71 #define kEmptyKeychainSizeInBytes 20460
72
73 //-----------------------------------------------------------------------------------
74
75 static SecPreferencesDomain defaultPreferenceDomain()
76 {
77 SessionAttributeBits sessionAttrs;
78 if (gServerMode) {
79 secdebug("servermode", "StorageManager initialized in server mode");
80 sessionAttrs = sessionIsRoot;
81 } else {
82 MacOSError::check(SessionGetInfo(callerSecuritySession, NULL, &sessionAttrs));
83 }
84
85 // If this is the root session, use system preferences.
86 // (In SecurityServer debug mode, you'll get a (fake) root session
87 // that has graphics access. Ignore that to help testing.)
88 if ((sessionAttrs & sessionIsRoot)
89 IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
90 secdebug("storagemgr", "using system preferences");
91 return kSecPreferencesDomainSystem;
92 }
93
94 // otherwise, use normal (user) preferences
95 return kSecPreferencesDomainUser;
96 }
97
98 static bool isAppSandboxed()
99 {
100 bool result = false;
101 SecTaskRef task = SecTaskCreateFromSelf(NULL);
102 if(task != NULL) {
103 CFTypeRef appSandboxValue = SecTaskCopyValueForEntitlement(task,
104 CFSTR("com.apple.security.app-sandbox"), NULL);
105 if(appSandboxValue != NULL) {
106 result = true;
107 CFRelease(appSandboxValue);
108 }
109 CFRelease(task);
110 }
111 return result;
112 }
113
114 static bool shouldAddToSearchList(const DLDbIdentifier &dLDbIdentifier)
115 {
116 // Creation of a private keychain should not modify the search list: rdar://13529331
117 // However, we want to ensure the login and System keychains are in
118 // the search list if that is not the case when they are created.
119 // Note that App Sandbox apps may not modify the list in either case.
120
121 bool loginOrSystemKeychain = false;
122 const char *dbname = dLDbIdentifier.dbName();
123 if (dbname) {
124 if ((!strcmp(dbname, "/Library/Keychains/System.keychain")) ||
125 (strstr(dbname, "/login.keychain")) ) {
126 loginOrSystemKeychain = true;
127 }
128 }
129 return (loginOrSystemKeychain && !isAppSandboxed());
130 }
131
132
133 StorageManager::StorageManager() :
134 mSavedList(defaultPreferenceDomain()),
135 mCommonList(kSecPreferencesDomainCommon),
136 mDomain(kSecPreferencesDomainUser),
137 mMutex(Mutex::recursive)
138 {
139 }
140
141
142 Mutex*
143 StorageManager::getStorageManagerMutex()
144 {
145 return &mKeychainMapMutex;
146 }
147
148
149 Keychain
150 StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier)
151 {
152 StLock<Mutex>_(mKeychainMapMutex);
153
154 if (!dLDbIdentifier)
155 return Keychain();
156
157 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
158 if (it != mKeychains.end())
159 {
160 return it->second;
161 }
162
163 if (gServerMode) {
164 secdebug("servermode", "keychain reference in server mode");
165 return Keychain();
166 }
167
168 // The keychain is not in our cache. Create it.
169 Db db(makeDb(dLDbIdentifier));
170
171 Keychain keychain(db);
172 // Add the keychain to the cache.
173 mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, &*keychain));
174 keychain->inCache(true);
175
176 return keychain;
177 }
178
179 CssmClient::Db
180 StorageManager::makeDb(DLDbIdentifier dLDbIdentifier) {
181 Module module(dLDbIdentifier.ssuid().guid());
182
183 DL dl;
184 if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
185 dl = SSCSPDL(module);
186 else
187 dl = DL(module);
188
189 dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
190 dl->version(dLDbIdentifier.ssuid().version());
191
192 CssmClient::Db db(dl, dLDbIdentifier.dbName());
193
194 return db;
195 }
196
197 void
198 StorageManager::reloadKeychain(Keychain keychain) {
199 StLock<Mutex>_(mKeychainMapMutex);
200
201 DLDbIdentifier dLDbIdentifier = keychain->database()->dlDbIdentifier();
202
203 // Since we're going to reload this database and switch over the keychain's
204 // mDb, grab its mDb mutex
205 {
206 StLock<Mutex>__(keychain->mDbMutex);
207
208 CssmClient::Db db(makeDb(dLDbIdentifier));
209 keychain->mDb = db;
210 }
211
212 // Since this new database is based on the exact same dLDbIdentifier, we
213 // don't need to update the mKeychains map.
214 }
215
216 void
217 StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier,
218 KeychainImpl *keychainImpl)
219 {
220 // Lock the recursive mutex
221
222 StLock<Mutex>_(mKeychainMapMutex);
223
224 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
225 if (it != mKeychains.end() && (KeychainImpl*) it->second == keychainImpl)
226 mKeychains.erase(it);
227
228 keychainImpl->inCache(false);
229 }
230
231 void
232 StorageManager::didRemoveKeychain(const DLDbIdentifier &dLDbIdentifier)
233 {
234 // Lock the recursive mutex
235
236 StLock<Mutex>_(mKeychainMapMutex);
237
238 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
239 if (it != mKeychains.end())
240 {
241 mKeychains.erase(it);
242 }
243 }
244
245 // Create keychain if it doesn't exist, and optionally add it to the search list.
246 Keychain
247 StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add)
248 {
249 StLock<Mutex>_(mKeychainMapMutex);
250
251 Keychain theKeychain = keychain(dLDbIdentifier);
252 bool post = false;
253 bool updateList = (add && shouldAddToSearchList(dLDbIdentifier));
254
255 if (updateList)
256 {
257 mSavedList.revert(false);
258 DLDbList searchList = mSavedList.searchList();
259 if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
260 return theKeychain; // theKeychain is already in the searchList.
261
262 mCommonList.revert(false);
263 searchList = mCommonList.searchList();
264 if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
265 return theKeychain; // theKeychain is already in the commonList don't add it to the searchList.
266
267 // If theKeychain doesn't exist don't bother adding it to the search list yet.
268 if (!theKeychain->exists())
269 return theKeychain;
270
271 // theKeychain exists and is not in our search list, so add it to the
272 // search list.
273 mSavedList.revert(true);
274 mSavedList.add(dLDbIdentifier);
275 mSavedList.save();
276 post = true;
277 }
278
279 if (post)
280 {
281 // Make sure we are not holding mStorageManagerLock anymore when we
282 // post this event.
283 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
284 }
285
286 return theKeychain;
287 }
288
289 // Be notified a Keychain just got created.
290 void
291 StorageManager::created(const Keychain &keychain)
292 {
293 StLock<Mutex>_(mKeychainMapMutex);
294
295 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
296 bool defaultChanged = false;
297 bool updateList = shouldAddToSearchList(dLDbIdentifier);
298
299 if (updateList)
300 {
301 mSavedList.revert(true);
302 // If we don't have a default Keychain yet. Make the newly created
303 // keychain the default.
304 if (!mSavedList.defaultDLDbIdentifier())
305 {
306 mSavedList.defaultDLDbIdentifier(dLDbIdentifier);
307 defaultChanged = true;
308 }
309
310 // Add the keychain to the search list prefs.
311 mSavedList.add(dLDbIdentifier);
312 mSavedList.save();
313
314 // Make sure we are not holding mLock when we post these events.
315 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
316 }
317
318 if (defaultChanged)
319 {
320 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
321 }
322 }
323
324 KCCursor
325 StorageManager::createCursor(SecItemClass itemClass,
326 const SecKeychainAttributeList *attrList)
327 {
328 StLock<Mutex>_(mMutex);
329
330 KeychainList searchList;
331 getSearchList(searchList);
332 return KCCursor(searchList, itemClass, attrList);
333 }
334
335 KCCursor
336 StorageManager::createCursor(const SecKeychainAttributeList *attrList)
337 {
338 StLock<Mutex>_(mMutex);
339
340 KeychainList searchList;
341 getSearchList(searchList);
342 return KCCursor(searchList, attrList);
343 }
344
345 void
346 StorageManager::lockAll()
347 {
348 StLock<Mutex>_(mMutex);
349
350 SecurityServer::ClientSession ss(Allocator::standard(), Allocator::standard());
351 ss.lockAll (false);
352 }
353
354 Keychain
355 StorageManager::defaultKeychain()
356 {
357 StLock<Mutex>_(mMutex);
358
359 Keychain theKeychain;
360 CFTypeRef ref;
361
362 {
363 mSavedList.revert(false);
364 DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
365 if (defaultDLDbIdentifier)
366 {
367 theKeychain = keychain(defaultDLDbIdentifier);
368 ref = theKeychain->handle(false);
369 }
370 }
371
372 if (theKeychain /* && theKeychain->exists() */)
373 return theKeychain;
374
375 MacOSError::throwMe(errSecNoDefaultKeychain);
376 }
377
378 void
379 StorageManager::defaultKeychain(const Keychain &keychain)
380 {
381 StLock<Mutex>_(mMutex);
382
383 // Only set a keychain as the default if we own it and can read/write it,
384 // and our uid allows modifying the directory for that preference domain.
385 if (!keychainOwnerPermissionsValidForDomain(keychain->name(), mDomain))
386 MacOSError::throwMe(errSecWrPerm);
387
388 DLDbIdentifier oldDefaultId;
389 DLDbIdentifier newDefaultId(keychain->dlDbIdentifier());
390 {
391 oldDefaultId = mSavedList.defaultDLDbIdentifier();
392 mSavedList.revert(true);
393 mSavedList.defaultDLDbIdentifier(newDefaultId);
394 mSavedList.save();
395 }
396
397 if (!(oldDefaultId == newDefaultId))
398 {
399 // Make sure we are not holding mLock when we post this event.
400 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
401 }
402 }
403
404 Keychain
405 StorageManager::defaultKeychain(SecPreferencesDomain domain)
406 {
407 StLock<Mutex>_(mMutex);
408
409 if (domain == kSecPreferencesDomainDynamic)
410 MacOSError::throwMe(errSecInvalidPrefsDomain);
411
412 if (domain == mDomain)
413 return defaultKeychain();
414 else
415 {
416 DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
417 if (defaultDLDbIdentifier)
418 return keychain(defaultDLDbIdentifier);
419
420 MacOSError::throwMe(errSecNoDefaultKeychain);
421 }
422 }
423
424 void
425 StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
426 {
427 StLock<Mutex>_(mMutex);
428
429 if (domain == kSecPreferencesDomainDynamic)
430 MacOSError::throwMe(errSecInvalidPrefsDomain);
431
432 if (domain == mDomain)
433 defaultKeychain(keychain);
434 else
435 DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dlDbIdentifier());
436 }
437
438 Keychain
439 StorageManager::loginKeychain()
440 {
441 StLock<Mutex>_(mMutex);
442
443 Keychain theKeychain;
444 {
445 mSavedList.revert(false);
446 DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
447 if (loginDLDbIdentifier)
448 {
449 theKeychain = keychain(loginDLDbIdentifier);
450 }
451 }
452
453 if (theKeychain && theKeychain->exists())
454 return theKeychain;
455
456 MacOSError::throwMe(errSecNoSuchKeychain);
457 }
458
459 void
460 StorageManager::loginKeychain(Keychain keychain)
461 {
462 StLock<Mutex>_(mMutex);
463
464 mSavedList.revert(true);
465 mSavedList.loginDLDbIdentifier(keychain->dlDbIdentifier());
466 mSavedList.save();
467 }
468
469 size_t
470 StorageManager::size()
471 {
472 StLock<Mutex>_(mMutex);
473
474 mSavedList.revert(false);
475 mCommonList.revert(false);
476 return mSavedList.searchList().size() + mCommonList.searchList().size();
477 }
478
479 Keychain
480 StorageManager::at(unsigned int ix)
481 {
482 StLock<Mutex>_(mMutex);
483
484 mSavedList.revert(false);
485 DLDbList dLDbList = mSavedList.searchList();
486 if (ix < dLDbList.size())
487 {
488 return keychain(dLDbList[ix]);
489 }
490 else
491 {
492 ix -= dLDbList.size();
493 mCommonList.revert(false);
494 DLDbList commonList = mCommonList.searchList();
495 if (ix >= commonList.size())
496 MacOSError::throwMe(errSecInvalidKeychain);
497
498 return keychain(commonList[ix]);
499 }
500 }
501
502 Keychain
503 StorageManager::operator[](unsigned int ix)
504 {
505 StLock<Mutex>_(mMutex);
506
507 return at(ix);
508 }
509
510 void StorageManager::rename(Keychain keychain, const char* newName)
511 {
512
513 StLock<Mutex>_(mKeychainMapMutex);
514
515 bool changedDefault = false;
516 DLDbIdentifier newDLDbIdentifier;
517 {
518 mSavedList.revert(true);
519 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
520
521 // Find the keychain object for the given ref
522 DLDbIdentifier dLDbIdentifier = keychain->dlDbIdentifier();
523
524 // Actually rename the database on disk.
525 keychain->database()->rename(newName);
526
527 if (dLDbIdentifier == defaultId)
528 changedDefault=true;
529
530 newDLDbIdentifier = keychain->dlDbIdentifier();
531 // Rename the keychain in the search list.
532 mSavedList.rename(dLDbIdentifier, newDLDbIdentifier);
533
534 // If this was the default keychain change it accordingly
535 if (changedDefault)
536 mSavedList.defaultDLDbIdentifier(newDLDbIdentifier);
537
538 mSavedList.save();
539
540 // we aren't worried about a weak reference here, because we have to
541 // hold a lock on an item in order to do the rename
542
543 // Now update the Keychain cache
544 if (keychain->inCache())
545 {
546 KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
547 if (it != mKeychains.end() && (KeychainImpl*) it->second == keychain.get())
548 {
549 // Remove the keychain from the cache under its old
550 // dLDbIdentifier
551 mKeychains.erase(it);
552 }
553 }
554
555 // If we renamed this keychain on top of an existing one we should
556 // drop the old one from the cache.
557 KeychainMap::iterator it = mKeychains.find(newDLDbIdentifier);
558 if (it != mKeychains.end())
559 {
560 Keychain oldKeychain(it->second);
561 oldKeychain->inCache(false);
562 // @@@ Ideally we should invalidate or fault this keychain object.
563 }
564
565 if (keychain->inCache())
566 {
567 // If the keychain wasn't in the cache to being with let's not put
568 // it there now. There was probably a good reason it wasn't in it.
569 // If the keychain was in the cache, update it to use
570 // newDLDbIdentifier.
571 mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier,
572 keychain));
573 }
574 }
575
576 // Make sure we are not holding mLock when we post these events.
577 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
578
579 if (changedDefault)
580 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent,
581 newDLDbIdentifier);
582 }
583
584 void StorageManager::renameUnique(Keychain keychain, CFStringRef newName)
585 {
586 StLock<Mutex>_(mMutex);
587
588 bool doneCreating = false;
589 int index = 1;
590 do
591 {
592 char newNameCString[MAXPATHLEN];
593 if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
594 {
595 // Construct the new name...
596 //
597 CFMutableStringRef newNameCFStr = NULL;
598 newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN);
599 if ( newNameCFStr )
600 {
601 CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), newNameCString, index);
602 CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix)); // add .keychain
603 char toUseBuff2[MAXPATHLEN];
604 if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
605 {
606 struct stat filebuf;
607 if ( lstat(toUseBuff2, &filebuf) )
608 {
609 rename(keychain, toUseBuff2);
610 KeychainList kcList;
611 kcList.push_back(keychain);
612 remove(kcList, false);
613 doneCreating = true;
614 }
615 else
616 index++;
617 }
618 else
619 doneCreating = true; // failure to get c string.
620 CFRelease(newNameCFStr);
621 }
622 else
623 doneCreating = false; // failure to create mutable string.
624 }
625 else
626 doneCreating = false; // failure to get the string (i.e. > MAXPATHLEN?)
627 }
628 while (!doneCreating && index != INT_MAX);
629 }
630
631 #define KEYCHAIN_SYNC_KEY CFSTR("KeychainSyncList")
632 #define KEYCHAIN_SYNC_DOMAIN CFSTR("com.apple.keychainsync")
633
634 static CFStringRef MakeExpandedPath (const char* path)
635 {
636 std::string name = DLDbListCFPref::ExpandTildesInPath (std::string (path));
637 CFStringRef expanded = CFStringCreateWithCString (NULL, name.c_str (), 0);
638 return expanded;
639 }
640
641 void StorageManager::removeKeychainFromSyncList (const DLDbIdentifier &id)
642 {
643 StLock<Mutex>_(mMutex);
644
645 // make a CFString of our identifier
646 const char* idname = id.dbName ();
647 if (idname == NULL)
648 {
649 return;
650 }
651
652 CFRef<CFStringRef> idString = MakeExpandedPath (idname);
653
654 // check and see if this keychain is in the keychain syncing list
655 CFArrayRef value =
656 (CFArrayRef) CFPreferencesCopyValue (KEYCHAIN_SYNC_KEY,
657 KEYCHAIN_SYNC_DOMAIN,
658 kCFPreferencesCurrentUser,
659 kCFPreferencesAnyHost);
660 if (value == NULL)
661 {
662 return;
663 }
664
665 // make a mutable copy of the dictionary
666 CFRef<CFMutableArrayRef> mtValue = CFArrayCreateMutableCopy (NULL, 0, value);
667 CFRelease (value);
668
669 // walk the array, looking for the value
670 CFIndex i;
671 CFIndex limit = CFArrayGetCount (mtValue.get());
672 bool found = false;
673
674 for (i = 0; i < limit; ++i)
675 {
676 CFDictionaryRef idx = (CFDictionaryRef) CFArrayGetValueAtIndex (mtValue.get(), i);
677 CFStringRef v = (CFStringRef) CFDictionaryGetValue (idx, CFSTR("DbName"));
678 if (v == NULL)
679 {
680 return; // something is really wrong if this is taken
681 }
682
683 char* stringBuffer = NULL;
684 const char* pathString = CFStringGetCStringPtr(v, 0);
685 if (pathString == 0)
686 {
687 CFIndex maxLen = CFStringGetMaximumSizeForEncoding(CFStringGetLength(v), kCFStringEncodingUTF8) + 1;
688 stringBuffer = (char*) malloc(maxLen);
689 CFStringGetCString(v, stringBuffer, maxLen, kCFStringEncodingUTF8);
690 pathString = stringBuffer;
691 }
692
693 CFStringRef vExpanded = MakeExpandedPath(pathString);
694 CFComparisonResult result = CFStringCompare (vExpanded, idString.get(), 0);
695 if (stringBuffer != NULL)
696 {
697 free(stringBuffer);
698 }
699
700 CFRelease (vExpanded);
701
702 if (result == 0)
703 {
704 CFArrayRemoveValueAtIndex (mtValue.get(), i);
705 found = true;
706 break;
707 }
708 }
709
710 if (found)
711 {
712 #ifndef NDEBUG
713 CFShow (mtValue.get());
714 #endif
715
716 CFPreferencesSetValue (KEYCHAIN_SYNC_KEY,
717 mtValue,
718 KEYCHAIN_SYNC_DOMAIN,
719 kCFPreferencesCurrentUser,
720 kCFPreferencesAnyHost);
721 CFPreferencesSynchronize (KEYCHAIN_SYNC_DOMAIN, kCFPreferencesCurrentUser, kCFPreferencesAnyHost);
722 }
723 }
724
725 void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
726 {
727 StLock<Mutex>_(mMutex);
728
729 bool unsetDefault = false;
730 bool updateList = (!isAppSandboxed());
731
732 if (updateList)
733 {
734 mSavedList.revert(true);
735 DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
736 for (KeychainList::const_iterator ix = kcsToRemove.begin();
737 ix != kcsToRemove.end(); ++ix)
738 {
739 // Find the keychain object for the given ref
740 Keychain theKeychain = *ix;
741 DLDbIdentifier dLDbIdentifier = theKeychain->dlDbIdentifier();
742
743 // Remove it from the saved list
744 mSavedList.remove(dLDbIdentifier);
745 if (dLDbIdentifier == defaultId)
746 unsetDefault=true;
747
748 if (deleteDb)
749 {
750 removeKeychainFromSyncList (dLDbIdentifier);
751
752 // Now remove it from the cache
753 removeKeychain(dLDbIdentifier, theKeychain.get());
754 }
755 }
756
757 if (unsetDefault)
758 mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
759
760 mSavedList.save();
761 }
762
763 if (deleteDb)
764 {
765 // Delete the actual databases without holding any locks.
766 for (KeychainList::const_iterator ix = kcsToRemove.begin();
767 ix != kcsToRemove.end(); ++ix)
768 {
769 (*ix)->database()->deleteDb();
770 }
771 }
772
773 if (updateList) {
774 // Make sure we are not holding mLock when we post these events.
775 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
776 }
777
778 if (unsetDefault)
779 KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
780 }
781
782 void
783 StorageManager::getSearchList(KeychainList &keychainList)
784 {
785 // hold the global lock since we make keychain objects in this function
786
787 // to do: each of the items in this list must be retained, otherwise mayhem will occur
788 StLock<Mutex>_(mMutex);
789
790 if (gServerMode) {
791 keychainList.clear();
792 return;
793 }
794
795 mSavedList.revert(false);
796 mCommonList.revert(false);
797
798 // Merge mSavedList, mDynamicList and mCommonList
799 DLDbList dLDbList = mSavedList.searchList();
800 DLDbList dynamicList = mDynamicList.searchList();
801 DLDbList commonList = mCommonList.searchList();
802 KeychainList result;
803 result.reserve(dLDbList.size() + dynamicList.size() + commonList.size());
804
805 {
806 for (DLDbList::const_iterator it = dynamicList.begin();
807 it != dynamicList.end(); ++it)
808 {
809 Keychain k = keychain(*it);
810 result.push_back(k);
811 }
812
813 for (DLDbList::const_iterator it = dLDbList.begin();
814 it != dLDbList.end(); ++it)
815 {
816 Keychain k = keychain(*it);
817 result.push_back(k);
818 }
819
820 for (DLDbList::const_iterator it = commonList.begin();
821 it != commonList.end(); ++it)
822 {
823 Keychain k = keychain(*it);
824 result.push_back(k);
825 }
826 }
827
828 keychainList.swap(result);
829 }
830
831 void
832 StorageManager::setSearchList(const KeychainList &keychainList)
833 {
834 StLock<Mutex>_(mMutex);
835
836 DLDbList commonList = mCommonList.searchList();
837
838 // Strip out the common list part from the end of the search list.
839 KeychainList::const_iterator it_end = keychainList.end();
840 DLDbList::const_reverse_iterator end_common = commonList.rend();
841 for (DLDbList::const_reverse_iterator it_common = commonList.rbegin(); it_common != end_common; ++it_common)
842 {
843 // Eliminate common entries from the end of the passed in keychainList.
844 if (it_end == keychainList.begin())
845 break;
846
847 --it_end;
848 if (!((*it_end)->dlDbIdentifier() == *it_common))
849 {
850 ++it_end;
851 break;
852 }
853 }
854
855 /* it_end now points one past the last element in keychainList which is not in commonList. */
856 DLDbList searchList, oldSearchList(mSavedList.searchList());
857 for (KeychainList::const_iterator it = keychainList.begin(); it != it_end; ++it)
858 {
859 searchList.push_back((*it)->dlDbIdentifier());
860 }
861
862 {
863 // Set the current searchlist to be what was passed in, the old list will be freed
864 // upon exit of this stackframe.
865 mSavedList.revert(true);
866 mSavedList.searchList(searchList);
867 mSavedList.save();
868 }
869
870 if (!(oldSearchList == searchList))
871 {
872 // Make sure we are not holding mLock when we post this event.
873 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
874 }
875 }
876
877 void
878 StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
879 {
880 StLock<Mutex>_(mMutex);
881
882 if (gServerMode) {
883 keychainList.clear();
884 return;
885 }
886
887 if (domain == kSecPreferencesDomainDynamic)
888 {
889 convertList(keychainList, mDynamicList.searchList());
890 }
891 else if (domain == mDomain)
892 {
893 mSavedList.revert(false);
894 convertList(keychainList, mSavedList.searchList());
895 }
896 else
897 {
898 convertList(keychainList, DLDbListCFPref(domain).searchList());
899 }
900 }
901
902 void StorageManager::forceUserSearchListReread()
903 {
904 mSavedList.forceUserSearchListReread();
905 }
906
907 void
908 StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
909 {
910 StLock<Mutex>_(mMutex);
911
912 if (domain == kSecPreferencesDomainDynamic)
913 MacOSError::throwMe(errSecInvalidPrefsDomain);
914
915 DLDbList searchList;
916 convertList(searchList, keychainList);
917
918 if (domain == mDomain)
919 {
920 DLDbList oldSearchList(mSavedList.searchList());
921 {
922 // Set the current searchlist to be what was passed in, the old list will be freed
923 // upon exit of this stackframe.
924 mSavedList.revert(true);
925 mSavedList.searchList(searchList);
926 mSavedList.save();
927 }
928
929 if (!(oldSearchList == searchList))
930 {
931 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
932 }
933 }
934 else
935 {
936 DLDbListCFPref(domain).searchList(searchList);
937 }
938 }
939
940 void
941 StorageManager::domain(SecPreferencesDomain domain)
942 {
943 StLock<Mutex>_(mMutex);
944
945 if (domain == kSecPreferencesDomainDynamic)
946 MacOSError::throwMe(errSecInvalidPrefsDomain);
947
948 if (domain == mDomain)
949 return; // no change
950
951 #if !defined(NDEBUG)
952 switch (domain)
953 {
954 case kSecPreferencesDomainSystem:
955 secdebug("storagemgr", "switching to system domain"); break;
956 case kSecPreferencesDomainUser:
957 secdebug("storagemgr", "switching to user domain (uid %d)", getuid()); break;
958 default:
959 secdebug("storagemgr", "switching to weird prefs domain %d", domain); break;
960 }
961 #endif
962
963 mDomain = domain;
964 mSavedList.set(domain);
965 }
966
967 void
968 StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
969 {
970 StLock<Mutex>_(mMutex);
971
972 if (!keychainOrArray)
973 getSearchList(keychainList);
974 else
975 {
976 CFTypeID typeID = CFGetTypeID(keychainOrArray);
977 if (typeID == CFArrayGetTypeID())
978 convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
979 else if (typeID == gTypes().KeychainImpl.typeID)
980 keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
981 else
982 MacOSError::throwMe(errSecParam);
983 }
984 }
985
986 // static methods.
987 void
988 StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
989 {
990 CFIndex count = CFArrayGetCount(keychainArray);
991 if (!(count > 0))
992 return;
993
994 KeychainList keychains(count);
995 for (CFIndex ix = 0; ix < count; ++ix)
996 {
997 keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
998 }
999
1000 keychainList.swap(keychains);
1001 }
1002
1003 CFArrayRef
1004 StorageManager::convertFromKeychainList(const KeychainList &keychainList)
1005 {
1006 CFRef<CFMutableArrayRef> keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks));
1007
1008 for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix)
1009 {
1010 SecKeychainRef keychainRef = (*ix)->handle();
1011 CFArrayAppendValue(keychainArray, keychainRef);
1012 CFRelease(keychainRef);
1013 }
1014
1015 // Counter the CFRelease that CFRef<> is about to do when keychainArray goes out of scope.
1016 CFRetain(keychainArray);
1017 return keychainArray;
1018 }
1019
1020 void StorageManager::convertList(DLDbList &ids, const KeychainList &kcs)
1021 {
1022 DLDbList result;
1023 result.reserve(kcs.size());
1024 for (KeychainList::const_iterator ix = kcs.begin(); ix != kcs.end(); ++ix)
1025 {
1026 result.push_back((*ix)->dlDbIdentifier());
1027 }
1028 ids.swap(result);
1029 }
1030
1031 void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
1032 {
1033 StLock<Mutex>_(mMutex);
1034
1035 KeychainList result;
1036 result.reserve(ids.size());
1037 {
1038 for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
1039 result.push_back(keychain(*ix));
1040 }
1041 kcs.swap(result);
1042 }
1043
1044 #pragma mark ____ Login Functions ____
1045
1046 void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name)
1047 {
1048 StLock<Mutex>_(mMutex);
1049
1050 AuthorizationItemSet* info = NULL;
1051 OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info); // get the results of the copy rights call.
1052 Boolean created = false;
1053 if ( result == errSecSuccess && info->count )
1054 {
1055 // Grab the password from the auth context (info) and create the keychain...
1056 //
1057 AuthorizationItem* currItem = info->items;
1058 for (UInt32 index = 1; index <= info->count; index++) //@@@plugin bug won't return a specific context.
1059 {
1060 if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
1061 {
1062 // creates the login keychain with the specified password
1063 try
1064 {
1065 login(nameLength, name, (UInt32)currItem->valueLength, currItem->value);
1066 created = true;
1067 }
1068 catch(...)
1069 {
1070 }
1071 break;
1072 }
1073 currItem++;
1074 }
1075 }
1076 if ( info )
1077 AuthorizationFreeItemSet(info);
1078
1079 if ( !created )
1080 MacOSError::throwMe(errAuthorizationInternal);
1081 }
1082
1083 void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
1084 {
1085 StLock<Mutex>_(mMutex);
1086
1087 if ( name == NULL || password == NULL )
1088 MacOSError::throwMe(errSecParam);
1089
1090 login(name[0], name + 1, password[0], password + 1);
1091 }
1092
1093 void StorageManager::login(UInt32 nameLength, const void *name,
1094 UInt32 passwordLength, const void *password)
1095 {
1096 if (passwordLength != 0 && password == NULL)
1097 {
1098 secdebug("KCLogin", "StorageManager::login: invalid argument (NULL password)");
1099 MacOSError::throwMe(errSecParam);
1100 }
1101
1102 DLDbIdentifier loginDLDbIdentifier;
1103 {
1104 mSavedList.revert(true);
1105 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1106 }
1107
1108 secdebug("KCLogin", "StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1109 if (!loginDLDbIdentifier)
1110 MacOSError::throwMe(errSecNoSuchKeychain);
1111
1112
1113 //***************************************************************
1114 // gather keychain information
1115 //***************************************************************
1116
1117 // user name
1118 int uid = geteuid();
1119 struct passwd *pw = getpwuid(uid);
1120 if (pw == NULL) {
1121 secdebug("KCLogin", "StorageManager::login: invalid argument (NULL uid)");
1122 MacOSError::throwMe(errSecParam);
1123 }
1124 char *userName = pw->pw_name;
1125
1126 // make keychain path strings
1127 std::string keychainPath = DLDbListCFPref::ExpandTildesInPath(kLoginKeychainPathPrefix);
1128 std::string shortnameKeychain = keychainPath + userName;
1129 std::string shortnameDotKeychain = shortnameKeychain + ".keychain";
1130 std::string loginDotKeychain = keychainPath + "login.keychain";
1131 std::string loginRenamed1Keychain = keychainPath + "login_renamed1.keychain";
1132
1133 // check for existence of keychain files
1134 bool shortnameKeychainExists = false;
1135 bool shortnameDotKeychainExists = false;
1136 bool loginKeychainExists = false;
1137 bool loginRenamed1KeychainExists = false;
1138 {
1139 struct stat st;
1140 int stat_result;
1141 stat_result = ::stat(shortnameKeychain.c_str(), &st);
1142 shortnameKeychainExists = (stat_result == 0);
1143 stat_result = ::stat(shortnameDotKeychain.c_str(), &st);
1144 shortnameDotKeychainExists = (stat_result == 0);
1145 stat_result = ::stat(loginDotKeychain.c_str(), &st);
1146 loginKeychainExists = (stat_result == 0);
1147 stat_result = ::stat(loginRenamed1Keychain.c_str(), &st);
1148 loginRenamed1KeychainExists = (stat_result == 0);
1149 }
1150
1151 bool loginUnlocked = false;
1152
1153 // make the keychain identifiers
1154 CSSM_VERSION version = {0, 0};
1155 DLDbIdentifier shortnameDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameKeychain.c_str(), NULL);
1156 DLDbIdentifier shortnameDotDLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, shortnameDotKeychain.c_str(), NULL);
1157 DLDbIdentifier loginRenamed1DLDbIdentifier = DLDbListCFPref::makeDLDbIdentifier(gGuidAppleCSPDL, version, 0, CSSM_SERVICE_CSP | CSSM_SERVICE_DL, loginRenamed1Keychain.c_str(), NULL);
1158
1159 //***************************************************************
1160 // make file renaming changes first
1161 //***************************************************************
1162
1163 // if "~/Library/Keychains/shortname" exists, we need to migrate it forward;
1164 // either to login.keychain if there isn't already one, otherwise to shortname.keychain
1165 if (shortnameKeychainExists) {
1166 int rename_stat = 0;
1167 if (loginKeychainExists) {
1168 struct stat st;
1169 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1170 if (tmp_result == 0) {
1171 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1172 tmp_result = ::unlink(loginDotKeychain.c_str());
1173 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1174 shortnameKeychainExists = (rename_stat != 0);
1175 }
1176 }
1177 }
1178 if (shortnameKeychainExists) {
1179 if (loginKeychainExists && !shortnameDotKeychainExists) {
1180 rename_stat = ::rename(shortnameKeychain.c_str(), shortnameDotKeychain.c_str());
1181 shortnameDotKeychainExists = (rename_stat == 0);
1182 } else if (!loginKeychainExists) {
1183 rename_stat = ::rename(shortnameKeychain.c_str(), loginDotKeychain.c_str());
1184 loginKeychainExists = (rename_stat == 0);
1185 } else {
1186 // we have all 3 keychains: login.keychain, shortname, and shortname.keychain.
1187 // on Leopard we never want a shortname keychain, so we must move it aside.
1188 char pathbuf[MAXPATHLEN];
1189 std::string shortnameRenamedXXXKeychain = keychainPath;
1190 shortnameRenamedXXXKeychain += userName;
1191 shortnameRenamedXXXKeychain += "_renamed_XXX.keychain";
1192 ::strlcpy(pathbuf, shortnameRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1193 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1194 rename_stat = ::rename(shortnameKeychain.c_str(), pathbuf);
1195 shortnameKeychainExists = (rename_stat != 0);
1196 }
1197 }
1198 if (rename_stat != 0) {
1199 MacOSError::throwMe(errno);
1200 }
1201 }
1202
1203 //***************************************************************
1204 // handle special case where user previously reset the keychain
1205 //***************************************************************
1206 // Since 9A581, we have changed the definition of kKeychainRenamedSuffix from "_renamed" to "_renamed_".
1207 // Therefore, if "login_renamed1.keychain" exists and there is no plist, the user may have run into a
1208 // prior upgrade issue and clicked Reset. If we can successfully unlock login_renamed1.keychain with the
1209 // supplied password, then we will attempt to rename it to login.keychain if that file is empty, or with
1210 // "shortname.keychain" if it is not.
1211
1212 if (loginRenamed1KeychainExists && (!loginKeychainExists ||
1213 (mSavedList.searchList().size() == 1 && mSavedList.member(loginDLDbIdentifier)) )) {
1214 try
1215 {
1216 Keychain loginRenamed1KC(keychain(loginRenamed1DLDbIdentifier));
1217 secdebug("KCLogin", "Attempting to unlock %s with %d-character password",
1218 (loginRenamed1KC) ? loginRenamed1KC->name() : "<NULL>", (unsigned int)passwordLength);
1219 loginRenamed1KC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1220 // if we get here, we unlocked it
1221 if (loginKeychainExists) {
1222 struct stat st;
1223 int tmp_result = ::stat(loginDotKeychain.c_str(), &st);
1224 if (tmp_result == 0) {
1225 if (st.st_size <= kEmptyKeychainSizeInBytes) {
1226 tmp_result = ::unlink(loginDotKeychain.c_str());
1227 tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1228 } else if (!shortnameDotKeychainExists) {
1229 tmp_result = ::rename(loginRenamed1Keychain.c_str(), shortnameDotKeychain.c_str());
1230 shortnameDotKeychainExists = (tmp_result == 0);
1231 } else {
1232 throw 1; // can't do anything with it except move it out of the way
1233 }
1234 }
1235 } else {
1236 int tmp_result = ::rename(loginRenamed1Keychain.c_str(), loginDotKeychain.c_str());
1237 loginKeychainExists = (tmp_result == 0);
1238 }
1239 }
1240 catch(...)
1241 {
1242 // we failed to unlock the login_renamed1.keychain file with the login password.
1243 // move it aside so we don't try to deal with it again.
1244 char pathbuf[MAXPATHLEN];
1245 std::string loginRenamedXXXKeychain = keychainPath;
1246 loginRenamedXXXKeychain += "login_renamed_XXX.keychain";
1247 ::strlcpy(pathbuf, loginRenamedXXXKeychain.c_str(), sizeof(pathbuf));
1248 ::mkstemps(pathbuf, 9); // 9 == strlen(".keychain")
1249 ::rename(loginRenamed1Keychain.c_str(), pathbuf);
1250 }
1251 }
1252
1253 // if login.keychain does not exist at this point, create it
1254 if (!loginKeychainExists) {
1255 Keychain theKeychain(keychain(loginDLDbIdentifier));
1256 secdebug("KCLogin", "Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1257 theKeychain->create(passwordLength, password);
1258 secdebug("KCLogin", "Login keychain created successfully");
1259 loginKeychainExists = true;
1260 // Set the prefs for this new login keychain.
1261 loginKeychain(theKeychain);
1262 // Login Keychain does not lock on sleep nor lock after timeout by default.
1263 theKeychain->setSettings(INT_MAX, false);
1264 loginUnlocked = true;
1265 mSavedList.revert(true);
1266 }
1267
1268 //***************************************************************
1269 // make plist changes after files have been renamed or created
1270 //***************************************************************
1271
1272 // if the shortname keychain exists in the search list, either rename or remove the entry
1273 if (mSavedList.member(shortnameDLDbIdentifier)) {
1274 if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
1275 // change shortname to shortname.keychain (login.keychain will be added later if not present)
1276 secdebug("KCLogin", "Renaming %s to %s in keychain search list",
1277 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1278 (shortnameDotDLDbIdentifier) ? shortnameDotDLDbIdentifier.dbName() : "<NULL>");
1279 mSavedList.rename(shortnameDLDbIdentifier, shortnameDotDLDbIdentifier);
1280 } else if (!mSavedList.member(loginDLDbIdentifier)) {
1281 // change shortname to login.keychain
1282 secdebug("KCLogin", "Renaming %s to %s in keychain search list",
1283 (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>",
1284 (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1285 mSavedList.rename(shortnameDLDbIdentifier, loginDLDbIdentifier);
1286 } else {
1287 // already have login.keychain in list, and renaming to shortname.keychain isn't an option,
1288 // so just remove the entry
1289 secdebug("KCLogin", "Removing %s from keychain search list", (shortnameDLDbIdentifier) ? shortnameDLDbIdentifier.dbName() : "<NULL>");
1290 mSavedList.remove(shortnameDLDbIdentifier);
1291 }
1292
1293 // note: save() will cause the plist to be unlinked if the only remaining entry is for login.keychain
1294 mSavedList.save();
1295 mSavedList.revert(true);
1296 }
1297
1298 // make sure that login.keychain is in the search list
1299 if (!mSavedList.member(loginDLDbIdentifier)) {
1300 secdebug("KCLogin", "Adding %s to keychain search list", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1301 mSavedList.add(loginDLDbIdentifier);
1302 mSavedList.save();
1303 mSavedList.revert(true);
1304 }
1305
1306 // if we have a shortname.keychain, always include it in the plist (after login.keychain)
1307 if (shortnameDotKeychainExists && !mSavedList.member(shortnameDotDLDbIdentifier)) {
1308 mSavedList.add(shortnameDotDLDbIdentifier);
1309 mSavedList.save();
1310 mSavedList.revert(true);
1311 }
1312
1313 // make sure that the default keychain is in the search list; if not, reset the default to login.keychain
1314 if (!mSavedList.member(mSavedList.defaultDLDbIdentifier())) {
1315 secdebug("KCLogin", "Changing default keychain to %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1316 mSavedList.defaultDLDbIdentifier(loginDLDbIdentifier);
1317 mSavedList.save();
1318 mSavedList.revert(true);
1319 }
1320
1321 //***************************************************************
1322 // auto-unlock the login keychain(s)
1323 //***************************************************************
1324 // all our preflight fixups are finally done, so we can now attempt to unlock the login keychain
1325
1326 OSStatus loginResult = errSecSuccess;
1327 if (!loginUnlocked) {
1328 try
1329 {
1330 Keychain theKeychain(keychain(loginDLDbIdentifier));
1331 secdebug("KCLogin", "Attempting to unlock login keychain \"%s\" with %d-character password",
1332 (theKeychain) ? theKeychain->name() : "<NULL>", (unsigned int)passwordLength);
1333 theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
1334 loginUnlocked = true;
1335 }
1336 catch(const CssmError &e)
1337 {
1338 loginResult = e.osStatus(); // save this result
1339 }
1340 }
1341
1342 if (!loginUnlocked) {
1343 try {
1344 loginResult = errSecSuccess;
1345 Keychain theKeychain(keychain(loginDLDbIdentifier));
1346
1347 // build a fake key
1348 CssmKey key;
1349 key.header().BlobType = CSSM_KEYBLOB_RAW;
1350 key.header().Format = CSSM_KEYBLOB_RAW_FORMAT_OCTET_STRING;
1351 key.header().AlgorithmId = CSSM_ALGID_3DES_3KEY;
1352 key.header().KeyClass = CSSM_KEYCLASS_SESSION_KEY;
1353 key.header().KeyUsage = CSSM_KEYUSE_ENCRYPT | CSSM_KEYUSE_DECRYPT | CSSM_KEYATTR_EXTRACTABLE;
1354 key.header().KeyAttr = 0;
1355 key.KeyData = CssmData(const_cast<void *>(password), passwordLength);
1356
1357 // unwrap it into the CSP (but keep it raw)
1358 UnwrapKey unwrap(theKeychain->csp(), CSSM_ALGID_NONE);
1359 CssmKey masterKey;
1360 CssmData descriptiveData;
1361 unwrap(key,
1362 KeySpec(CSSM_KEYUSE_ANY, CSSM_KEYATTR_EXTRACTABLE),
1363 masterKey, &descriptiveData, NULL);
1364
1365 CssmClient::Db db = theKeychain->database();
1366
1367 // create the keychain, using appropriate credentials
1368 Allocator &alloc = db->allocator();
1369 AutoCredentials cred(alloc); // will leak, but we're quitting soon :-)
1370
1371 // use this passphrase
1372 cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
1373 new(alloc) ListElement(CSSM_SAMPLE_TYPE_SYMMETRIC_KEY),
1374 new(alloc) ListElement(CssmData::wrap(theKeychain->csp()->handle())),
1375 new(alloc) ListElement(CssmData::wrap(masterKey)),
1376 new(alloc) ListElement(CssmData()));
1377 db->authenticate(CSSM_DB_ACCESS_READ, &cred);
1378 db->unlock();
1379 loginUnlocked = true;
1380 } catch (const CssmError &e) {
1381 loginResult = e.osStatus();
1382 }
1383 }
1384
1385 // if "shortname.keychain" exists and is in the search list, attempt to auto-unlock it with the same password
1386 if (shortnameDotKeychainExists && mSavedList.member(shortnameDotDLDbIdentifier)) {
1387 try
1388 {
1389 Keychain shortnameDotKC(keychain(shortnameDotDLDbIdentifier));
1390 secdebug("KCLogin", "Attempting to unlock %s",
1391 (shortnameDotKC) ? shortnameDotKC->name() : "<NULL>");
1392 shortnameDotKC->unlock(CssmData(const_cast<void *>(password), passwordLength));
1393 }
1394 catch(const CssmError &e)
1395 {
1396 // ignore; failure to unlock this keychain is not considered an error
1397 }
1398 }
1399
1400 if (loginResult != errSecSuccess) {
1401 MacOSError::throwMe(loginResult);
1402 }
1403 }
1404
1405 void StorageManager::stashLogin()
1406 {
1407 OSStatus loginResult = errSecSuccess;
1408
1409 DLDbIdentifier loginDLDbIdentifier;
1410 {
1411 mSavedList.revert(true);
1412 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1413 }
1414
1415 secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1416 if (!loginDLDbIdentifier)
1417 MacOSError::throwMe(errSecNoSuchKeychain);
1418
1419 try
1420 {
1421 CssmData empty;
1422 Keychain theKeychain(keychain(loginDLDbIdentifier));
1423 secdebug("KCLogin", "Attempting to use stash for login keychain \"%s\"",
1424 (theKeychain) ? theKeychain->name() : "<NULL>");
1425 theKeychain->stashCheck();
1426 }
1427 catch(const CssmError &e)
1428 {
1429 loginResult = e.osStatus(); // save this result
1430 }
1431
1432
1433 if (loginResult != errSecSuccess) {
1434 MacOSError::throwMe(loginResult);
1435 }
1436 }
1437
1438 void StorageManager::stashKeychain()
1439 {
1440 OSStatus loginResult = errSecSuccess;
1441
1442 DLDbIdentifier loginDLDbIdentifier;
1443 {
1444 mSavedList.revert(true);
1445 loginDLDbIdentifier = mSavedList.loginDLDbIdentifier();
1446 }
1447
1448 secdebug("KCLogin", "StorageManager::stash: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
1449 if (!loginDLDbIdentifier)
1450 MacOSError::throwMe(errSecNoSuchKeychain);
1451
1452 try
1453 {
1454 Keychain theKeychain(keychain(loginDLDbIdentifier));
1455 secdebug("KCLogin", "Attempting to stash login keychain \"%s\"",
1456 (theKeychain) ? theKeychain->name() : "<NULL>");
1457 theKeychain->stash();
1458 }
1459 catch(const CssmError &e)
1460 {
1461 loginResult = e.osStatus(); // save this result
1462 }
1463
1464
1465 if (loginResult != errSecSuccess) {
1466 MacOSError::throwMe(loginResult);
1467 }
1468 }
1469
1470 void StorageManager::logout()
1471 {
1472 // nothing left to do here
1473 }
1474
1475 void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
1476 {
1477 StLock<Mutex>_(mMutex);
1478
1479 loginKeychain()->changePassphrase(oldPassword, newPassword);
1480 secdebug("KClogin", "Changed login keychain password successfully");
1481 }
1482
1483
1484 void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword, UInt32 newPasswordLength, const void *newPassword)
1485 {
1486 StLock<Mutex>_(mMutex);
1487
1488 loginKeychain()->changePassphrase(oldPasswordLength, oldPassword, newPasswordLength, newPassword);
1489 secdebug("KClogin", "Changed login keychain password successfully");
1490 }
1491
1492 // Clear out the keychain search list and rename the existing login.keychain.
1493 //
1494 void StorageManager::resetKeychain(Boolean resetSearchList)
1495 {
1496 StLock<Mutex>_(mMutex);
1497
1498 // Clear the keychain search list.
1499 try
1500 {
1501 if ( resetSearchList )
1502 {
1503 StorageManager::KeychainList keychainList;
1504 setSearchList(keychainList);
1505 }
1506 // Get a reference to the existing login keychain...
1507 // If we don't have one, we throw (not requiring a rename).
1508 //
1509 Keychain keychain = loginKeychain();
1510 //
1511 // Rename the existing login.keychain (i.e. put it aside).
1512 //
1513 CFMutableStringRef newName = NULL;
1514 newName = CFStringCreateMutable(NULL, 0);
1515 CFStringRef currName = NULL;
1516 currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
1517 if ( newName && currName )
1518 {
1519 CFStringAppend(newName, currName);
1520 CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
1521 if ( CFStringHasSuffix(newName, kcSuffix) ) // remove the .keychain extension
1522 {
1523 CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
1524 CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
1525 }
1526 CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix)); // add "_renamed_"
1527 try
1528 {
1529 renameUnique(keychain, newName);
1530 }
1531 catch(...)
1532 {
1533 // we need to release 'newName' & 'currName'
1534 }
1535 } // else, let the login call report a duplicate
1536 if ( newName )
1537 CFRelease(newName);
1538 if ( currName )
1539 CFRelease(currName);
1540 }
1541 catch(...)
1542 {
1543 // We either don't have a login keychain, or there was a
1544 // failure to rename the existing one.
1545 }
1546 }
1547
1548 #pragma mark ____ File Related ____
1549
1550 Keychain StorageManager::make(const char *pathName)
1551 {
1552 return make(pathName, true);
1553 }
1554
1555 Keychain StorageManager::make(const char *pathName, bool add)
1556 {
1557 return makeKeychain(makeDLDbIdentifier(pathName), add);
1558 }
1559
1560 DLDbIdentifier StorageManager::makeDLDbIdentifier(const char *pathName) {
1561 StLock<Mutex>_(mMutex);
1562
1563 string fullPathName;
1564 if ( pathName[0] == '/' )
1565 fullPathName = pathName;
1566 else
1567 {
1568 // Get Home directory from environment.
1569 switch (mDomain)
1570 {
1571 case kSecPreferencesDomainUser:
1572 {
1573 const char *homeDir = getenv("HOME");
1574 if (homeDir == NULL)
1575 {
1576 // If $HOME is unset get the current user's home directory
1577 // from the passwd file.
1578 uid_t uid = geteuid();
1579 if (!uid) uid = getuid();
1580 struct passwd *pw = getpwuid(uid);
1581 if (!pw)
1582 MacOSError::throwMe(errSecParam);
1583 homeDir = pw->pw_dir;
1584 }
1585 fullPathName = homeDir;
1586 }
1587 break;
1588 case kSecPreferencesDomainSystem:
1589 fullPathName = "";
1590 break;
1591 default:
1592 assert(false); // invalid domain for this
1593 }
1594
1595 fullPathName += "/Library/Keychains/";
1596 fullPathName += pathName;
1597 }
1598
1599 const CSSM_NET_ADDRESS *DbLocation = NULL; // NULL for keychains
1600 const CSSM_VERSION *version = NULL;
1601 uint32 subserviceId = 0;
1602 CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP;
1603 const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version,
1604 subserviceId, subserviceType);
1605 DLDbIdentifier dlDbIdentifier(ssuid, fullPathName.c_str(), DbLocation);
1606 return dlDbIdentifier;
1607 }
1608
1609 Keychain StorageManager::makeLoginAuthUI(const Item *item)
1610 {
1611 StLock<Mutex>_(mMutex);
1612
1613 // Create a login/default keychain for the user using UI.
1614 // The user can cancel out of the operation, or create a new login keychain.
1615 // If auto-login is turned off, the user will be asked for their login password.
1616 //
1617 OSStatus result = errSecSuccess;
1618 Keychain keychain; // We return this keychain.
1619 //
1620 // Set up the Auth ref to bring up UI.
1621 //
1622 AuthorizationItem *currItem, *authEnvirItemArrayPtr = NULL;
1623 AuthorizationRef authRef = NULL;
1624 try
1625 {
1626 result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
1627 if ( result )
1628 MacOSError::throwMe(result);
1629
1630 AuthorizationEnvironment envir;
1631 envir.count = 6; // up to 6 hints can be used.
1632 authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
1633 if ( !authEnvirItemArrayPtr )
1634 MacOSError::throwMe(errAuthorizationInternal);
1635
1636 currItem = envir.items = authEnvirItemArrayPtr;
1637
1638 //
1639 // 1st Hint (optional): The keychain item's account attribute string.
1640 // When item is specified, we assume an 'add' operation is being attempted.
1641 char buff[256];
1642 UInt32 actLen = 0;
1643 SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
1644 if ( item )
1645 {
1646 try
1647 {
1648 (*item)->getAttribute(attr, &actLen);
1649 }
1650 catch(...)
1651 {
1652 actLen = 0; // This item didn't have the account attribute, so don't display one in the UI.
1653 }
1654 }
1655 currItem->name = AGENT_HINT_ATTR_NAME; // name str that identifies this hint as attr name
1656 if ( actLen ) // Fill in the hint if we have an account attr
1657 {
1658 if ( actLen >= sizeof(buff) )
1659 buff[sizeof(buff)-1] = 0;
1660 else
1661 buff[actLen] = 0;
1662 currItem->valueLength = strlen(buff)+1;
1663 currItem->value = buff;
1664 }
1665 else
1666 {
1667 currItem->valueLength = 0;
1668 currItem->value = NULL;
1669 }
1670 currItem->flags = 0;
1671
1672 //
1673 // 2nd Hint (optional): The item's keychain full path.
1674 //
1675 currItem++;
1676 char* currDefaultName = NULL;
1677 try
1678 {
1679 currDefaultName = (char*)defaultKeychain()->name(); // Use the name if we have it.
1680 currItem->name = AGENT_HINT_LOGIN_KC_NAME; // Name str that identifies this hint as kc path
1681 currItem->valueLength = (currDefaultName) ? strlen(currDefaultName) : 0;
1682 currItem->value = (currDefaultName) ? (void*)currDefaultName : (void*)"";
1683 currItem->flags = 0;
1684 currItem++;
1685 }
1686 catch(...)
1687 {
1688 envir.count--;
1689 }
1690
1691 //
1692 // 3rd Hint (required): check if curr default keychain is unavailable.
1693 // This is determined by the parent not existing.
1694 //
1695 currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
1696 Boolean loginUnavail = false;
1697 try
1698 {
1699 Keychain defaultKC = defaultKeychain();
1700 if ( !defaultKC->exists() )
1701 loginUnavail = true;
1702 }
1703 catch(...) // login.keychain not present
1704 {
1705 }
1706 currItem->valueLength = sizeof(Boolean);
1707 currItem->value = (void*)&loginUnavail;
1708 currItem->flags = 0;
1709
1710 //
1711 // 4th Hint (required): userName
1712 //
1713 currItem++;
1714 currItem->name = AGENT_HINT_LOGIN_KC_USER_NAME;
1715 char* uName = getenv("USER");
1716 string userName = uName ? uName : "";
1717 if ( userName.length() == 0 )
1718 {
1719 uid_t uid = geteuid();
1720 if (!uid) uid = getuid();
1721 struct passwd *pw = getpwuid(uid); // fallback case...
1722 if (pw)
1723 userName = pw->pw_name;
1724 endpwent();
1725 }
1726 if ( userName.length() == 0 ) // did we ultimately get one?
1727 MacOSError::throwMe(errAuthorizationInternal);
1728
1729 currItem->value = (void*)userName.c_str();
1730 currItem->valueLength = userName.length();
1731 currItem->flags = 0;
1732
1733 //
1734 // 5th Hint (required): flags if user has more than 1 keychain (used for a later warning when reset to default).
1735 //
1736 currItem++;
1737 currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
1738 Boolean moreThanOneKCExists = false;
1739 {
1740 // if item is NULL, then this is a user-initiated full reset
1741 if (item && mSavedList.searchList().size() > 1)
1742 moreThanOneKCExists = true;
1743 }
1744 currItem->value = &moreThanOneKCExists;
1745 currItem->valueLength = sizeof(Boolean);
1746 currItem->flags = 0;
1747
1748 //
1749 // 6th Hint (required): If no item is involved, this is a user-initiated full reset.
1750 // We want to suppress the "do you want to reset to defaults?" panel in this case.
1751 //
1752 currItem++;
1753 currItem->name = AGENT_HINT_LOGIN_KC_SUPPRESS_RESET_PANEL;
1754 Boolean suppressResetPanel = (item == NULL) ? TRUE : FALSE;
1755 currItem->valueLength = sizeof(Boolean);
1756 currItem->value = (void*)&suppressResetPanel;
1757 currItem->flags = 0;
1758
1759 //
1760 // Set up the auth rights and make the auth call.
1761 //
1762 AuthorizationItem authItem = { LOGIN_KC_CREATION_RIGHT, 0 , NULL, 0 };
1763 AuthorizationRights rights = { 1, &authItem };
1764 AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
1765 result = AuthorizationCopyRights(authRef, &rights, &envir, flags, NULL);
1766 if ( result )
1767 MacOSError::throwMe(result);
1768 try
1769 {
1770 resetKeychain(true); // Clears the plist, moves aside existing login.keychain
1771 }
1772 catch (...) // can throw if no existing login.keychain is found
1773 {
1774 }
1775 login(authRef, (UInt32)userName.length(), userName.c_str()); // Create login.keychain
1776 keychain = loginKeychain(); // Get newly-created login keychain
1777 defaultKeychain(keychain); // Set it to be the default
1778
1779 free(authEnvirItemArrayPtr);
1780 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
1781 }
1782
1783 catch (...)
1784 {
1785 // clean up allocations, then rethrow error
1786 if ( authEnvirItemArrayPtr )
1787 free(authEnvirItemArrayPtr);
1788 if ( authRef )
1789 AuthorizationFree(authRef, kAuthorizationFlagDefaults);
1790 throw;
1791 }
1792
1793 return keychain;
1794 }
1795
1796 Keychain StorageManager::defaultKeychainUI(Item &item)
1797 {
1798 StLock<Mutex>_(mMutex);
1799
1800 Keychain returnedKeychain;
1801 try
1802 {
1803 returnedKeychain = defaultKeychain(); // If we have one, return it.
1804 if ( returnedKeychain->exists() )
1805 return returnedKeychain;
1806 }
1807 catch(...) // We could have one, but it isn't available (i.e. on a un-mounted volume).
1808 {
1809 }
1810 if ( globals().getUserInteractionAllowed() )
1811 {
1812 returnedKeychain = makeLoginAuthUI(&item); // If no Keychains is present, one will be created.
1813 if ( !returnedKeychain )
1814 MacOSError::throwMe(errSecInvalidKeychain); // Something went wrong...
1815 }
1816 else
1817 MacOSError::throwMe(errSecInteractionNotAllowed); // If UI isn't allowed, return an error.
1818
1819 return returnedKeychain;
1820 }
1821
1822 void
1823 StorageManager::addToDomainList(SecPreferencesDomain domain,
1824 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1825 {
1826 StLock<Mutex>_(mMutex);
1827
1828 if (domain == kSecPreferencesDomainDynamic)
1829 MacOSError::throwMe(errSecInvalidPrefsDomain);
1830
1831 // make the identifier
1832 CSSM_VERSION version = {0, 0};
1833 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1834 subServiceType, dbName, NULL);
1835
1836 if (domain == mDomain)
1837 {
1838 // manipulate the user's list
1839 {
1840 mSavedList.revert(true);
1841 mSavedList.add(id);
1842 mSavedList.save();
1843 }
1844
1845 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1846 }
1847 else
1848 {
1849 // manipulate the other list
1850 DLDbListCFPref(domain).add(id);
1851 }
1852 }
1853
1854 void
1855 StorageManager::isInDomainList(SecPreferencesDomain domain,
1856 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1857 {
1858 StLock<Mutex>_(mMutex);
1859
1860 if (domain == kSecPreferencesDomainDynamic)
1861 MacOSError::throwMe(errSecInvalidPrefsDomain);
1862
1863 CSSM_VERSION version = {0, 0};
1864 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1865 subServiceType, dbName, NULL);
1866
1867 // determine the list to search
1868 bool result;
1869 if (domain == mDomain)
1870 {
1871 result = mSavedList.member(id);
1872 }
1873 else
1874 {
1875 result = DLDbListCFPref(domain).member(id);
1876 }
1877
1878 // do the search
1879 if (!result)
1880 {
1881 MacOSError::throwMe(errSecNoSuchKeychain);
1882 }
1883 }
1884
1885 void
1886 StorageManager::removeFromDomainList(SecPreferencesDomain domain,
1887 const char* dbName, const CSSM_GUID &guid, uint32 subServiceType)
1888 {
1889 StLock<Mutex>_(mMutex);
1890
1891 if (domain == kSecPreferencesDomainDynamic)
1892 MacOSError::throwMe(errSecInvalidPrefsDomain);
1893
1894 // make the identifier
1895 CSSM_VERSION version = {0, 0};
1896 DLDbIdentifier id = DLDbListCFPref::makeDLDbIdentifier (guid, version, 0,
1897 subServiceType, dbName, NULL);
1898
1899 if (domain == mDomain)
1900 {
1901 // manipulate the user's list
1902 {
1903 mSavedList.revert(true);
1904 mSavedList.remove(id);
1905 mSavedList.save();
1906 }
1907
1908 KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
1909 }
1910 else
1911 {
1912 // manipulate the other list
1913 DLDbListCFPref(domain).remove(id);
1914 }
1915 }
1916
1917 bool
1918 StorageManager::keychainOwnerPermissionsValidForDomain(const char* path, SecPreferencesDomain domain)
1919 {
1920 struct stat sb;
1921 mode_t perms;
1922 const char* sysPrefDir = "/Library/Preferences";
1923 const char* errMsg = "Will not set default";
1924 char* mustOwnDir = NULL;
1925 struct passwd* pw = NULL;
1926
1927 // get my uid
1928 uid_t uid = geteuid();
1929 if (!uid) uid = getuid();
1930
1931 // our (e)uid must own the appropriate preferences or home directory
1932 // for the specified preference domain whose default we will be modifying
1933 switch (domain) {
1934 case kSecPreferencesDomainUser:
1935 mustOwnDir = getenv("HOME");
1936 if (mustOwnDir == NULL) {
1937 pw = getpwuid(uid);
1938 if (!pw) return false;
1939 mustOwnDir = pw->pw_dir;
1940 }
1941 break;
1942 case kSecPreferencesDomainSystem:
1943 mustOwnDir = (char*)sysPrefDir;
1944 break;
1945 case kSecPreferencesDomainCommon:
1946 mustOwnDir = (char*)sysPrefDir;
1947 break;
1948 default:
1949 return false;
1950 }
1951
1952 if (mustOwnDir != NULL) {
1953 struct stat dsb;
1954 if ( (stat(mustOwnDir, &dsb) != 0) || (dsb.st_uid != uid) ) {
1955 fprintf(stderr, "%s: UID=%d does not own directory %s\n", errMsg, (int)uid, mustOwnDir);
1956 mustOwnDir = NULL; // will return below after calling endpwent()
1957 }
1958 }
1959
1960 if (pw != NULL)
1961 endpwent();
1962
1963 if (mustOwnDir == NULL)
1964 return false;
1965
1966 // check that file actually exists
1967 if (stat(path, &sb) != 0) {
1968 fprintf(stderr, "%s: file %s does not exist\n", errMsg, path);
1969 return false;
1970 }
1971
1972 // check flags
1973 if (sb.st_flags & (SF_IMMUTABLE | UF_IMMUTABLE)) {
1974 fprintf(stderr, "%s: file %s is immutable\n", errMsg, path);
1975 return false;
1976 }
1977
1978 // check ownership
1979 if (sb.st_uid != uid) {
1980 fprintf(stderr, "%s: file %s is owned by UID=%d, but we have UID=%d\n",
1981 errMsg, path, (int)sb.st_uid, (int)uid);
1982 return false;
1983 }
1984
1985 // check mode
1986 perms = sb.st_mode;
1987 perms |= 0600; // must have owner read/write permission set
1988 if (sb.st_mode != perms) {
1989 fprintf(stderr, "%s: file %s does not have the expected permissions\n", errMsg, path);
1990 return false;
1991 }
1992
1993 // user owns file and can read/write it
1994 return true;
1995 }