]> git.saurik.com Git - apple/security.git/blobdiff - Keychain/StorageManager.cpp
Security-164.1.tar.gz
[apple/security.git] / Keychain / StorageManager.cpp
index 78194a43d07d0fb319d84ee5b430d6246ce7c2f7..8a1a72df55235807ef4df97c7a5390b252d688a6 100644 (file)
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
+ * Copyright (c) 2000-2002 Apple Computer, Inc. All Rights Reserved.
  * 
  * The contents of this file constitute Original Code as defined in and are
  * subject to the Apple Public Source License Version 1.2 (the 'License').
@@ -21,9 +21,6 @@
 
        Contains:       Working with multiple keychains
 
-       Copyright:      2000 by Apple Computer, Inc., all rights reserved.
-
-       To Do:
 */
 
 #include "StorageManager.h"
 
 #include <Security/cssmapple.h>
 #include <sys/types.h>
+#include <sys/param.h>
+#include <syslog.h>
 #include <pwd.h>
 #include <CoreServices/../Frameworks/CarbonCore.framework/Headers/MacErrors.h>
 #include <algorithm>
 #include <string>
-#include <Security/Authorization.h>
 #include <Security/AuthorizationTags.h>
 #include <Security/AuthSession.h>
 #include <Security/debugging.h>
+#include <Security/SecCFTypes.h>
+#include <Security/AuthSession.h>
+#include <Security/SecurityAgentClient.h>
+#include <Security/ssclient.h>
 
 #include "KCCursor.h"
 #include "Globals.h"
-#include "DefaultKeychain.h"
 
 using namespace CssmClient;
 using namespace KeychainCore;
 
+// normal debug calls, which get stubbed out for deployment builds
+#define x_debug(str) secdebug("KClogin",(str))
+#define x_debug1(fmt,arg1) secdebug("KClogin",(fmt),(arg1))
+#define x_debug2(fmt,arg1,arg2) secdebug("KClogin",(fmt),(arg1),(arg2))
+
+//-----------------------------------------------------------------------------------
+
 StorageManager::StorageManager() :
-    mSavedList(),
-    mKeychains(),
-    mMultiDLDb(mSavedList.list(), true) // Passinng true enables use of Secure Storage
+       mSavedList(kSecPreferencesDomainUser),
+       mCommonList(kSecPreferencesDomainCommon),
+       mDomain(kSecPreferencesDomainUser),
+    mKeychains()
 {
+       // get session attributes
+       SessionAttributeBits sessionAttrs;
+       if (OSStatus err = SessionGetInfo(callerSecuritySession,
+               NULL, &sessionAttrs))
+                       CssmError::throwMe(err);
+       
+       // If this is the root session, switch to system preferences.
+       // (In SecurityServer debug mode, you'll get a (fake) root session
+       // that has graphics access. Ignore that to help testing.)
+       if ((sessionAttrs & sessionIsRoot)
+                       IFDEBUG( && !(sessionAttrs & sessionHasGraphicAccess))) {
+               secdebug("storagemgr", "switching to system preferences");
+               mDomain = kSecPreferencesDomainSystem;
+               mSavedList.set(kSecPreferencesDomainSystem);
+       }
 }
 
 // Create KC if it doesn't exist       
 Keychain
 StorageManager::keychain(const DLDbIdentifier &dLDbIdentifier)
 {
-       //StLock<Mutex> _(mKeychainsLock);
+       StLock<Mutex> _(mLock);
+       return _keychain(dLDbIdentifier);
+}
+
+Keychain
+StorageManager::_keychain(const DLDbIdentifier &dLDbIdentifier)
+{
+       if (!dLDbIdentifier)
+               return Keychain();
+
     KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
     if (it != mKeychains.end())
                return it->second;
 
        // The keychain is not in our cache.  Create it.
-       Keychain keychain(mMultiDLDb->database(dLDbIdentifier));
+       Module module(dLDbIdentifier.ssuid().guid());
+       DL dl;
+       if (dLDbIdentifier.ssuid().subserviceType() & CSSM_SERVICE_CSP)
+               dl = SSCSPDL(module);
+       else
+               dl = DL(module);
+
+       dl->subserviceId(dLDbIdentifier.ssuid().subserviceId());
+       dl->version(dLDbIdentifier.ssuid().version());
+       Db db(dl, dLDbIdentifier.dbName());
 
+       Keychain keychain(db);
        // Add the keychain to the cache.
-       mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, keychain));
+       mKeychains.insert(KeychainMap::value_type(dLDbIdentifier, &*keychain));
+
        return keychain;
 }
 
-// Create KC if it doesn't exist       
-Keychain
-StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier)
+// Called from KeychainImpl's destructor remove it from the map.
+void 
+StorageManager::removeKeychain(const DLDbIdentifier &dLDbIdentifier, KeychainImpl *keychainImpl)
 {
-       Keychain keychain(keychain(dLDbIdentifier));
+       // @@@ Work out locking StLock<Mutex> _(mLock);
+       KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+       if (it != mKeychains.end() && it->second == keychainImpl)
+               mKeychains.erase(it);
+}
 
