X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/72a12576750f52947eb043106ba5c12c0d07decf..b1ab9ed8d0e0f1c3b66d7daa8fd5564444c56195:/libsecurity_mds/lib/MDSSession.cpp diff --git a/libsecurity_mds/lib/MDSSession.cpp b/libsecurity_mds/lib/MDSSession.cpp new file mode 100644 index 00000000..4f2f7410 --- /dev/null +++ b/libsecurity_mds/lib/MDSSession.cpp @@ -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 +#include "MDSModule.h" +#include "MDSAttrParser.h" +#include "MDSAttrUtils.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +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 + * / -- owner = , mode = 0700 + * mdsObject.db -- owner = , mode = 000, object DB + * mdsDirectory.db -- owner = , mode = 000, MDS directory DB + * mds.lock -- owner = , 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//. + */ +#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; dexd_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; relationRecordAttributeNames[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 me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"))) + if (CFRef url = CFBundleCopyBundleURL(me)) + if (CFRef 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; attrDexNumberOfValues; 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; attrDexNumberOfValues; 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; iguid(), objDbHand()); + mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand()); + } + for(unsigned i=0; id_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(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