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