-       const vector<DLDbIdentifier> &list = mMultiDLDb->list();
-       if (find(list.begin(), list.end(), dLDbIdentifier) != list.end())
-       {
-               // The dLDbIdentifier for this keychain is already on our search list.
-               return keychain;
+// if a database is key-unlockable, authenticate it with any matching unlock keys found in the KC list
+void StorageManager::setDefaultCredentials(const Db &db)
+{
+       try {
+               CssmAutoData index(db->allocator());
+               if (!db->getUnlockKeyIndex(index.get()))
+                       return;         // no suggested index (probably not a CSPDL)
+       
+               TrackingAllocator alloc(CssmAllocator::standard());
+       
+               KCCursor search(createCursor(CSSM_DL_DB_RECORD_SYMMETRIC_KEY, NULL));
+               CssmAutoData keyLabel(CssmAllocator::standard());
+               keyLabel = StringData("SYSKC**");
+               keyLabel.append(index);
+               static const CSSM_DB_ATTRIBUTE_INFO infoLabel = {
+                       CSSM_DB_ATTRIBUTE_NAME_AS_STRING,
+                       {"Label"},
+                       CSSM_DB_ATTRIBUTE_FORMAT_BLOB
+               };
+               search->add(CSSM_DB_EQUAL, infoLabel, keyLabel.get());
+       
+               // could run a loop below to catch *all* eligible keys,
+               // but that's stretching it; and beware CSP scope if you add this...
+               AutoCredentials cred(alloc);
+               Item keyItem;
+               if (search->next(keyItem)) {
+                       CssmClient::Key key = dynamic_cast<KeyItem &>(*keyItem).key();
+       
+                       // create AccessCredentials from that key. Still allow interactive unlock
+                       const CssmKey &masterKey = key;
+                       CSSM_CSP_HANDLE cspHandle = key->csp()->handle();
+                       cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
+                               new(alloc) ListElement(CSSM_WORDID_SYMMETRIC_KEY),
+                               new(alloc) ListElement(CssmData::wrap(cspHandle)),
+                               new(alloc) ListElement(CssmData::wrap(masterKey)));
+                       cred += TypedList(alloc, CSSM_SAMPLE_TYPE_KEYCHAIN_LOCK,
+                               new(alloc) ListElement(CSSM_SAMPLE_TYPE_KEYCHAIN_PROMPT));
+       
+                       secdebug("storagemgr", "authenticating %s for default key credentials", db->name());
+                       db->authenticate(db->accessRequest(), &cred);
+               }
+       } catch (...) {
+               secdebug("storagemgr", "setDefaultCredentials for %s abandoned due to exception", db->name());
        }
+}
 
-       // If the keychain doesn't exist don't bother adding it to the search list yet.
-       if (!keychain->exists())
-               return keychain;
+// Create KC if it doesn't exist, add it to the search list if it exists and is not already on it.
+Keychain
+StorageManager::makeKeychain(const DLDbIdentifier &dLDbIdentifier, bool add)
+{
+       Keychain keychain;
+       bool post = false;
 
-       // The keychain exists and is not in our search list add it to the search
-       // list and the cache.  Then inform mMultiDLDb.
-       mSavedList.revert(true);
-       mSavedList.add(dLDbIdentifier);
-       mSavedList.save();
+       {
+               StLock<Mutex> _(mLock);
+               keychain = _keychain(dLDbIdentifier);
 
-       // @@@ Will happen again when kSecKeychainListChangedEvent notification is received.
-       mMultiDLDb->list(mSavedList.list());
+               if (add)
+               {
+                       mSavedList.revert(false);
+                       DLDbList searchList = mSavedList.searchList();
+                       if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
+                               return keychain;  // Keychain is already in the searchList.
 
-       KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+                       mCommonList.revert(false);
+                       searchList = mCommonList.searchList();
+                       if (find(searchList.begin(), searchList.end(), dLDbIdentifier) != searchList.end())
+                               return keychain;  // Keychain is already in the commonList don't add it to the searchList.
+               
+                       // If the keychain doesn't exist don't bother adding it to the search list yet.
+                       if (!keychain->exists())
+                               return keychain;
+               
+                       // The keychain exists and is not in our search list add it to the search
+                       // list and the cache.
+                       mSavedList.revert(true);
+                       mSavedList.add(dLDbIdentifier);
+                       mSavedList.save();
+                       post = true;
+               }
+       }
+
+       if (post)
+       {
+               // Make sure we are not holding mLock when we post this event.
+               KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+       }
 
        return keychain;
 }
@@ -106,70 +220,178 @@ void
 StorageManager::created(const Keychain &keychain) // Be notified a Keychain just got created.
 {
     DLDbIdentifier dLDbIdentifier = keychain->dLDbIdentifier();
-       
-    // If we don't have a default Keychain yet.  Make the newly created keychain the default.
-    DefaultKeychain &defaultKeychain = globals().defaultKeychain;
-    if (!defaultKeychain.isSet())
-        defaultKeychain.dLDbIdentifier(dLDbIdentifier);
+       bool defaultChanged = false;
 
-       // Add the keychain to the search list and the cache.  Then inform mMultiDLDb.
-       mSavedList.revert(true);
-       mSavedList.add(dLDbIdentifier);
-       mSavedList.save();
+       {
+               StLock<Mutex> _(mLock);
+
+               mSavedList.revert(true);
+               // If we don't have a default Keychain yet.  Make the newly created keychain the default.
+               if (!mSavedList.defaultDLDbIdentifier())
+               {
+                       mSavedList.defaultDLDbIdentifier(dLDbIdentifier);
+                       defaultChanged = true;
+               }
 
-       // @@@ Will happen again when kSecKeychainListChangedEvent notification is received.
-       mMultiDLDb->list(mSavedList.list());
+               // Add the keychain to the search list prefs.
+               mSavedList.add(dLDbIdentifier);
+               mSavedList.save();
+       }
 
+       // Make sure we are not holding mLock when we post these events.
        KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
-}
 
+       if (defaultChanged)
+       {
+               KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, dLDbIdentifier);
+       }
+}
 
 KCCursor
 StorageManager::createCursor(SecItemClass itemClass, const SecKeychainAttributeList *attrList)
 {
-       return KCCursor(DbCursor(mMultiDLDb), itemClass, attrList);
+       KeychainList searchList;
+       getSearchList(searchList);
+       return KCCursor(searchList, itemClass, attrList);
 }
 
 KCCursor
 StorageManager::createCursor(const SecKeychainAttributeList *attrList)
 {
-       return KCCursor(DbCursor(mMultiDLDb), attrList);
+       KeychainList searchList;
+       getSearchList(searchList);
+       return KCCursor(searchList, attrList);
 }
 
 void
 StorageManager::lockAll()
 {
-    for (KeychainMap::iterator ix = mKeychains.begin(); ix != mKeychains.end(); ix++)
+    SecurityServer::ClientSession ss(CssmAllocator::standard(), CssmAllocator::standard());
+    ss.lockAll (false);
+}
+
+Keychain
+StorageManager::defaultKeychain()
+{
+       Keychain theKeychain;
        {
-               Keychain keychain(ix->second);
-               if (keychain->isActive())
-                       keychain->lock();
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(false);
+               DLDbIdentifier defaultDLDbIdentifier(mSavedList.defaultDLDbIdentifier());
+               if (defaultDLDbIdentifier)
+               {
+                       theKeychain = _keychain(defaultDLDbIdentifier);
+               }
        }
+
+       if (theKeychain /* && theKeychain->exists() */)
+               return theKeychain;
+
+       MacOSError::throwMe(errSecNoDefaultKeychain);
 }
 
 void
