]> git.saurik.com Git - apple/security.git/blobdiff - libsecurity_mds/lib/MDSSession.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_mds / lib / MDSSession.cpp
diff --git a/libsecurity_mds/lib/MDSSession.cpp b/libsecurity_mds/lib/MDSSession.cpp
new file mode 100644 (file)
index 0000000..4f2f741
--- /dev/null
@@ -0,0 +1,1863 @@
+/*
+ * Copyright (c) 2000-2001 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').
+ * You may not use this file except in compliance with the License. Please obtain
+ * a copy of the License at http://www.apple.com/publicsource and read it before
+ * using this file.
+ * 
+ * This Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
+ * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
+ * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+ * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
+ * specific language governing rights and limitations under the License.
+ */
+
+
+#include "MDSSession.h"
+
+#include <security_cdsa_plugin/DbContext.h>
+#include "MDSModule.h"
+#include "MDSAttrParser.h"
+#include "MDSAttrUtils.h"
+
+#include <memory>
+#include <Security/cssmerr.h>
+#include <security_utilities/logging.h>
+#include <security_utilities/debugging.h>
+#include <security_utilities/cfutilities.h>
+#include <security_cdsa_client/dlquery.h>
+#include <securityd_client/ssclient.h>
+#include <Security/mds_schema.h>
+#include <CoreFoundation/CFBundle.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <assert.h>
+#include <time.h>
+#include <string>
+#include <unistd.h>
+
+using namespace CssmClient;
+
+
+/* 
+ * The layout of the various MDS DB files on disk is as follows:
+ *
+ * /var/db/mds                         -- owner = root, mode = 01777, world writable, sticky
+ *    system/                          -- owner = root, mode = 0755
+ *       mdsObject.db          -- owner = root, mode = 0644, object DB
+ *       mdsDirectory.db       -- owner = root, mode = 0644, MDS directory DB
+ *          mds.lock           -- temporary, owner = root, protects creation of and 
+ *                                                        updates to previous two files
+ *       mds.install.lock      -- owner = root, protects MDS_Install operation
+ *    <uid>/                           -- owner = <uid>, mode = 0700
+ *              mdsObject.db           -- owner = <uid>, mode = 000, object DB
+ *       mdsDirectory.db       -- owner = <uid>, mode = 000, MDS directory DB
+ *          mds.lock                   -- owner = <uid>, protects updates of previous two files
+ * 
+ * The /var/db/mds/system directory is created at OS install time. The DB files in 
+ * it are created by root at MDS_Install time. The ownership and mode of this directory and
+ * of its parent is also re-checked and forced to the correct state at MDS_Install time. 
+ * Each user has their own private directory with two DB files and a lock. The first time 
+ * a user accesses MDS, the per-user directory is created and the per-user DB files are 
+ * created as copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system
+ * DB files when they are the source of these copies; this is the same mechanism
+ * used by the underlying AtomicFile. 
+ *
+ * The sticky bit in /var/db/mds ensures that users cannot modify other userss private 
+ * MDS directories. 
+ */
+namespace Security
+{
+
+/*
+ * Nominal location of Security.framework.
+ */
+#define MDS_SYSTEM_PATH                "/System/Library/Frameworks"
+#define MDS_SYSTEM_FRAME       "Security.framework"
+
+/*
+ * Nominal location of standard plugins.
+ */
+#define MDS_BUNDLE_PATH                "/System/Library/Security"
+#define MDS_BUNDLE_EXTEN       ".bundle"
+
+
+/*
+ * Location of MDS database and lock files.
+ */
+#define MDS_BASE_DB_DIR                        "/private/var/db/mds"
+#define MDS_SYSTEM_DB_COMP             "system"
+#define MDS_SYSTEM_DB_DIR              MDS_BASE_DB_DIR "/" MDS_SYSTEM_DB_COMP
+#define MDS_USER_DB_COMP               "mds"
+
+#define MDS_LOCK_FILE_NAME             "mds.lock"                      
+#define MDS_INSTALL_LOCK_NAME  "mds.install.lock"      
+#define MDS_OBJECT_DB_NAME             "mdsObject.db"
+#define MDS_DIRECT_DB_NAME             "mdsDirectory.db"
+
+#define MDS_INSTALL_LOCK_PATH  MDS_SYSTEM_DB_DIR "/" MDS_INSTALL_LOCK_NAME
+#define MDS_OBJECT_DB_PATH             MDS_SYSTEM_DB_DIR "/" MDS_OBJECT_DB_NAME
+#define MDS_DIRECT_DB_PATH             MDS_SYSTEM_DB_DIR "/" MDS_DIRECT_DB_NAME
+
+/* hard coded modes and a symbolic UID for root */
+#define MDS_BASE_DB_DIR_MODE   (mode_t)0755
+#define MDS_SYSTEM_DB_DIR_MODE (mode_t)0755
+#define MDS_SYSTEM_DB_MODE             (mode_t)0644
+#define MDS_USER_DB_DIR_MODE   (mode_t)0700
+#define MDS_USER_DB_MODE               (mode_t)0600
+#define MDS_SYSTEM_UID                 (uid_t)0
+
+/*
+ * Location of per-user bundles, relative to home directory.
+ * Per-user DB files are in MDS_BASE_DB_DIR/<uid>/. 
+ */
+#define MDS_USER_BUNDLE                "Library/Security"
+
+/* time to wait in ms trying to acquire lock */
+#define DB_LOCK_TIMEOUT                (2 * 1000)
+
+/* Minimum interval, in seconds, between rescans for plugin changes */
+#define MDS_SCAN_INTERVAL      5
+
+/* trace file I/O */
+#define MSIoDbg(args...)               secdebug("MDS_IO", ## args)
+
+/* Trace cleanDir() */
+#define MSCleanDirDbg(args...) secdebug("MDS_CleanDir", ## args)
+
+static std::string GetMDSBaseDBDir(bool isRoot)
+{
+       // what we return depends on whether or not we are root
+       string retValue;
+       if (isRoot)
+       {
+               retValue = MDS_SYSTEM_DB_DIR;
+       }
+       else
+       {
+               char strBuffer[PATH_MAX + 1];
+               confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer));
+               retValue = strBuffer;
+       }
+       
+       return retValue;
+}
+
+
+
+static std::string GetMDSDBDir()
+{
+       string retValue;
+       bool isRoot = geteuid() == 0;
+       
+       if (isRoot)
+       {
+               retValue = MDS_SYSTEM_DB_DIR;
+       }
+       else
+       {
+               retValue = GetMDSBaseDBDir(isRoot) + "/" + MDS_USER_DB_COMP;
+       }
+       
+       return retValue;
+}
+
+
+
+static std::string GetMDSObjectDBPath()
+{
+       return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME;
+}
+
+
+
+static std::string GetMDSDirectDBPath()
+{
+       return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH;
+}
+
+
+
+static std::string GetMDSDBLockPath()
+{
+       return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME;
+}
+
+
+
+/*
+ * Given a path to a directory, remove everything in the directory except for the optional
+ * keepFileNames. Returns 0 on success, else an errno. 
+ */
+static int cleanDir(
+       const char *dirPath,
+       const char **keepFileNames,             // array of strings, size numKeepFileNames
+       unsigned numKeepFileNames)
+{
+    DIR *dirp;
+    struct dirent *dp;
+       char fullPath[MAXPATHLEN];
+       int rtn = 0;
+       
+       MSCleanDirDbg("cleanDir(%s) top", dirPath);
+    if ((dirp = opendir(dirPath)) == NULL) {
+               rtn = errno;
+               MSCleanDirDbg("opendir(%s) returned  %d", dirPath, rtn);
+        return rtn;
+    }
+
+    for(;;) {
+               bool skip = false;
+               const char *d_name = NULL;
+               
+               /* this block is for breaking on unqualified entries */
+               do {
+                       errno = 0;
+                       dp = readdir(dirp);
+                       if(dp == NULL) {
+                               /* end of directory or error */
+                               rtn = errno;
+                               if(rtn) {
+                                       MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath, rtn);
+                               }
+                               break;
+                       }
+                       d_name = dp->d_name;
+                       
+                       /* skip "." and ".." */
+                       if( (d_name[0] == '.') &&
+                           ( (d_name[1] == '\0') ||
+                                 ( (d_name[1] == '.') && (d_name[2] == '\0') ) ) ) {
+                               skip = true;
+                               break;
+                       }
+                       
+                       /* skip entries in keepFileNames */
+                       for(unsigned dex=0; dex<numKeepFileNames; dex++) {
+                               if(!strcmp(keepFileNames[dex], d_name)) {
+                                       skip = true;
+                                       break;
+                               }
+                       }
+               } while(0);
+               if(rtn || (dp == NULL)) {
+                       /* one way or another, we're done */
+                       break;
+               }
+               if(skip) {
+                       /* try again */
+                       continue;
+               }
+
+               /* We have an entry to delete. Delete it, or recurse. */
+
+               snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, d_name);
+               if(dp->d_type == DT_DIR) {
+                       /* directory. Clean it, then delete. */
+                       MSCleanDirDbg("cleanDir recursing for dir %s", fullPath);
+                       rtn = cleanDir(fullPath, NULL, 0);
+                       if(rtn) {
+                               break;
+                       }
+                       MSCleanDirDbg("cleanDir deleting dir %s", fullPath);
+                       if(rmdir(fullPath)) {
+                               rtn = errno;
+                               MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn);
+                               break;
+                       }
+               }
+               else {
+                       MSCleanDirDbg("cleanDir deleting file %s", fullPath);
+                       if(unlink(fullPath)) {
+                               rtn = errno;
+                               MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn);
+                               break;
+                       }
+               }
+               
+               /* 
+                * Back to beginning of directory for clean scan.
+                * Normally we'd just do a rewinddir() here but the RAMDisk filesystem,
+                * used when booting from DVD, does not implement that properly.
+                */
+               closedir(dirp);
+               if ((dirp = opendir(dirPath)) == NULL) {
+                       rtn = errno;
+                       MSCleanDirDbg("opendir(%s) returned  %d", dirPath, rtn);
+                       return rtn;
+               }
+    } /* main loop */
+
+       closedir(dirp);
+       return rtn;
+}
+
+/* 
+ * Determine if a file exists as regular file with specified owner. Returns true if so.
+ * If the purge argument is true, and there is something at the specified path that
+ * doesn't meet spec, we do everything we can to delete it. If that fails we throw
+ * CssmError(CSSM_ERRCODE_MDS_ERROR). If the delete succeeds we return false.
+ * Returns the stat info on success for further processing by caller. 
+ */
+static bool doesFileExist(
+       const char *filePath,
+       uid_t forUid,
+       bool purge,
+       struct stat &sb)                // RETURNED
+{
+       MSIoDbg("stat %s in doesFileExist", filePath);
+       if(lstat(filePath, &sb)) {
+               /* doesn't exist or we can't even get to it. */
+               if(errno == ENOENT) {
+                       return false;
+               }
+               if(purge) {
+                       /* If we can't stat it we sure can't delete it. */
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+               return false;
+       }
+       
+       /* it's there...how does it look? */
+       mode_t fileType = sb.st_mode & S_IFMT;
+       if((fileType == S_IFREG) && (sb.st_uid == forUid)) {
+               return true;
+       }
+       if(!purge) {
+               return false;
+       }
+
+       /* not what we want: get rid of it. */
+       if(fileType == S_IFDIR) {
+               /* directory: clean then remove */
+               if(cleanDir(filePath, NULL, 0)) {
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+               if(rmdir(filePath)) {
+                       MSDebug("rmdir(%s) returned %d", filePath, errno);
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+       }
+       else {
+               if(unlink(filePath)) {
+                       MSDebug("unlink(%s) returned %d", filePath, errno);
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+       }
+       
+       /* caller should be somewhat happy */
+       return false;
+}
+
+/*
+ * Determine if both of the specified DB files exist as accessible regular files with specified 
+ * owner. Returns true if they do. 
+ *
+ * If the purge argument is true, we'll ensure that either both files exist with
+ * the right owner, or neither of the files exist on exit. An error on that operation
+ * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed). 
+ * Returns the stat info for both files on success for further processing by caller. 
+ */
+static bool doFilesExist(
+       const char *objDbFile,
+       const char *directDbFile,
+       uid_t forUid,
+       bool purge,                                     // false means "passive" check 
+       struct stat &objDbSb,           // RETURNED
+       struct stat &directDbSb)        // RETURNED
+       
+{
+       bool objectExist = doesFileExist(objDbFile, forUid, purge, objDbSb);
+       bool directExist = doesFileExist(directDbFile, forUid, purge, directDbSb);
+       if(objectExist && directExist) {
+               return true;
+       }
+       else if(!purge) {
+               return false;
+       }
+       
+       /* 
+        * At least one does not exist - ensure neither of them do.
+        * Note that if we got this far, we know the one that exists is a regular file
+        * so it's safe to just unlink it. 
+        */
+       if(objectExist) {
+               if(unlink(objDbFile)) {
+                       MSDebug("unlink(%s) returned %d", objDbFile, errno);
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+       }
+       if(directExist) {
+               if(unlink(directDbFile)) {
+                       MSDebug("unlink(%s) returned %d", directDbFile, errno);
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+       }
+       return false;
+}
+
+/*
+ * Determine if specified directory exists with specified owner and mode. 
+ * Returns true if copacetic, else returns false and also indicates
+ * via the directStatus out param what went wrong. 
+ */
+typedef enum {
+       MDS_NotPresent,         /* nothing there */
+       MDS_NotDirectory,       /* not a directory */
+       MDS_BadOwnerMode,       /* wrong owner or mode */
+       MDS_Access                      /* couldn't search the directories */
+} MdsDirectStatus;
+
+static bool doesDirectExist(
+       const char              *dirPath,
+       uid_t                   forUid,
+       mode_t                  mode,
+       MdsDirectStatus &directStatus)          /* RETURNED */
+{
+       struct stat sb;
+       
+       MSIoDbg("stat %s in doesDirectExist", dirPath);
+       if (lstat(dirPath, &sb)) {
+               int err = errno;
+               switch(err) {
+                       case EACCES:
+                               directStatus = MDS_Access;
+                               break;
+                       case ENOENT:
+                               directStatus = MDS_NotPresent;
+                               break;
+                       /* Any others? Is this a good SWAG to handle the default? */
+                       default:
+                               directStatus = MDS_NotDirectory;
+                               break;
+               }
+               return false;
+       }
+       mode_t fileType = sb.st_mode & S_IFMT;
+       if(fileType != S_IFDIR) {
+               directStatus = MDS_NotDirectory;
+               return false;
+       }
+       if(sb.st_uid != forUid) {
+               directStatus = MDS_BadOwnerMode;
+               return false;
+       }
+       if((sb.st_mode & 07777) != mode) {
+               directStatus = MDS_BadOwnerMode;
+               return false;
+       }
+       return true;
+}
+
+/*
+ * Create specified directory if it doesn't already exist. If there is something 
+ * already there that doesn't meet spec (not a directory, wrong mode, wrong owner)
+ * we'll do everything we can do delete what is there and then try to create it
+ * correctly.
+ *
+ * Returns an errno on any unrecoverable error. 
+ */
+static int createDir(
+       const char *dirPath,
+       uid_t forUid,                   // for checking - we don't try to set this
+       mode_t dirMode)
+{
+       MdsDirectStatus directStatus;
+       
+       if(doesDirectExist(dirPath, forUid, dirMode, directStatus)) {
+               /* we're done */
+               return 0;
+       }
+       
+       /*
+        * Attempt recovery if there is *something* there.
+        * Anything other than "not present" should be considered to be a possible
+        * attack; syslog it. 
+        */
+       int rtn;
+       switch(directStatus) {
+               case MDS_NotPresent:
+                       /* normal trivial case: proceed. */
+                       break;
+                       
+               case MDS_NotDirectory:
+                       /* there's a file or symlink in the way */
+                       if(unlink(dirPath)) {
+                               rtn = errno;
+                               MSDebug("createDir(%s): unlink() returned %d", dirPath, rtn);
+                               return rtn;
+                       }
+                       break;
+                       
+               case MDS_BadOwnerMode:
+                       /* 
+                        * It's a directory; try to clean it out (which may well fail if we're
+                        * not root).
+                        */
+                       rtn = cleanDir(dirPath, NULL, 0);
+                       if(rtn) {
+                               return rtn;
+                       }
+                       if(rmdir(dirPath)) {
+                               rtn = errno;
+                               MSDebug("createDir(%s): rmdir() returned %d", dirPath, rtn);
+                               return rtn;
+                       }
+                       /* good to go */
+                       break;
+                       
+               case MDS_Access:                /* hopeless */
+                       MSDebug("createDir(%s): access failure, bailing", dirPath);
+                       return EACCES;
+       }
+       rtn = mkdir(dirPath, dirMode);
+       if(rtn) {
+               rtn = errno;
+               MSDebug("createDir(%s): mkdir() returned %d", dirPath, errno);
+       }
+       else {
+               /* make sure umask does't trick us */
+               rtn = chmod(dirPath, dirMode);
+               if(rtn) {
+                       MSDebug("chmod(%s) returned  %d", dirPath, errno);
+               }
+       }
+       return rtn;
+}
+
+/*
+ * Create an MDS session.
+ */
+MDSSession::MDSSession (const Guid *inCallerGuid,
+                        const CSSM_MEMORY_FUNCS &inMemoryFunctions) :
+       DatabaseSession(MDSModule::get().databaseManager()),
+       mCssmMemoryFunctions (inMemoryFunctions),
+       mModule(MDSModule::get())
+{
+       MSDebug("MDSSession::MDSSession");
+               
+    mCallerGuidPresent =  inCallerGuid != nil;
+    if (mCallerGuidPresent) {
+        mCallerGuid = *inCallerGuid;
+       }
+}
+
+MDSSession::~MDSSession ()
+{
+       MSDebug("MDSSession::~MDSSession");
+}
+
+void
+MDSSession::terminate ()
+{
+       MSDebug("MDSSession::terminate");
+    closeAll();
+}
+
+const char* kExceptionDeletePath = "messages";
+
+
+/*
+ * Called by security server via MDS_Install().
+ */
+void
+MDSSession::install ()
+{
+       //
+       // Installation requires root
+       //
+       if(geteuid() != (uid_t)0) { 
+               CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
+       }
+       
+       //
+       // install() is only (legitimately) called from securityd.
+       // Mark "server mode" so we don't end up waiting for ourselves when the databases open.
+       //
+       mModule.setServerMode();
+       
+       int sysFdLock = -1;
+       try {
+               /* ensure MDS base directory exists with correct permissions */
+               if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) {
+                       MSDebug("Error creating base MDS dir; aborting.");
+                       CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
+               }
+                      
+               /* ensure the the system MDS DB directory exists with correct permissions */
+               if(createDir(MDS_SYSTEM_DB_DIR, MDS_SYSTEM_UID, MDS_SYSTEM_DB_DIR_MODE)) {
+                       MSDebug("Error creating system MDS dir; aborting.");
+                       CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
+               }
+
+               if(!obtainLock(MDS_INSTALL_LOCK_PATH, sysFdLock, DB_LOCK_TIMEOUT)) {
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+
+               /* 
+                * We own the whole MDS system. Clean everything out except for our lock
+                * (and the directory it's in :-)
+                */
+               
+               const char *savedFile = MDS_INSTALL_LOCK_NAME;
+               if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) {
+                       /* this should never happen - we're root */
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+               
+               const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath};
+               if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) {
+                       /* this should never happen - we're root */
+                       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+               }
+                               
+               /* 
+                * Do initial population of system DBs.
+                */
+               createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE);
+               DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
+               dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH);
+       }
+       catch(...) {
+               if(sysFdLock != -1) {
+                       releaseLock(sysFdLock);
+               }
+               throw;
+       }
+       releaseLock(sysFdLock);
+}
+
+//
+// In this implementation, the uninstall() call is not supported since
+// we do not allow programmatic deletion of the MDS databases.
+//
+
+void
+MDSSession::uninstall ()
+{
+       CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
+}
+
+/*
+ * Common private open routine given a full specified path.
+ */
+CSSM_DB_HANDLE MDSSession::dbOpen(const char *dbName, bool batched)
+{
+       static CSSM_APPLEDL_OPEN_PARAMETERS batchOpenParams = {
+               sizeof(CSSM_APPLEDL_OPEN_PARAMETERS),
+               CSSM_APPLEDL_OPEN_PARAMETERS_VERSION,
+               CSSM_FALSE,             // do not auto-commit
+               0                               // mask - do not use following fields
+       };
+       
+       MSDebug("Opening %s%s", dbName, batched ? " in batch mode" : "");
+       MSIoDbg("open %s in dbOpen(name, batched)", dbName);
+       CSSM_DB_HANDLE dbHand;
+       DatabaseSession::DbOpen(dbName,
+               NULL,                           // DbLocation
+               CSSM_DB_ACCESS_READ,
+               NULL,                           // AccessCred - hopefully optional 
+               batched ? &batchOpenParams : NULL,
+               dbHand);
+       return dbHand;
+}
+
+/* DatabaseSession routines we need to override */
+void MDSSession::DbOpen(const char *DbName,
+               const CSSM_NET_ADDRESS *DbLocation,
+               CSSM_DB_ACCESS_TYPE AccessRequest,
+               const AccessCredentials *AccessCred,
+               const void *OpenParameters,
+               CSSM_DB_HANDLE &DbHandle)
+{
+       if (!mModule.serverMode()) {
+               /*
+                * Make sure securityd has finished initializing (system) MDS data.
+                * Note that activate() only does IPC once and retains global state after that.
+                */
+               SecurityServer::ClientSession client(Allocator::standard(), Allocator::standard());
+               client.activate();              /* contact securityd - won't return until MDS is ready */
+       }
+
+       /* make sure DBs are up-to-date */
+       updateDataBases();
+       
+       /* 
+        * Only task here is map incoming DbName - specified in the CDSA 
+        * spec - to a filename we actually use (which is a path to either 
+        * a system MDS DB file or a per-user MDS DB file).  
+        */
+       if(DbName == NULL) {
+               CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
+       }
+       const char *dbName;
+       if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) {
+               dbName = MDS_OBJECT_DB_NAME;
+       }
+       else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) {
+               dbName = MDS_DIRECT_DB_NAME;
+       }
+       else {
+               CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
+       }
+       char fullPath[MAXPATHLEN];
+       dbFullPath(dbName, fullPath);
+       MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName);
+       DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred,
+               OpenParameters, DbHandle);
+}
+
+CSSM_HANDLE MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle,
+                             const CssmQuery *Query,
+                             CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes,
+                             CssmData *Data,
+                             CSSM_DB_UNIQUE_RECORD_PTR &UniqueId)
+{
+       updateDataBases();
+       return DatabaseSession::DataGetFirst(DBHandle, Query, Attributes, Data, UniqueId);
+}
+
+
+void
+MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList)
+{
+       outNameList = new CSSM_NAME_LIST[1];
+       outNameList->NumStrings = 2;
+       outNameList->String = new char*[2];
+       outNameList->String[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME);
+       outNameList->String[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME);
+}
+
+void
+MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList)
+{
+       delete [] inNameList.String[0];
+       delete [] inNameList.String[1];
+       delete [] inNameList.String;
+}
+
+void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle,
+       char **DbName)
+{
+       printf("GetDbNameFromHandle: code on demand\n");
+       CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+}
+
+//
+// Attempt to obtain an exclusive lock over the the MDS databases. The
+// parameter is the maximum amount of time, in milliseconds, to spend
+// trying to obtain the lock. A value of zero means to return failure
+// right away if the lock cannot be obtained.
+//
+bool
+MDSSession::obtainLock(
+       const char *lockFile,   // e.g. MDS_INSTALL_LOCK_PATH
+       int &fd,                                // IN/OUT
+       int timeout)                    // default 0
+{
+       fd = -1;
+       for(;;) {
+               secdebug("mdslock", "obtainLock: calling open(%s)", lockFile);
+               fd = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
+               if(fd == -1) {
+                       int err = errno;
+                       secdebug("mdslock", "obtainLock: open error %d", errno);
+                       if(err == EINTR) {
+                               /* got a signal, go again */
+                               continue;
+                       }
+                       else {
+                               /* theoretically should never happen */
+                               return false;
+                       }
+               }
+               else {
+                       secdebug("mdslock", "obtainLock: success");
+                       return true;
+               }
+       }
+       
+       /* not reached */
+       return false;
+}
+
+//
+// Release the exclusive lock over the MDS databases. If this session
+// does not hold the lock, this method does nothing.
+//
+
+void
+MDSSession::releaseLock(int &fd)
+{
+       secdebug("mdslock", "releaseLock");
+       assert(fd != -1);
+       flock(fd, LOCK_UN);
+       close(fd);
+       fd = -1;
+}
+
+/* given DB file name, fill in fully specified path */
+void MDSSession::dbFullPath(
+       const char *dbName,
+       char fullPath[MAXPATHLEN+1])
+{
+       mModule.getDbPath(fullPath);
+       assert(fullPath[0] != '\0');
+       strcat(fullPath, "/");
+       strcat(fullPath, dbName);
+}
+
+/*
+ * See if any per-user bundles exist in specified directory. Returns true if so.
+ * First the check for one entry....
+ */
+static bool isBundle(
+       const struct dirent *dp)
+{
+       if(dp == NULL) {
+               return false;
+       }
+       /* NFS directories show up as DT_UNKNOWN */
+       switch(dp->d_type) {
+               case DT_UNKNOWN:
+               case DT_DIR:
+                       break;
+               default:
+                       return false;
+       }
+       int suffixLen = strlen(MDS_BUNDLE_EXTEN);
+       int len = strlen(dp->d_name);
+       
+       return (len >= suffixLen) && 
+              !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN);
+}
+
+/* now the full directory search */
+static bool checkUserBundles(
+       const char *bundlePath)
+{
+       MSDebug("searching for user bundles in %s", bundlePath);
+       DIR *dir = opendir(bundlePath);
+       if (dir == NULL) {
+               return false;
+       }
+       struct dirent *dp;
+       bool rtn = false;
+       while ((dp = readdir(dir)) != NULL) {
+               if(isBundle(dp)) {
+                       /* any other checking to do? */
+                       rtn = true;
+                       break;
+               }
+       }
+       closedir(dir);
+       MSDebug("...%s bundle(s) found", rtn ? "" : "No");
+       return rtn;
+}
+
+#define COPY_BUF_SIZE  1024
+
+/* 
+ * Single file copy with locking. 
+ * Ensures that the source is a regular file with specified owner. 
+ * Caller specifies mode of destination file. 
+ * Throws a CssmError if the source file doesn't meet spec; throws a
+ *    UnixError on any other error (which is generally recoverable by 
+ *    having the user MDS session use the system DB files).
+ */
+static void safeCopyFile(
+       const char *fromPath,
+       uid_t fromUid,
+       const char *toPath,
+       mode_t toMode)
+{
+       int error = 0;
+       bool haveLock = false;
+       int destFd = 0;
+       int srcFd = 0;
+       struct stat sb;
+       char tmpToPath[MAXPATHLEN+1];
+               
+       MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath);
+
+       if(!doesFileExist(fromPath, fromUid, false, sb)) {
+               MSDebug("safeCopyFile: bad system DB file %s", fromPath);
+               CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+       }
+       
+       /* create temp destination */
+       snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath);
+       destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode);
+       if(destFd < 0) {
+               error = errno;
+               MSDebug("Error %d opening user DB file %s\n", error, tmpToPath);
+               UnixError::throwMe(error);
+       }
+       
+       struct flock fl;
+       try {
+               /* don't get tripped up by umask */
+               if(fchmod(destFd, toMode)) {
+                       error = errno;
+                       MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath);
+                       UnixError::throwMe(error);
+               }
+
+               /* open source for reading */
+               srcFd = open(fromPath, O_RDONLY, 0);
+               if(srcFd < 0) {
+                       error = errno;
+                       MSDebug("Error %d opening system DB file %s\n", error, fromPath);
+                       UnixError::throwMe(error);
+               }
+               
+               /* acquire the same kind of lock AtomicFile uses */
+               fl.l_start = 0;
+               fl.l_len = 1;
+               fl.l_pid = getpid();
+               fl.l_type = F_RDLCK;            // AtomicFile gets F_WRLCK
+               fl.l_whence = SEEK_SET;
+
+               // Keep trying to obtain the lock if we get interupted.
+               for (;;) {
+                       if (::fcntl(srcFd, F_SETLKW, &fl) == -1) {
+                               error = errno;
+                               if (error == EINTR) {
+                                       error = 0;
+                                       continue;
+                               }
+                               MSDebug("Error %d locking system DB file %s\n", error, fromPath);
+                               UnixError::throwMe(error);
+                       }
+                       else {
+                               break;
+                               haveLock = true;
+                       }
+               }
+
+               /* copy */
+               char buf[COPY_BUF_SIZE];
+               while(1) {
+                       int bytesRead = read(srcFd, buf, COPY_BUF_SIZE);
+                       if(bytesRead == 0) {
+                               break;
+                       }
+                       if(bytesRead < 0) {
+                               error = errno;
+                               MSDebug("Error %d reading system DB file %s\n", error, fromPath);
+                               UnixError::throwMe(error);
+                       }
+                       int bytesWritten = write(destFd, buf, bytesRead);
+                       if(bytesWritten < 0) {
+                               error = errno;
+                               MSDebug("Error %d writing user DB file %s\n", error, tmpToPath);
+                               UnixError::throwMe(error);
+                       }
+               }
+       }
+       catch(...) {
+               /* error is nonzero, we'll re-throw below...still have some cleanup */
+       }
+       
+       /* unlock source and close both */
+       if(haveLock) {
+               fl.l_type = F_UNLCK;
+               if (::fcntl(srcFd, F_SETLK, &fl) == -1) {
+                       MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath);
+               }
+       }
+       MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath);
+       if(srcFd) {
+               close(srcFd);
+       }
+       if(destFd) {
+               close(destFd);
+       }
+       if(error == 0) {
+               /* commit temp file */
+               if(::rename(tmpToPath, toPath)) {
+                       error = errno;
+                       MSDebug("Error %d committing %s\n", error, toPath);
+               }
+       }
+       if(error) {
+               UnixError::throwMe(error);
+       }
+}
+
+/* 
+ * Copy system DB files to specified user dir. Caller holds user DB lock. 
+ * Throws a UnixError on error.
+ */
+static void copySystemDbs(
+       const char *userDbFileDir)
+{
+       char toPath[MAXPATHLEN+1];
+       
+       snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME);
+       safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
+       snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME);
+       safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
+}
+
+/*
+ * Ensure current DB files exist and are up-to-date.
+ * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
+ * other public functions which access a DB from scratch.
+ */
+void MDSSession::updateDataBases()
+{
+       RecursionBlock::Once once(mUpdating);
+       if (once())
+               return; // already updating; don't recurse
+       
+       uid_t ourUid = geteuid();
+       bool isRoot = (ourUid == 0);
+       
+       /* if we scanned recently, we're done */
+       double delta = mModule.timeSinceLastScan();
+       if(delta < (double)MDS_SCAN_INTERVAL) {
+               return;
+       }
+
+       /*
+        * If we're root, the first thing we do is to ensure that system DBs are present.
+        * Note that this is a necessary artifact of the problem behind Radar 3800811.
+        * When that is fixed, install() should ONLY be called from the public MDS_Install()
+        * routine.
+        * Anyway, if we *do* have to install here, we're done.
+        */
+       if(isRoot && !systemDatabasesPresent(false)) {
+               install();
+               mModule.setDbPath(MDS_SYSTEM_DB_DIR);
+               mModule.lastScanIsNow();
+               return;
+       }
+       
+       /* 
+        * Obtain various per-user paths. Root is a special case but follows most
+        * of the same logic from here on.
+        */
+       std::string userDBFileDir = GetMDSDBDir();
+       std::string userObjDBFilePath = GetMDSObjectDBPath();
+       std::string userDirectDBFilePath = GetMDSDirectDBPath();
+       char userBundlePath[MAXPATHLEN+1];
+       std::string userDbLockPath = GetMDSDBLockPath();
+       
+       /* this means "no user bundles" */
+       userBundlePath[0] = '\0';
+       if(!isRoot) {
+               char *userHome = getenv("HOME");
+               if((userHome == NULL) ||
+                  (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) {
+                       /* Can't check for user bundles */
+                       MSDebug("missing or invalid HOME; skipping user bundle check");
+               }
+               /* TBD: any other checking of userHome? */
+               else {
+                       snprintf(userBundlePath, sizeof(userBundlePath), 
+                               "%s/%s", userHome, MDS_USER_BUNDLE);
+               }
+       }
+
+       /* 
+        * Create the per-user directory...that's where the lock we'll be using lives.
+        */
+       if(!isRoot) {
+               if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) {
+                       /* 
+                        * We'll just have to limp along using the read-only system DBs.
+                        * Note that this protects (somewhat) against the DoS attack in 
+                        * Radar 3801292. The only problem is that this user won't be able 
+                        * to use per-user bundles. 
+                        */
+                       MSDebug("Error creating user DBs; using system DBs");
+                       mModule.setDbPath(MDS_SYSTEM_DB_DIR);
+                       return;
+               }
+       }
+
+       /* always release userLockFd no matter what happens */
+       int userLockFd = -1;
+       if(!obtainLock(userDbLockPath.c_str(), userLockFd, DB_LOCK_TIMEOUT)) {
+               CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
+       }
+       try {
+               if(!isRoot) {
+                       try {
+                               /* 
+                                * We copy the system DBs to the per-user DBs in two cases:
+                                * -- user DBs don't exist, or
+                                * -- system DBs have changed since the the last update to the user DBs. 
+                                *    This happens on smart card insertion and removal. 
+                                */
+                               bool doCopySystem = false;
+                               struct stat userObjStat, userDirectStat;
+                               if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true,
+                                               userObjStat, userDirectStat)) {
+                                       doCopySystem = true;
+                               }
+                               else {
+                                       /* compare the two mdsDirectory.db files */
+                                       MSIoDbg("stat %s, %s in updateDataBases",
+                                               MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str());
+                                       struct stat sysStat;
+                                       if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) {
+                                               doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) ||
+                                                       ((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) &&
+                                                               (sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec));
+                                               if(doCopySystem) {
+                                                       MSDebug("user DB files obsolete at %s", userDBFileDir.c_str());
+                                               }
+                                       }
+                               }
+                               if(doCopySystem) {
+                                       /* copy system DBs to user DBs */
+                                       MSDebug("copying system DBs to user at %s", userDBFileDir.c_str());
+                                       copySystemDbs(userDBFileDir.c_str());
+                               }
+                               else {
+                                       MSDebug("Using existing user DBs at %s", userDBFileDir.c_str());
+                               }
+                       }
+                       catch(const CssmError &cerror) {
+                               /*
+                                * Bad system DB file detected. Fatal.
+                                */
+                               throw;
+                       }
+                       catch(...) {
+                               /* 
+                                * Error on delete or create user DBs; fall back on system DBs. 
+                                */
+                               MSDebug("doFilesExist(purge) error; using system DBs");
+                               mModule.setDbPath(MDS_SYSTEM_DB_DIR);
+                               releaseLock(userLockFd);
+                               return;
+                       }
+               }
+               else {
+                       MSDebug("Using system DBs only");
+               }
+               
+               /* 
+                * Update per-user DBs from both bundle sources (System bundles, user bundles)
+                * as appropriate. 
+                */
+               DbFilesInfo dbFiles(*this, userDBFileDir.c_str());
+               dbFiles.removeOutdatedPlugins();
+               dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH);
+               if(userBundlePath[0]) {
+                       /* skip for invalid or missing $HOME... */
+                       if(checkUserBundles(userBundlePath)) {
+                               dbFiles.updateForBundleDir(userBundlePath);
+                       }
+               }
+               mModule.setDbPath(userDBFileDir.c_str());
+       }       /* main block protected by mLockFd */
+       catch(...) {
+               releaseLock(userLockFd);
+               throw;
+       }
+       mModule.lastScanIsNow();
+       releaseLock(userLockFd);
+}
+
+/*
+ * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
+ */
+void MDSSession::removeRecordsForGuid(
+       const char *guid,
+       CSSM_DB_HANDLE dbHand)
+{
+       // tell the DB to flush its intermediate data to disk
+       PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
+       CssmClient::Query query = Attribute("ModuleID") == guid;
+       clearRecords(dbHand, query.cssmQuery());
+}
+
+
+void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query)
+{
+       CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
+       CSSM_HANDLE resultHand = DataGetFirst(dbHand,
+               &query,
+               NULL,
+               NULL,                   // No data
+               record);
+       if (resultHand == CSSM_INVALID_HANDLE)
+               return; // no matches
+       try {
+               do {
+                       DataDelete(dbHand, *record);
+                       FreeUniqueRecord(dbHand, *record);
+                       record = NULL;
+               } while (DataGetNext(dbHand,
+                       resultHand,
+                       NULL,
+                       NULL,
+                       record));
+       } catch (...) {
+               if (record)
+                       FreeUniqueRecord(dbHand, *record);
+               DataAbortQuery(dbHand, resultHand);
+       }
+}
+
+
+/*
+ * Determine if system databases are present. 
+ * If the purge argument is true, we'll ensure that either both or neither 
+ * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH.
+ */
+bool MDSSession::systemDatabasesPresent(bool purge)
+{
+       bool rtn = false;
+       
+       try {
+               /* 
+                * This can throw on a failed attempt to delete sole existing file....
+                * But if that happens while we're root, our goose is fully cooked. 
+                */
+               struct stat objDbSb, directDbSb;
+               if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH, 
+                               MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) {
+                       rtn = true;
+               }
+       }
+       catch(...) {
+       
+       }
+       return rtn;
+}
+
+/* 
+ * Given a DB name (which is used as an absolute path) and an array of 
+ * RelationInfos, create a DB.
+ */
+void
+MDSSession::createSystemDatabase(
+       const char *dbName,
+       const RelationInfo *relationInfo,
+       unsigned numRelations,
+       CSSM_BOOL autoCommit,
+       mode_t mode,
+       CSSM_DB_HANDLE &dbHand)                 // RETURNED
+{
+       CSSM_DBINFO dbInfo;
+       CSSM_DBINFO_PTR dbInfoP = &dbInfo;
+       
+       memset(dbInfoP, 0, sizeof(CSSM_DBINFO));
+       dbInfoP->NumberOfRecordTypes = numRelations;
+       dbInfoP->IsLocal = CSSM_TRUE;           // TBD - what does this mean?
+       dbInfoP->AccessPath = NULL;             // TBD
+       
+       /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
+        * info arrays */
+       unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations;
+       dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
+       memset(dbInfoP->DefaultParsingModules, 0, size);
+       size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations;
+       dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
+       memset(dbInfoP->RecordAttributeNames, 0, size);
+       size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations;
+       dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
+       memset(dbInfoP->RecordIndexes, 0, size);
+       
+       /* cook up attribute and index info for each relation */
+       unsigned relation;
+       for(relation=0; relation<numRelations; relation++) {
+               const struct RelationInfo *relp = &relationInfo[relation];      // source
+               CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo = 
+                       &dbInfoP->RecordAttributeNames[relation];                                       // dest 1
+               CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo = 
+                       &dbInfoP->RecordIndexes[relation];                                              // dest 2
+                       
+               attrInfo->DataRecordType = relp->DataRecordType;
+               attrInfo->NumberOfAttributes = relp->NumberOfAttributes;
+               attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo;
+               
+               indexInfo->DataRecordType = relp->DataRecordType;
+               indexInfo->NumberOfIndexes = relp->NumberOfIndexes;
+               indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo;
+       }
+
+       /* set autocommit and mode */
+       CSSM_APPLEDL_OPEN_PARAMETERS openParams;
+       memset(&openParams, 0, sizeof(openParams));
+       openParams.length = sizeof(openParams);
+       openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
+       openParams.autoCommit = autoCommit;
+       openParams.mask = kCSSM_APPLEDL_MASK_MODE;
+       openParams.mode = mode;
+       
+       try {
+               DbCreate(dbName,
+                       NULL,                   // DbLocation
+                       *dbInfoP,
+                       CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
+                       NULL,                   // CredAndAclEntry
+                       &openParams,
+                       dbHand);
+       }
+       catch(...) {
+               MSDebug("Error on DbCreate");
+               free(dbInfoP->DefaultParsingModules);
+               free(dbInfoP->RecordAttributeNames);
+               free(dbInfoP->RecordIndexes);
+               throw;
+       }
+       free(dbInfoP->DefaultParsingModules);
+       free(dbInfoP->RecordAttributeNames);
+       free(dbInfoP->RecordIndexes);
+       
+}
+
+/*
+ * Create system databases from scratch if they do not already exist. 
+ * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
+ * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH). 
+ * Returns true if we actually built the files, false if they already 
+ * existed.
+ */
+bool MDSSession::createSystemDatabases(
+       CSSM_BOOL autoCommit,
+       mode_t mode)
+{
+       CSSM_DB_HANDLE objectDbHand = 0;
+       CSSM_DB_HANDLE directoryDbHand = 0;
+       
+       assert(geteuid() == (uid_t)0);
+       if(systemDatabasesPresent(true)) {
+               /* both databases exist as regular files with correct owner - we're done */
+               MSDebug("system DBs already exist");
+               return false;
+       }
+
+       /* create two DBs - any exception here results in deleting both of them */
+       MSDebug("Creating MDS DBs");
+       try {
+               createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1, 
+                       autoCommit, mode, objectDbHand);
+               MSIoDbg("close objectDbHand in createSystemDatabases");
+               DbClose(objectDbHand);
+               objectDbHand = 0;
+               createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations,
+                       autoCommit, mode, directoryDbHand);
+               MSIoDbg("close directoryDbHand in createSystemDatabases");
+               DbClose(directoryDbHand);
+               directoryDbHand = 0;
+       }
+       catch (...) {
+               MSDebug("Error creating MDS DBs - deleting both DB files");
+               unlink(MDS_OBJECT_DB_PATH);
+               unlink(MDS_DIRECT_DB_PATH);
+               throw;
+       }
+       return true;
+}
+
+/*
+ * DbFilesInfo helper class
+ */
+/* Note both DB files MUST exist at construction time */
+MDSSession::DbFilesInfo::DbFilesInfo(
+       MDSSession &session, 
+       const char *dbPath) :
+               mSession(session),
+               mObjDbHand(0),
+               mDirectDbHand(0),
+               mLaterTimestamp(0)
+{
+       assert(strlen(dbPath) < MAXPATHLEN);
+       strcpy(mDbPath, dbPath);
+       
+       /* stat the two DB files, snag the later timestamp */
+       char path[MAXPATHLEN];
+       sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
+       struct stat sb;
+       MSIoDbg("stat %s in DbFilesInfo()", path);
+       int rtn = ::stat(path, &sb);
+       if(rtn) {
+               int error = errno;
+               MSDebug("Error %d statting DB file %s", error, path);
+               UnixError::throwMe(error);
+       }
+       mLaterTimestamp = sb.st_mtimespec.tv_sec;
+       sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
+       MSIoDbg("stat %s in DbFilesInfo()", path);
+       rtn = ::stat(path, &sb);
+       if(rtn) {
+               int error = errno;
+               MSDebug("Error %d statting DB file %s", error, path);
+               UnixError::throwMe(error);
+       }
+       if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
+               mLaterTimestamp = sb.st_mtimespec.tv_sec;
+       }
+}
+
+MDSSession::DbFilesInfo::~DbFilesInfo()
+{
+       if(mObjDbHand != 0) {
+               /* autocommit on, henceforth */
+               mSession.PassThrough(mObjDbHand,
+                       CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
+               MSIoDbg("close objectDbHand in ~DbFilesInfo()");
+               mSession.DbClose(mObjDbHand);
+               mObjDbHand = 0;
+       }
+       if(mDirectDbHand != 0) {
+               mSession.PassThrough(mDirectDbHand,
+                       CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
+               MSIoDbg("close mDirectDbHand in ~DbFilesInfo()");
+               mSession.DbClose(mDirectDbHand);
+               mDirectDbHand = 0;
+       }
+}
+
+/* lazy evaluation of both DB handlesÊ*/
+CSSM_DB_HANDLE MDSSession::DbFilesInfo::objDbHand()
+{
+       if(mObjDbHand != 0) {
+               return mObjDbHand;
+       }
+       char fullPath[MAXPATHLEN + 1];
+       sprintf(fullPath, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
+       MSIoDbg("open %s in objDbHand()", fullPath);
+       mObjDbHand = mSession.dbOpen(fullPath, true);   // batch mode
+       return mObjDbHand;
+}
+
+CSSM_DB_HANDLE MDSSession::DbFilesInfo::directDbHand()
+{
+       if(mDirectDbHand != 0) {
+               return mDirectDbHand;
+       }
+       char fullPath[MAXPATHLEN + 1];
+       sprintf(fullPath, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
+       MSIoDbg("open %s in directDbHand()", fullPath);
+       mDirectDbHand = mSession.dbOpen(fullPath, true);        // batch mode
+       return mDirectDbHand;
+}
+
+/*
+ * Update the info for Security.framework and the system bundles.
+ */
+void MDSSession::DbFilesInfo::updateSystemDbInfo(
+       const char *systemPath,         // e.g., /System/Library/Frameworks
+       const char *bundlePath)         // e.g., /System/Library/Security
+{
+       /* 
+        * Security.framework - CSSM and built-in modules - only for initial population of
+        * system DB files. 
+        */
+       if (systemPath) {
+               string path;
+               if (CFRef<CFBundleRef> me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")))
+                       if (CFRef<CFURLRef> url = CFBundleCopyBundleURL(me))
+                               if (CFRef<CFStringRef> cfpath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle))
+                                       path = cfString(cfpath);        // path to my bundle
+
+               if (path.empty())   // use system default
+                       path = string(systemPath) + "/" MDS_SYSTEM_FRAME;
+               updateForBundle(path.c_str());
+       }
+       
+       /* Standard loadable bundles */
+       updateForBundleDir(bundlePath);
+}
+
+
+MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
+       const CSSM_DATA &guid)
+{
+       assert(guid.Length <= MAX_GUID_LEN);
+       assert(guid.Length != 0);
+       memmove(mGuid, guid.Data, guid.Length);
+       if(mGuid[guid.Length - 1] != '\0') {
+               mGuid[guid.Length] = '\0';
+       }
+}
+
+/*
+ * Test if plugin specified by pluginPath needs to be deleted from DBs. 
+ * If so, add to tbdVector.
+ */
+void MDSSession::DbFilesInfo::checkOutdatedPlugin(
+       const CSSM_DATA &pathValue, 
+       const CSSM_DATA &guidValue, 
+       TbdVector &tbdVector)
+{
+       /* stat the specified plugin */
+       struct stat sb;
+       bool obsolete = false;
+       string path = CssmData::overlay(pathValue).toString();
+       if (!path.empty() && path[0] == '*') {
+               /* builtin pseudo-path; never obsolete this */
+               return;
+       }
+       MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str());
+       int rtn = ::stat(path.c_str(), &sb);
+       if(rtn) {
+               /* not there or inaccessible; delete */
+               obsolete = true;
+       }
+       else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
+               /* timestamp of plugin's main directory later than that of DBs */
+               obsolete = true;
+       }
+       if(obsolete) {
+               TbdRecord *tbdRecord = new TbdRecord(guidValue);
+               tbdVector.push_back(tbdRecord);
+               MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str());
+       }
+}
+
+/*
+ * Examine dbFiles.objDbHand; remove all fields associated with any bundle
+ * i.e., with any path) which are either not present on disk, or which 
+ * have changed since dbFiles.laterTimestamp().
+ */
+void MDSSession::DbFilesInfo::removeOutdatedPlugins()
+{
+       CSSM_QUERY                                              query;
+       CSSM_DB_UNIQUE_RECORD_PTR               record = NULL;
+       CSSM_HANDLE                                             resultHand;
+       CSSM_DB_RECORD_ATTRIBUTE_DATA   recordAttrs;
+       CSSM_DB_ATTRIBUTE_DATA                  theAttrs[2];
+       CSSM_DB_ATTRIBUTE_INFO_PTR              attrInfo;
+       TbdVector                                               tbdRecords;
+       
+       /* 
+        * First, scan object directory. All we need are the path and GUID attributes. 
+        */
+       recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
+       recordAttrs.SemanticInformation = 0;
+       recordAttrs.NumberOfAttributes = 2;
+       recordAttrs.AttributeData = theAttrs;
+       
+       attrInfo = &theAttrs[0].Info;
+       attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+       attrInfo->Label.AttributeName = (char*) "ModuleID";
+       attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+       theAttrs[0].NumberOfValues = 0;
+       theAttrs[0].Value = NULL;
+       attrInfo = &theAttrs[1].Info;
+       attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+       attrInfo->Label.AttributeName = (char*) "Path";
+       attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+       theAttrs[1].NumberOfValues = 0;
+       theAttrs[1].Value = NULL;
+       
+       /* just search by recordType, no predicates */
+       query.RecordType = MDS_OBJECT_RECORDTYPE;
+       query.Conjunctive = CSSM_DB_NONE;
+       query.NumSelectionPredicates = 0;
+       query.SelectionPredicate = NULL;
+       query.QueryLimits.TimeLimit = 0;                        // FIXME - meaningful?
+       query.QueryLimits.SizeLimit = 1;                        // FIXME - meaningful?
+       query.QueryFlags = 0;           // CSSM_QUERY_RETURN_DATA...FIXME - used?
+
+       CssmQuery perryQuery(query);
+       try {
+               resultHand = mSession.DataGetFirst(objDbHand(),
+                       &perryQuery,
+                       &recordAttrs,
+                       NULL,                   // No data
+                       record);
+       }
+       catch(...) {
+               MSDebug("removeOutdatedPlugins: DataGetFirst threw");
+               return;         // ???
+       }
+       if(record) {
+               mSession.FreeUniqueRecord(mObjDbHand, *record);
+       }
+       if(resultHand) {
+               if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
+                       checkOutdatedPlugin(*theAttrs[1].Value, *theAttrs[0].Value, 
+                               tbdRecords);
+               }
+               else {
+                       MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
+               }
+               for(unsigned dex=0; dex<2; dex++) {
+                       CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
+                       for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
+                               if(attr->Value[attrDex].Data) {
+                                       mSession.free(attr->Value[attrDex].Data);
+                               }
+                       }
+                       if(attr->Value) {
+                               mSession.free(attr->Value);
+                       }
+               }
+       }
+       else {
+               /* empty Object DB - we're done */
+               MSDebug("removeOutdatedPlugins: empty object DB");
+               return;
+       }
+       
+       /* now the rest of the object DB records */
+       for(;;) {
+               bool brtn = mSession.DataGetNext(objDbHand(),
+                       resultHand, 
+                       &recordAttrs,
+                       NULL,
+                       record);
+               if(!brtn) {
+                       /* end of data */
+                       break;
+               }
+               if(record) {
+                       mSession.FreeUniqueRecord(mObjDbHand, *record);
+               }
+               if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
+                       checkOutdatedPlugin(*theAttrs[1].Value, 
+                               *theAttrs[0].Value, 
+                               tbdRecords);
+               }
+               else {
+                       MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
+               }
+               for(unsigned dex=0; dex<2; dex++) {
+                       CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
+                       for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
+                               if(attr->Value[attrDex].Data) {
+                                       mSession.free(attr->Value[attrDex].Data);
+                               }
+                       }
+                       if(attr->Value) {
+                               mSession.free(attr->Value);
+                       }
+               }
+       }
+       /* no DataAbortQuery needed; we scanned until completion */
+       
+       /*
+        * We have a vector of plugins to be deleted. Remove all records from both
+        * DBs associated with the plugins, as specified by guid.
+        */
+       unsigned numRecords = tbdRecords.size();
+       for(unsigned i=0; i<numRecords; i++) {
+               TbdRecord *tbdRecord = tbdRecords[i];
+               mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand());
+               mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand());
+       }
+       for(unsigned i=0; i<numRecords; i++) {
+               delete tbdRecords[i];
+       }
+}
+
+
+/*
+ * Update DBs for all bundles in specified directory.
+ */
+void MDSSession::DbFilesInfo::updateForBundleDir(
+       const char *bundleDirPath)
+{
+       /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
+        * much too heavyweight */
+       MSDebug("...updating DBs for dir %s", bundleDirPath);
+       DIR *dir = opendir(bundleDirPath);
+       if (dir == NULL) {
+               MSDebug("updateForBundleDir: error %d opening %s", errno, bundleDirPath);
+               return;
+       }
+       struct dirent *dp;
+       char fullPath[MAXPATHLEN];
+       while ((dp = readdir(dir)) != NULL) {
+               if(isBundle(dp)) {
+                       sprintf(fullPath, "%s/%s", bundleDirPath, dp->d_name);
+                       updateForBundle(fullPath);
+               }
+       }
+       closedir(dir);
+}
+
+/*
+ * lookup by path - just returns true if there is a record assoociated with the path
+ * in mObjDbHand. 
+ */
+bool MDSSession::DbFilesInfo::lookupForPath(
+       const char *path)
+{
+       CSSM_QUERY                                              query;
+       CSSM_DB_UNIQUE_RECORD_PTR               record = NULL;
+       CSSM_HANDLE                                             resultHand = 0;
+       CSSM_DB_RECORD_ATTRIBUTE_DATA   recordAttrs;
+       CSSM_DB_ATTRIBUTE_DATA                  theAttr;
+       CSSM_DB_ATTRIBUTE_INFO_PTR              attrInfo = &theAttr.Info;
+       CSSM_SELECTION_PREDICATE                predicate;
+       CSSM_DATA                                               predData;
+       
+       recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
+       recordAttrs.SemanticInformation = 0;
+       recordAttrs.NumberOfAttributes = 1;
+       recordAttrs.AttributeData = &theAttr;
+       
+       attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+       attrInfo->Label.AttributeName = (char*) "Path";
+       attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+       
+       theAttr.NumberOfValues = 0;
+       theAttr.Value = NULL;
+       
+       predicate.DbOperator = CSSM_DB_EQUAL;
+       predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
+       predicate.Attribute.Info.Label.AttributeName = (char*) "Path";
+       predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
+       predData.Data = (uint8 *)path;
+       predData.Length = strlen(path);
+       predicate.Attribute.Value = &predData;
+       predicate.Attribute.NumberOfValues = 1;
+       
+       query.RecordType = MDS_OBJECT_RECORDTYPE;
+       query.Conjunctive = CSSM_DB_NONE;
+       query.NumSelectionPredicates = 1;
+       query.SelectionPredicate = &predicate;
+       query.QueryLimits.TimeLimit = 0;                        // FIXME - meaningful?
+       query.QueryLimits.SizeLimit = 1;                        // FIXME - meaningful?
+       query.QueryFlags = 0;           // CSSM_QUERY_RETURN_DATA...FIXME - used?
+
+       bool ourRtn = true;
+       try {
+               CssmQuery perryQuery(query);
+               resultHand = mSession.DataGetFirst(objDbHand(),
+                       &perryQuery,
+                       &recordAttrs,
+                       NULL,                   // No data
+                       record);
+       }
+       catch (...) {
+               ourRtn = false;
+       }
+       if(record) {
+               mSession.FreeUniqueRecord(mObjDbHand, *record);
+       }
+       else {
+               ourRtn = false;
+       }
+       if(resultHand && ourRtn) {
+               /* more resulting pending; terminate the search */
+               try {
+                       mSession.DataAbortQuery(mObjDbHand, resultHand);
+               }
+               catch(...) {
+                       MSDebug("exception on DataAbortQuery in lookupForPath");
+               }
+       }
+       for(unsigned dex=0; dex<theAttr.NumberOfValues; dex++) {
+               if(theAttr.Value[dex].Data) {
+                       mSession.free(theAttr.Value[dex].Data);
+               }
+       }
+       mSession.free(theAttr.Value);
+       return ourRtn;
+}
+
+/* update entry for one bundle, which is known to exist */
+void MDSSession::DbFilesInfo::updateForBundle(
+       const char *bundlePath)
+{
+       MSDebug("...updating DBs for bundle %s", bundlePath);
+       
+       /* Quick lookup - do we have ANY entry for a bundle with this path? */
+       if(lookupForPath(bundlePath)) {
+               /* Yep, we're done */
+               return;
+       }
+       MDSAttrParser parser(bundlePath,
+               mSession,
+               objDbHand(),
+               directDbHand());
+       try {
+               parser.parseAttrs();
+       }
+       catch (const CssmError &err) {
+               // a corrupt MDS info file invalidates the entire plugin
+               const char *guid = parser.guid();
+               if (guid) {
+                       mSession.removeRecordsForGuid(guid, objDbHand());
+                       mSession.removeRecordsForGuid(guid, directDbHand());
+               }
+       }
+}
+
+
+//
+// Private API: add MDS records from contents of file
+// These files are typically written by securityd and handed to us in this call.
+//
+void MDSSession::installFile(const MDS_InstallDefaults *defaults,
+       const char *inBundlePath, const char *subdir, const char *file)
+{
+       string bundlePath = inBundlePath ? inBundlePath : cfString(CFBundleGetMainBundle());
+       DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
+       MDSAttrParser parser(bundlePath.c_str(),
+               *this,
+               dbFiles.objDbHand(),
+               dbFiles.directDbHand());
+       parser.setDefaults(defaults);
+
+       try {
+               if (file == NULL)       // parse a directory
+                       if (subdir)             // a particular directory
+                               parser.parseAttrs(CFTempString(subdir));
+                       else                    // all resources in bundle
+                               parser.parseAttrs(NULL);
+               else                            // parse just one file
+                       parser.parseFile(CFRef<CFURLRef>(makeCFURL(file)), CFTempString(subdir));
+       }
+       catch (const CssmError &err) {
+               const char *guid = parser.guid();
+               if (guid) {
+                       removeRecordsForGuid(guid, dbFiles.objDbHand());
+                       removeRecordsForGuid(guid, dbFiles.directDbHand());
+               }
+       }
+}
+
+
+//
+// Private API: Remove all records for a guid/subservice
+//
+// Note: Multicursors searching for SSID fail because not all records in the
+// database have this attribute. So we have to explicitly run through all tables
+// that do.
+//
+void MDSSession::removeSubservice(const char *guid, uint32 ssid)
+{
+       DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
+
+       CssmClient::Query query =
+               Attribute("ModuleID") == guid &&
+               Attribute("SSID") == ssid;
+
+       // only CSP and DL tables are cleared here
+       // (this function is private to securityd, which only handles those types)
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE));
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE));
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE));
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE));
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE));
+       clearRecords(dbFiles.directDbHand(),
+               CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE));
+}
+
+
+} // end namespace Security