+++ /dev/null
-/*
- * 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>
-#include <syslog.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];
- size_t result = confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer));
- if (result == 0)
- {
- // we have an error, log it
- syslog(LOG_CRIT, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error.");
- CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
- }
-
- 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();
-
- 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);
- }
-
- LockHelper lh;
-
- if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, 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(...) {
- throw;
- }
-}
-
-//
-// 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::LockHelper::obtainLock(
- const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH
- int timeout) // default 0
-{
- mFD = -1;
- for(;;) {
- secdebug("mdslock", "obtainLock: calling open(%s)", lockFile);
- mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
- if(mFD == -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.
-//
-
-MDSSession::LockHelper::~LockHelper()
-{
- secdebug("mdslock", "releaseLock");
- if (mFD == -1)
- {
- return;
- }
-
- flock(mFD, LOCK_UN);
- close(mFD);
- mFD = -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);
- size_t 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) {
- ssize_t 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);
- }
- ssize_t 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 */
- LockHelper lh;
-
- if(!lh.obtainLock(userDbLockPath.c_str(), 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);
- 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(...) {
- throw;
- }
- mModule.lastScanIsNow();
-}
-
-/*
- * 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.
- */
- size_t numRecords = tbdRecords.size();
- for(size_t i=0; i<numRecords; i++) {
- TbdRecord *tbdRecord = tbdRecords[i];
- mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand());
- mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand());
- }
- for(size_t 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