-StorageManager::reload(bool force)
+StorageManager::defaultKeychain(const Keychain &keychain)
 {
-    // Reinitialize list from CFPrefs if changed.  When force is true force a prefs revert now.
-    if (mSavedList.revert(force))
-        mMultiDLDb->list(mSavedList.list());
+       DLDbIdentifier oldDefaultId;
+       DLDbIdentifier newDefaultId(keychain->dLDbIdentifier());
+       {
+               StLock<Mutex> _(mLock);
+               oldDefaultId = mSavedList.defaultDLDbIdentifier();
+               mSavedList.revert(true);
+               mSavedList.defaultDLDbIdentifier(newDefaultId);
+               mSavedList.save();
+       }
+
+       if (!(oldDefaultId == newDefaultId))
+       {
+               // Make sure we are not holding mLock when we post this event.
+               KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDefaultId);
+       }
+}
+
+Keychain
+StorageManager::defaultKeychain(SecPreferencesDomain domain)
+{
+       if (domain == mDomain)
+               return defaultKeychain();
+       else
+       {
+               DLDbIdentifier defaultDLDbIdentifier(DLDbListCFPref(domain).defaultDLDbIdentifier());
+               if (defaultDLDbIdentifier)
+                       return keychain(defaultDLDbIdentifier);
+
+               MacOSError::throwMe(errSecNoDefaultKeychain);
+       }
+}
+
+void
+StorageManager::defaultKeychain(SecPreferencesDomain domain, const Keychain &keychain)
+{
+       if (domain == mDomain)
+               defaultKeychain(keychain);
+       else
+               DLDbListCFPref(domain).defaultDLDbIdentifier(keychain->dLDbIdentifier());
+}
+
+Keychain
+StorageManager::loginKeychain()
+{
+       Keychain theKeychain;
+       {
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(false);
+               DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
+               if (loginDLDbIdentifier)
+               {
+                       theKeychain = _keychain(loginDLDbIdentifier);
+               }
+       }
+
+       if (theKeychain && theKeychain->exists())
+               return theKeychain;
+
+       MacOSError::throwMe(errSecNoSuchKeychain);
+}
+
+void
+StorageManager::loginKeychain(Keychain keychain)
+{
+       StLock<Mutex> _(mLock);
+       mSavedList.revert(true);
+       mSavedList.loginDLDbIdentifier(keychain->dLDbIdentifier());
+       mSavedList.save();
 }
 
 size_t
 StorageManager::size()
 {
-    reload();
-    return mMultiDLDb->list().size();
+       StLock<Mutex> _(mLock);
+    mSavedList.revert(false);
+       mCommonList.revert(false);
+       return mSavedList.searchList().size() + mCommonList.searchList().size();
 }
 
 Keychain
 StorageManager::at(unsigned int ix)
 {
-    reload();
-    if (ix >= mMultiDLDb->list().size())
-        MacOSError::throwMe(errSecInvalidKeychain);
+       StLock<Mutex> _(mLock);
+       mSavedList.revert(false);
+       DLDbList dLDbList = mSavedList.searchList();
+       if (ix < dLDbList.size())
+       {
+               return _keychain(dLDbList[ix]);
+       }
+       else
+       {
+               ix -= dLDbList.size();
+               mCommonList.revert(false);
+               DLDbList commonList = mCommonList.searchList();
+               if (ix >= commonList.size())
+                       MacOSError::throwMe(errSecInvalidKeychain);
 
-    return keychain(mMultiDLDb->list().at(ix));
+               return _keychain(commonList[ix]);
+       }
 }
 
 Keychain
@@ -178,78 +400,390 @@ StorageManager::operator[](unsigned int ix)
     return at(ix);
 }      
 
-void StorageManager::remove(const list<SecKeychainRef>& kcsToRemove)
+void StorageManager::rename(Keychain keychain, const char* newName)
 {
-       //StLock<Mutex> _(mKeychainsLock);
-       mSavedList.revert(true);
-       DLDbIdentifier defaultId = globals().defaultKeychain.dLDbIdentifier();
-       bool unsetDefault=false;
-    for (list<SecKeychainRef>::const_iterator ix = kcsToRemove.begin();ix!=kcsToRemove.end();ix++)
+       // This is not a generic purpose rename method for keychains.
+    // The keychain doesn't remain in the cache.
+    //
+    bool changedDefault = false;
+       DLDbIdentifier newDLDbIdentifier;
        {
-               // Find the keychain object for the given ref
-               Keychain keychainToRemove;
-               try
-               {
-                       keychainToRemove = KeychainRef::required(*ix);
-               }
-               catch (const MacOSError& err)
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(true);
+               DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
+
+        // Find the keychain object for the given ref
+        DLDbIdentifier dLDbIdentifier = keychain->dLDbIdentifier();
+
+        // Remove it from the saved list
+        mSavedList.remove(dLDbIdentifier);
+        if (dLDbIdentifier == defaultId)
+            changedDefault=true;
+
+               // Actually rename the database on disk.
+        keychain->database()->rename(newName);
+
+               newDLDbIdentifier = keychain->dLDbIdentifier();
+
+        // Now update the keychain map to use the newDLDbIdentifier 
+        KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+        if (it != mKeychains.end())
+        {
+            mKeychains.erase(it);
+            mKeychains.insert(KeychainMap::value_type(newDLDbIdentifier, keychain));
+        }
+
+               // If this was the default keychain change it accordingly
+               if (changedDefault)
+                       mSavedList.defaultDLDbIdentifier(newDLDbIdentifier);
+
+               mSavedList.save();
+       }
+
+       // @@@ We need a kSecKeychainRenamedEvent so other clients can close this keychain and move on with life.
+       //KCEventNotifier::PostKeychainEvent(kSecKeychainRenamedEvent);
+
+       // Make sure we are not holding mLock when we post these events.
+       KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+
+       if (changedDefault)
+               KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent, newDLDbIdentifier);
+}
+
+void StorageManager::renameUnique(Keychain keychain, CFStringRef newName)
+{
+    bool doneCreating = false;
+    int index = 1;
+    do
+    {
+        char newNameCString[MAXPATHLEN];
+        if ( CFStringGetCString(newName, newNameCString, MAXPATHLEN, kCFStringEncodingUTF8) )  // make sure it fits in MAXPATHLEN, etc.
+        {
+            // Construct the new name...
+            //
+            CFMutableStringRef newNameCFStr = NULL;
+            newNameCFStr = CFStringCreateMutable(NULL, MAXPATHLEN); 
+            if ( newNameCFStr )
+            {
+                CFStringAppendFormat(newNameCFStr, NULL, CFSTR("%s%d"), &newNameCString, index);
+                CFStringAppend(newNameCFStr, CFSTR(kKeychainSuffix));  // add .keychain
+                char toUseBuff2[MAXPATHLEN];
+                if ( CFStringGetCString(newNameCFStr, toUseBuff2, MAXPATHLEN, kCFStringEncodingUTF8) ) // make sure it fits in MAXPATHLEN, etc.
+                {
+                    struct stat filebuf;
+                    if ( lstat(toUseBuff2, &filebuf) )
+                    {
+                        rename(keychain, toUseBuff2);
+                        doneCreating = true;
+                    }
+                    else
+                        index++;
+                }
+                else
+                    doneCreating = true;       // failure to get c string.
+                CFRelease(newNameCFStr);
+            }
+            else
+                doneCreating = false; // failure to create mutable string.
+        }
+        else
+            doneCreating = false; // failure to get the string (i.e. > MAXPATHLEN?)
+    }
+    while (!doneCreating && index != INT_MAX);
+}
+
+void StorageManager::remove(const KeychainList &kcsToRemove, bool deleteDb)
+{
+       bool unsetDefault = false;
+       {
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(true);
+               DLDbIdentifier defaultId = mSavedList.defaultDLDbIdentifier();
+               for (KeychainList::const_iterator ix = kcsToRemove.begin(); ix != kcsToRemove.end(); ++ix)
                {
-                       if (err.osStatus() == errSecInvalidKeychain)
-                               continue;
-                       throw;
+                       // Find the keychain object for the given ref
+                       Keychain keychainToRemove = *ix;
+                       DLDbIdentifier dLDbIdentifier = keychainToRemove->dLDbIdentifier();
+       
+                       // Remove it from the saved list
+                       mSavedList.remove(dLDbIdentifier);
+                       if (dLDbIdentifier == defaultId)
+                               unsetDefault=true;
+
+                       if (deleteDb)
+                       {
+                               keychainToRemove->database()->deleteDb();
+                               // Now remove it from the map
+                               KeychainMap::iterator it = mKeychains.find(dLDbIdentifier);
+                               if (it == mKeychains.end())
+                                       continue;
+                               mKeychains.erase(it);
+                       }
                }
-               
-               // Remove it from the saved list
-               mSavedList.remove(keychainToRemove->dLDbIdentifier());
-               if (keychainToRemove->dLDbIdentifier() == defaultId)
-                       unsetDefault=true;
-               // Now remove it from the map
-               KeychainMap::iterator it = mKeychains.find(keychainToRemove->dLDbIdentifier());
-               if (it==mKeychains.end())
-                       continue;
-               mKeychains.erase(it);
+
+               if (unsetDefault)
+                       mSavedList.defaultDLDbIdentifier(DLDbIdentifier());
+
+               mSavedList.save();
        }
-       mSavedList.save();
-       mMultiDLDb->list(mSavedList.list());
+
+       // Make sure we are not holding mLock when we post these events.
        KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+
        if (unsetDefault)
-               globals().defaultKeychain.unset();
+               KCEventNotifier::PostKeychainEvent(kSecDefaultChangedEvent);
+}
+
+void
+StorageManager::getSearchList(KeychainList &keychainList)
+{
+       StLock<Mutex> _(mLock);
+    mSavedList.revert(false);
+       mCommonList.revert(false);
+
+       // Merge mSavedList and common list
+       DLDbList dLDbList = mSavedList.searchList();
+       DLDbList commonList = mCommonList.searchList();
+       KeychainList result;
+       result.reserve(dLDbList.size() + commonList.size());
+
+    for (DLDbList::const_iterator it = dLDbList.begin(); it != dLDbList.end(); ++it)
+    {
+        Keychain keychain(_keychain(*it));
+        result.push_back(keychain);
+    }
+
+       for (DLDbList::const_iterator it = commonList.begin(); it != commonList.end(); ++it)
+       {
+               Keychain keychain(_keychain(*it));
+               result.push_back(keychain);
+       }
+
+       keychainList.swap(result);
+}
+
+void
+StorageManager::setSearchList(const KeychainList &keychainList)
+{
+       DLDbList commonList = mCommonList.searchList();
+
+       // Strip out the common list part from the end of the search list.
+       KeychainList::const_iterator it_end = keychainList.end();
+       DLDbList::const_reverse_iterator end_common = commonList.rend();
+       for (DLDbList::const_reverse_iterator it_common = commonList.rbegin(); it_common != end_common; ++it_common)
+       {
+               // Eliminate common entries from the end of the passed in keychainList.
+               if (it_end == keychainList.begin())
+                       break;
+
+               --it_end;
+               if (!((*it_end)->dLDbIdentifier() == *it_common))
+               {
+                       ++it_end;
+                       break;
+               }
+       }
+
+       /* it_end now points one past the last element in keychainList which is not in commonList. */
+       DLDbList searchList, oldSearchList(mSavedList.searchList());
+       for (KeychainList::const_iterator it = keychainList.begin(); it != it_end; ++it)
+       {
+               searchList.push_back((*it)->dLDbIdentifier());
+       }
+
+       {
+               // Set the current searchlist to be what was passed in, the old list will be freed
+               // upon exit of this stackframe.
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(true);
+               mSavedList.searchList(searchList);
+       mSavedList.save();
+       }
+
+       if (!(oldSearchList == searchList))
+       {
+               // Make sure we are not holding mLock when we post this event.
+               KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
+       }
 }
 
-void StorageManager::replace(const list<SecKeychainRef>& newKCList)
+void
+StorageManager::getSearchList(SecPreferencesDomain domain, KeychainList &keychainList)
 {
-       // replace keychains list with new list
-       CssmClient::DLDbList dldbList;
-       convert(newKCList,dldbList);
+       if (domain == mDomain)
+       {
+               StLock<Mutex> _(mLock);
+               mSavedList.revert(false);
+               convertList(keychainList, mSavedList.searchList());
+       }
+       else
+       {
+               convertList(keychainList, DLDbListCFPref(domain).searchList());
+       }
 }
 
-void StorageManager::convert(const list<SecKeychainRef>& SecKeychainRefList,CssmClient::DLDbList& dldbList)
+void
+StorageManager::setSearchList(SecPreferencesDomain domain, const KeychainList &keychainList)
 {
-    // Convert a list of SecKeychainRefs to a DLDbList
-       dldbList.clear();               // If we don't clear list, we should use "add" instead of push_back
-       for (list<SecKeychainRef>::const_iterator ix = SecKeychainRefList.begin();ix!=SecKeychainRefList.end();ix++)
+       DLDbList searchList;
+       convertList(searchList, keychainList);
+
+       if (domain == mDomain)
        {
-               // Find the keychain object for the given ref
-               Keychain keychain;
-               try
+               DLDbList oldSearchList(mSavedList.searchList());
                {
-                       keychain = KeychainRef::required(*ix);
+                       // Set the current searchlist to be what was passed in, the old list will be freed
+                       // upon exit of this stackframe.
+                       StLock<Mutex> _(mLock);
+                       mSavedList.revert(true);
+                       mSavedList.searchList(searchList);
+                       mSavedList.save();
                }
-               catch (const MacOSError& err)
+
+               if (!(oldSearchList == searchList))
                {
-                       if (err.osStatus() == errSecInvalidKeychain)
-                               continue;
-                       throw;
+                       // Make sure we are not holding mLock when we post this event.
+                       KCEventNotifier::PostKeychainEvent(kSecKeychainListChangedEvent);
                }
-               
-               // Add it to the list
-               dldbList.push_back(keychain->dLDbIdentifier());
        }
+       else
+       {
+               DLDbListCFPref(domain).searchList(searchList);
+       }
+}
+
+void
+StorageManager::domain(SecPreferencesDomain domain)
+{
+       StLock<Mutex> _(mLock);
+       if (domain == mDomain)
+               return; // no change
+
+#if !defined(NDEBUG)
+       switch (domain)
+       {
+       case kSecPreferencesDomainSystem:
+               secdebug("storagemgr", "switching to system domain"); break;
+       case kSecPreferencesDomainUser:
+               secdebug("storagemgr", "switching to user domain (uid %d)", getuid()); break;
+       default:
+               secdebug("storagemgr", "switching to weird prefs domain %d", domain); break;
+       }
+#endif
+
+       mDomain = domain;
+       mSavedList.set(domain);
+}
+
+void
+StorageManager::optionalSearchList(CFTypeRef keychainOrArray, KeychainList &keychainList)
+{
+       if (!keychainOrArray)
+               getSearchList(keychainList);
+       else
+       {
+               CFTypeID typeID = CFGetTypeID(keychainOrArray);
+               if (typeID == CFArrayGetTypeID())
+                       convertToKeychainList(CFArrayRef(keychainOrArray), keychainList);
+               else if (typeID == gTypes().KeychainImpl.typeID)
+                       keychainList.push_back(KeychainImpl::required(SecKeychainRef(keychainOrArray)));
+               else
+                       MacOSError::throwMe(paramErr);
+       }
+}
+
+// static methods.
+void
+StorageManager::convertToKeychainList(CFArrayRef keychainArray, KeychainList &keychainList)
+{
+       assert(keychainArray);
+       CFIndex count = CFArrayGetCount(keychainArray);
+       KeychainList keychains(count);
+       for (CFIndex ix = 0; ix < count; ++ix)
+       {
+               keychains[ix] = KeychainImpl::required(SecKeychainRef(CFArrayGetValueAtIndex(keychainArray, ix)));
+       }
+
+       keychainList.swap(keychains);
+}
+
+CFArrayRef
+StorageManager::convertFromKeychainList(const KeychainList &keychainList)
+{
+       CFRef<CFMutableArrayRef> keychainArray(CFArrayCreateMutable(NULL, keychainList.size(), &kCFTypeArrayCallBacks));
+
+       for (KeychainList::const_iterator ix = keychainList.begin(); ix != keychainList.end(); ++ix)
+       {
+               SecKeychainRef keychainRef = (*ix)->handle();
+               CFArrayAppendValue(keychainArray, keychainRef);
+               CFRelease(keychainRef);
+       }
+
+       // Counter the CFRelease that CFRef<> is about to do when keychainArray goes out of scope.
+       CFRetain(keychainArray);
+       return keychainArray;
+}
+
+void StorageManager::convertList(DLDbList &ids, const KeychainList &kcs)
+{
+       DLDbList result;
+       result.reserve(kcs.size());
+       for (KeychainList::const_iterator ix = kcs.begin(); ix != kcs.end(); ++ix)
+       {
+               result.push_back((*ix)->dLDbIdentifier());
+       }
+       ids.swap(result);
 }
 
+void StorageManager::convertList(KeychainList &kcs, const DLDbList &ids)
+{
+       KeychainList result;
+    result.reserve(ids.size());
+    for (DLDbList::const_iterator ix = ids.begin(); ix != ids.end(); ++ix)
+    {
+        Keychain keychain(_keychain(*ix));
+        result.push_back(keychain);
+    }
+    kcs.swap(result);
+}
 
 #pragma mark ÑÑÑÑ Login Functions ÑÑÑÑ
 
+void StorageManager::login(AuthorizationRef authRef, UInt32 nameLength, const char* name)
+{
+    AuthorizationItemSet* info = NULL;
+    OSStatus result = AuthorizationCopyInfo(authRef, NULL, &info);     // get the results of the copy rights call.
+    Boolean created = false;
+    if ( result == noErr && info->count )
+    {
+        // Grab the password from the auth context (info) and create the keychain...
+        //
+        AuthorizationItem* currItem = info->items;
+        for (UInt32 index = 1; index <= info->count; index++) //@@@plugin bug won't return a specific context.
+        {
+            if (strcmp(currItem->name, kAuthorizationEnvironmentPassword) == 0)
+            {
+                // creates the login keychain with the specified password
+                try
+                {
+                    login(nameLength, name, currItem->valueLength, currItem->value);
+                    created = true;
+                }
+                catch(...)
+                {
+                }
+                break;
+            }
+            currItem++;
+        }
+    }
+    if ( info )
+        AuthorizationFreeItemSet(info);
+
+    if ( !created )
+        MacOSError::throwMe(errAuthorizationInternal);
+}
+
 void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
 {
     if ( name == NULL || password == NULL )
@@ -260,50 +794,38 @@ void StorageManager::login(ConstStringPtr name, ConstStringPtr password)
 
 void StorageManager::login(UInt32 nameLength, const void *name, UInt32 passwordLength, const void *password)
 {
-    // @@@ set up the login session on behalf of loginwindow
-    // @@@ (this code should migrate into loginwindow)
-    debug("KClogin", "setting up login session");
-    if (OSStatus ssnErr = SessionCreate(sessionKeepCurrentBootstrap,
-            sessionHasGraphicAccess | sessionHasTTY))
-        debug("KClogin", "session setup failed status=%ld", ssnErr);
+       x_debug("StorageManager::login: entered");
+       mSavedList.revert(true);
+       if (passwordLength != 0 && password == NULL)
+       {
+               x_debug("StorageManager::login: invalid argument (NULL password)");
+               MacOSError::throwMe(paramErr);
+       }
 
-    if (name == NULL || (passwordLength != 0 && password == NULL))
-        MacOSError::throwMe(paramErr);
+       DLDbIdentifier loginDLDbIdentifier(mSavedList.loginDLDbIdentifier());
+       x_debug1("StorageManager::login: loginDLDbIdentifier is %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+       if (!loginDLDbIdentifier)
+               MacOSError::throwMe(errSecNoSuchKeychain);
 
-       // Make sure name is zero terminated
-       string theName(reinterpret_cast<const char *>(name), nameLength);
-       Keychain keychain = make(theName.c_str());
+       Keychain theKeychain(keychain(loginDLDbIdentifier));
        try
        {
-               keychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
-        debug("KClogin", "keychain unlock successful");
+               x_debug2("Attempting to unlock login keychain %s with %d-character password", (theKeychain) ? theKeychain->name() : "<NULL>", (unsigned int)passwordLength);
+               theKeychain->unlock(CssmData(const_cast<void *>(password), passwordLength));
+               x_debug("Login keychain unlocked successfully");
        }
        catch(const CssmError &e)
        {
                if (e.osStatus() != CSSMERR_DL_DATASTORE_DOESNOT_EXIST)
                        throw;
-        debug("KClogin", "creating login keychain");
-               keychain->create(passwordLength, password);
+               x_debug1("Creating login keychain %s", (loginDLDbIdentifier) ? loginDLDbIdentifier.dbName() : "<NULL>");
+               theKeychain->create(passwordLength, password);
+               x_debug("Login keychain created successfully");
+               // Set the prefs for this new login keychain.
+               loginKeychain(theKeychain);
                // Login Keychain does not lock on sleep nor lock after timeout by default.
-               keychain->setSettings(INT_MAX, false);
+               theKeychain->setSettings(INT_MAX, false);
        }
-
-       // @@@ Create a authorization credential for the current user.
-    debug("KClogin", "creating login authorization");
-       const AuthorizationItem envList[] =
-       {
-               { kAuthorizationEnvironmentUsername, nameLength, const_cast<void *>(name), 0 },
-               { kAuthorizationEnvironmentPassword, passwordLength, const_cast<void *>(password), 0 },
-               { kAuthorizationEnvironmentShared, 0, NULL, 0 }
-       };
-       const AuthorizationEnvironment environment =
-       {
-               sizeof(envList) / sizeof(*envList),
-               const_cast<AuthorizationItem *>(envList)
-       };
-       if (OSStatus authErr = AuthorizationCreate(NULL, &environment,
-            kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize, NULL))
-        debug("KClogin", "failed to create login auth, status=%ld", authErr);
 }
 
 void StorageManager::logout()
@@ -313,18 +835,85 @@ void StorageManager::logout()
 
 void StorageManager::changeLoginPassword(ConstStringPtr oldPassword, ConstStringPtr newPassword)
 {
-       globals().defaultKeychain.keychain()->changePassphrase(oldPassword, newPassword);
+       loginKeychain()->changePassphrase(oldPassword, newPassword);
+       secdebug("KClogin", "Changed login keychain password successfully");
 }
 
 
 void StorageManager::changeLoginPassword(UInt32 oldPasswordLength, const void *oldPassword,  UInt32 newPasswordLength, const void *newPassword)
 {
-       globals().defaultKeychain.keychain()->changePassphrase(oldPasswordLength, oldPassword,  newPasswordLength, newPassword);
+       loginKeychain()->changePassphrase(oldPasswordLength, oldPassword,  newPasswordLength, newPassword);
+       secdebug("KClogin", "Changed login keychain password successfully");
+}
+
+// Clear out the keychain search list and rename the existing login.keychain.
+//
+void StorageManager::resetKeychain(Boolean resetSearchList)
+{
+    // Clear the keychain search list.
+    //
+    CFArrayRef emptySearchList = nil;
+    try
+    {
+        if ( resetSearchList )
+        {
+            emptySearchList = CFArrayCreate(NULL, NULL, 0, NULL);
+            StorageManager::KeychainList keychainList;
+            convertToKeychainList(emptySearchList, keychainList);
+            setSearchList(keychainList);
+        }
+        // Get a reference to the existing login keychain...
+        // If we don't have one, we throw (not requiring a rename).
+        //
+        Keychain keychain = loginKeychain();
+        //
+        // Rename the existing login.keychain (i.e. put it aside).
+        //
+        CFMutableStringRef newName = NULL;
+        newName = CFStringCreateMutable(NULL, 0);
+        CFStringRef currName = NULL;
+        currName = CFStringCreateWithCString(NULL, keychain->name(), kCFStringEncodingUTF8);
+        if ( newName && currName )
+        {
+            CFStringAppend(newName, currName);
+            CFStringRef kcSuffix = CFSTR(kKeychainSuffix);
+            if ( CFStringHasSuffix(newName, kcSuffix) )        // remove the .keychain extension
+            {
+                CFRange suffixRange = CFStringFind(newName, kcSuffix, 0);
+                CFStringFindAndReplace(newName, kcSuffix, CFSTR(""), suffixRange, 0);
+            }
+            CFStringAppend(newName, CFSTR(kKeychainRenamedSuffix));    // add "_renamed"
+            try
+            {
+                renameUnique(keychain, newName);
+            }
+            catch(...)
+            {
+                // we need to release 'newName' & 'currName'
+            }
+        }       // else, let the login call report a duplicate
+        if ( newName )
+            CFRelease(newName);
+        if ( currName )
+            CFRelease(currName);
+    }
+    catch(...)
+    {
+        // We either don't have a login keychain, or there was a
+        // failure to rename the existing one.
+    }
+    if ( emptySearchList )
+        CFRelease(emptySearchList);
 }
 
 #pragma mark ÑÑÑÑ File Related ÑÑÑÑ
 
 Keychain StorageManager::make(const char *pathName)
+{
+       return make(pathName, true);
+}
+
+Keychain StorageManager::make(const char *pathName, bool add)
 {
        string fullPathName;
     if ( pathName[0] == '/' )
@@ -332,18 +921,30 @@ Keychain StorageManager::make(const char *pathName)
        else
     {
                // Get Home directory from environment.
-               const char *homeDir = getenv("HOME");
-               if (homeDir == NULL)
-               {
-                       // If $HOME is unset get the current users home directory from the passwd file.
-                       struct passwd *pw = getpwuid(getuid());
-                       if (!pw)
-                               MacOSError::throwMe(paramErr);
-
-                       homeDir = pw->pw_dir;
+               switch (mDomain) {
+               case kSecPreferencesDomainUser:
+                       {
+                               const char *homeDir = getenv("HOME");
+                               if (homeDir == NULL)
+                               {
+                                       // If $HOME is unset get the current user's home directory from the passwd file.
+                                       uid_t uid = geteuid();
+                                       if (!uid) uid = getuid();
+                                       struct passwd *pw = getpwuid(uid);
+                                       if (!pw)
+                                               MacOSError::throwMe(paramErr);
+                                       homeDir = pw->pw_dir;
+                               }
+                               fullPathName = homeDir;
+                       }
+                       break;
+               case kSecPreferencesDomainSystem:
+                       fullPathName = "";
+                       break;
+               default:
+                       assert(false);  // invalid domain for this
                }
 
-               fullPathName = homeDir;
                fullPathName += "/Library/Keychains/";
                fullPathName += pathName;
        }
@@ -352,19 +953,197 @@ Keychain StorageManager::make(const char *pathName)
     const CSSM_VERSION *version = NULL;
     uint32 subserviceId = 0;
     CSSM_SERVICE_TYPE subserviceType = CSSM_SERVICE_DL | CSSM_SERVICE_CSP;
-    const CssmSubserviceUid ssuid( gGuidAppleCSPDL, version, 
-                                   subserviceId, subserviceType );
-       DLDbIdentifier dLDbIdentifier( ssuid, fullPathName.c_str(), DbLocation );
-       return makeKeychain( dLDbIdentifier );
+    const CssmSubserviceUid ssuid(gGuidAppleCSPDL, version,
+                                   subserviceId, subserviceType);
+       DLDbIdentifier dLDbIdentifier(ssuid, fullPathName.c_str(), DbLocation);
+       return makeKeychain(dLDbIdentifier, add);
 }
 
-KeychainSchema
-StorageManager::keychainSchemaFor(const CssmClient::Db &db)
+Keychain StorageManager::makeLoginAuthUI(Item &item)
 {
-       KeychainSchema schema(db);
-       pair<KeychainSchemaSet::iterator, bool> result = mKeychainSchemaSet.insert(db);
-       if (result.second)
-               return schema;
-       return *result.first;
+    // Create a login/default keychain for the user using UI.
+    // The user can cancel out of the operation, or create a new login keychain.
+    // If auto-login is turned off, the user will be asked for their login password.
+    //
+    OSStatus result = noErr;
+    Keychain keychain = NULL;  // We return this keychain.
+    //
+    // Set up the Auth ref to bring up UI.
+    //
+    AuthorizationRef authRef = NULL;
+    result = AuthorizationCreate(NULL, NULL, kAuthorizationFlagDefaults, &authRef);
+    if ( result != noErr )
+        MacOSError::throwMe(errAuthorizationInternal);
+    AuthorizationEnvironment envir;
+    envir.count = 5;   // 5 hints are used.
+    AuthorizationItem* authEnvirItemArrayPtr = (AuthorizationItem*)malloc(sizeof(AuthorizationItem) * envir.count);
+    if ( !authEnvirItemArrayPtr )
+    {
+        if ( authRef )
+            AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+        MacOSError::throwMe(errAuthorizationInternal);
+    }
+    envir.items = authEnvirItemArrayPtr;
+    AuthorizationItem* currItem = authEnvirItemArrayPtr;
+    //
+    // 1st Hint (optional): The keychain item's account attribute string. 
+    //                                         When item is specified, we assume an 'add' operation is being attempted.
+    char buff[255];
+    UInt32 actLen;
+    SecKeychainAttribute attr = { kSecAccountItemAttr, 255, &buff };
+    try
+    {
+        item->getAttribute(attr, &actLen);
+    }
+    catch(...)
+    {
+        actLen = 0;    // This item didn't have the account attribute, so don't display one in the UI.
+    }
+    currItem->name = AGENT_HINT_ATTR_NAME;     // name str that identifies this hint as attr name
+    if ( actLen )      // Fill in the hint if we have a 'srvr' attr
+    {
+        if ( actLen > 255 )
+            buff[255] = 0;
+        else
+            buff[actLen] = 0;
+        currItem->valueLength = strlen(buff)+1;
+        currItem->value = buff;
+    }
+    else
+    {
+        currItem->valueLength = 0;
+        currItem->value = NULL;
+    }
+    currItem->flags = 0;
+    //
+    // 2nd Hint (optional): The item's keychain full path.
+    //
+    currItem++;
+    char* currDefaultName = NULL;
+    try
+    {
+               currDefaultName = (char*)globals().storageManager.defaultKeychain()->name();    // Use the name if we have it.
+               currItem->name = AGENT_HINT_LOGIN_KC_NAME;      // Name str that identifies this hint as kc path
+               currItem->valueLength = strlen(currDefaultName);
+               currItem->value = (void*)currDefaultName;
+               currItem->flags = 0;
+               currItem++;
+    }
+    catch(...)
+    {
+               envir.count--;
+    }
+       
+    //
+    // 3rd Hint (optional): If curr default keychain is unavailable.
+    // This is determined by the parent not existing.
+    //
+    currItem->name = AGENT_HINT_LOGIN_KC_EXISTS_IN_KC_FOLDER;
+    Boolean loginUnavail = false;
+    try
+    {
+        Keychain defaultKC = defaultKeychain();
+        if ( !defaultKC->exists() )
+               loginUnavail = true;
+    }
+    catch(...) // login.keychain not present
+    {
+    }
+       currItem->valueLength = sizeof(Boolean);
+    currItem->value = (void*)&loginUnavail;
+    currItem->flags = 0;
+    //
+    // 4th Hint (required) userName
+    //
+    currItem++;
+    currItem->name = AGENT_HINT_LOGIN_KC_USER_NAME;
+    char* uName = getenv("USER");
+    string userName = uName ? uName : "";
+       if ( userName.length() == 0 )
+    {
+        uid_t uid = geteuid();
+        if (!uid) uid = getuid();
+        struct passwd *pw = getpwuid(uid);     // fallback case...
+        if (pw)
+            userName = pw->pw_name;
+        endpwent();
+    }
+    if ( userName.length() != 0 )      // did we ultimately get one?
+    {
+        currItem->value = (void*)userName.c_str();
+        currItem->valueLength = userName.length();
+    }
+    else       // trouble getting user name; can't continue...
+    {
+        if ( authRef )
+            AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+        free(authEnvirItemArrayPtr);
+        MacOSError::throwMe(errAuthorizationInternal);
+    }
+    currItem->flags = 0;
+    //
+    // 5th Hint (optional) flags if user has more than 1 keychain (used for a later warning when reset to default).
+    //
+    currItem++; // last hint...
+    currItem->name = AGENT_HINT_LOGIN_KC_USER_HAS_OTHER_KCS_STR;
+    Boolean moreThanOneKCExists = false;
+       {
+               StLock<Mutex> _(mLock);
+               if (mSavedList.searchList().size() > 1)
+                       moreThanOneKCExists = true;
+       }
+    currItem->value = &moreThanOneKCExists;
+    currItem->valueLength = sizeof(Boolean);
+    currItem->flags = 0;
+    //
+    // Set up the auth rights and make the auth call.
+    //
+    AuthorizationItem authItem = { LOGIN_KC_CREATION_RIGHT, 0 , NULL, 0};
+    AuthorizationRights rights = { 1, &authItem };
+    result = AuthorizationCopyRights(authRef, &rights, &envir, kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights, NULL);
+    free(authEnvirItemArrayPtr);       // done with the auth items.
+    if ( result == errAuthorizationSuccess )   // On success, revert to defaults.
+    {
+        try
+        {
+            resetKeychain(true); // Clears the plist, moves aside existing login.keychain
+            login(authRef, userName.length(), userName.c_str());       // Creates a login.keychain
+            keychain = loginKeychain();        // Return it.
+            defaultKeychain(keychain); // Set it to the default.
+        }
+        catch(...)
+        {
+            // Reset failed, login.keychain creation failed, or setting it to default.
+            // We need to release 'authRef'...
+        }
+    }
+    if ( authRef )
+        AuthorizationFree(authRef, kAuthorizationFlagDefaults);
+    if ( result )
+        MacOSError::throwMe(result);   // Any other error means we don't return a keychain.
+    return keychain;
 }
 
+Keychain StorageManager::defaultKeychainUI(Item &item)
+{
+    Keychain returnedKeychain = NULL;
+    try
+    {
+        returnedKeychain = globals().storageManager.defaultKeychain(); // If we have one, return it.
+        if ( returnedKeychain->exists() )
+            return returnedKeychain;
+    }
+    catch(...) // We could have one, but it isn't available (i.e. on a un-mounted volume).
+    {
+    }
+    if ( globals().getUserInteractionAllowed() )
+    {
+        returnedKeychain = makeLoginAuthUI(item); // If no Keychains Ä is present, one will be created.
+        if ( !returnedKeychain )
+            MacOSError::throwMe(errSecInvalidKeychain);        // Something went wrong...
+    }
+    else
+        MacOSError::throwMe(errSecInteractionNotAllowed); // If UI isn't allowed, return an error.
+
+    return returnedKeychain;
+}