/* * 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 /* * The layout of the various MDS DB files on disk is as follows: * * /var/tmp/mds -- owner = root, mode = 01777, world writable, sticky * 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 * previous two files * / -- owner = , mode = 0644 * mdsObject.db -- owner = , mode = 0644, object DB * mdsDirectory.db -- owner = , mode = 0644, MDS directory DB * mds.lock -- temporary, owner = , protects creation of * previous two files * * The /var/tmp/mds directory and the two db files in it are created by root * via SS or an AEWP call. Each user except for root 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 AtomincFile. * * The sticky bit in /var/tmp/mds ensures that users cannot delete, rename, and/or * replace the root-owned DB files in that directory, and that users can not * modify other user's 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 system MDS database and lock files. */ #define MDS_SYSTEM_DB_DIR "/private/var/tmp/mds" #define MDS_LOCK_FILE_NAME "mds.lock" #define MDS_OBJECT_DB_NAME "mdsObject.db" #define MDS_DIRECT_DB_NAME "mdsDirectory.db" #define MDS_LOCK_FILE_PATH MDS_SYSTEM_DB_DIR "/" MDS_LOCK_FILE_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 /* * Location of per-user bundles, relative to home directory. * PEr-user DB files are in MDS_SYSTEM_DB_DIR//. */ #define MDS_USER_DB_DIR "Library/Security" #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 10 /* initial debug - start from scratch each time */ #define START_FROM_SCRATCH 0 /* debug - skip file-level locking */ #define SKIP_FILE_LOCKING 0 #ifndef NDEBUG /* Only allow root to create and update system DB files - in the final config this * will be true */ #define SYSTEM_MDS_ROOT_ONLY 0 /* * Early development; no Security Server/root involvement with system DB creation. * If this is true, SYSTEM_MDS_ROOT_ONLY must be false (though both can be * false for intermediate testing). */ #define SYSTEM_DBS_VIA_USER 1 #else /* NDEBUG */ /* normal deployment case */ #define SYSTEM_MDS_ROOT_ONLY 1 #define SYSTEM_DBS_VIA_USER 0 #endif /* NDEBUG */ /* * Determine if both of the specified DB files exist as * accessible regular files. Returns true if they do. If the purge argument * is true, we'll ensure that either both or neither of the files exist on * exit. */ static bool doFilesExist( const char *objDbFile, const char *directDbFile, bool purge) // false means "passive" check { struct stat sb; bool objectExist = false; bool directExist = false; if (stat(objDbFile, &sb) == 0) { /* Object DB exists */ if(!(sb.st_mode & S_IFREG)) { MSDebug("deleting non-regular file %s", objDbFile); if(purge && unlink(objDbFile)) { MSDebug("unlink(%s) returned %d", objDbFile, errno); CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); } } else { objectExist = true; } } if (stat(directDbFile, &sb) == 0) { /* directory DB exists */ if(!(sb.st_mode & S_IFREG)) { MSDebug("deleting non-regular file %s", directDbFile); if(purge & unlink(directDbFile)) { MSDebug("unlink(%s) returned %d", directDbFile, errno); CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); } } directExist = true; } if(objectExist && directExist) { /* both databases exist as regular files */ return true; } else if(!purge) { return false; } /* at least one does not exist - ensure neither of them do */ 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. */ static bool doesDirectExist( const char *dirPath) { struct stat sb; if (stat(dirPath, &sb)) { return false; } if(!(sb.st_mode & S_IFDIR)) { return false; } return true; } /* * Create specified directory if it doesn't already exist. * Zero for mode means "use the default provided by 0755 modified by umask". */ static int createDir( const char *dirPath, mode_t dirMode = 0) { if(doesDirectExist(dirPath)) { return 0; } int rtn = mkdir(dirPath, 0755); if(rtn) { if(errno == EEXIST) { /* this one's OK */ rtn = 0; } else { rtn = errno; MSDebug("mkdir(%s) returned %d", dirPath, errno); } } if((rtn == 0) && (dirMode != 0)) { 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()), mLockFd(-1) { MSDebug("MDSSession::MDSSession"); #if START_FROM_SCRATCH unlink(MDS_LOCK_FILE_PATH); unlink(MDS_OBJECT_DB_PATH); unlink(MDS_DIRECT_DB_PATH); #endif mCallerGuidPresent = inCallerGuid != nil; if (mCallerGuidPresent) mCallerGuid = *inCallerGuid; /* * Create DB files if necessary; make sure they are up-to-date */ // no! done in either install or open! updateDataBases(); } MDSSession::~MDSSession () { MSDebug("MDSSession::~MDSSession"); releaseLock(mLockFd); } void MDSSession::terminate () { MSDebug("MDSSession::terminate"); releaseLock(mLockFd); closeAll(); } /* * Called by security server or AEWP-executed privileged tool. */ void MDSSession::install () { if((getuid() != (uid_t)0) && SYSTEM_MDS_ROOT_ONLY) { CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); } int sysFdLock = -1; try { /* before we obtain the lock, ensure the the system MDS DB directory exists */ if(createDir(MDS_SYSTEM_DB_DIR, 01777)) { MSDebug("Error creating system MDS dir; aborting."); CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); } if(!obtainLock(MDS_LOCK_FILE_PATH, sysFdLock, DB_LOCK_TIMEOUT)) { CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); } if(!systemDatabasesPresent(true)) { /* * Root umask is 0 when this runs, so specify readable (only) * by world. Also, turn off autocommit during initial * system DB population. */ bool created = createSystemDatabases(CSSM_FALSE, 0644); if(created) { /* * Skip possible race condition in which this is called twice, * both via SS by user procs who say "no system DBs present" * in their updateDataBases() method. * * Do initial population of system DBs. */ DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); dbFiles.autoCommit(CSSM_FALSE); 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. * * FIXME: both of these dbOpen routines leak like crazy even though * we know we close properly. * Typical stack trace (from MallocDebug) of a leak is * * DatabaseSession::DbOpen(char const *, cssm_net_address const...) * DatabaseManager::dbOpen(Security::DatabaseSession &, ...) * Database::_dbOpen(Security::DatabaseSession &, unsigned long, ...) * AppleDatabase::dbOpen(Security::DbContext &) * DbModifier::openDatabase(void) * DbModifier::getDbVersion(void) * DbVersion::DbVersion(Security::AtomicFile &, ...) * DbVersion::open(void) * MetaRecord::unpackRecord(Security::ReadSection const &, ...) * MetaRecord::unpackAttribute(Security::ReadSection const &, ...) * MetaAttribute::unpackAttribute(Security::ReadSection const &, ..) * TypedMetaAttribute::unpackValue(...) * TrackingAllocator::malloc(unsigned long) */ CSSM_DB_HANDLE MDSSession::dbOpen( const char *dbName) { MSDebug("Opening %s", dbName); CSSM_DB_HANDLE dbHand; DatabaseSession::DbOpen(dbName, NULL, // DbLocation CSSM_DB_ACCESS_READ, NULL, // AccessCred - hopefully optional NULL, // OpenParameters 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) { /* 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); DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred, OpenParameters, DbHandle); } 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_LOCK_FILE_PATH int &fd, // IN/OUT int timeout) // default 0 { #if SKIP_FILE_LOCKING return true; #else static const int kRetryDelay = 250; // ms fd = open(MDS_LOCK_FILE_PATH, O_CREAT | O_EXCL, 0544); while (fd == -1 && timeout >= kRetryDelay) { timeout -= kRetryDelay; usleep(1000 * kRetryDelay); mLockFd = open(MDS_LOCK_FILE_PATH, O_CREAT | O_EXCL, 0544); } return (fd != -1); #endif /* SKIP_FILE_LOCKING */ } // // 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) { #if !SKIP_FILE_LOCKING if (fd != -1) { close(fd); unlink(MDS_LOCK_FILE_PATH); fd = -1; } #endif } /* 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 */ static void safeCopyFile( const char *fromPath, const char *toPath) { /* open source for reading */ int srcFd = open(fromPath, O_RDONLY, 0); if(srcFd < 0) { /* FIXME - what error would we see if the file is locked for writing * by someone else? We definitely have to handle that. */ int error = errno; MSDebug("Error %d opening system DB file %s\n", error, fromPath); UnixError::throwMe(error); } /* acquire the same kind of lock AtomicFile uses */ struct flock fl; 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, reinterpret_cast(&fl)) == -1) { int error = errno; if (error == EINTR) { continue; } MSDebug("Error %d locking system DB file %s\n", error, fromPath); UnixError::throwMe(error); } else { break; } } /* create destination */ int destFd = open(toPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, 0644); if(destFd < 0) { int error = errno; MSDebug("Error %d opening user DB file %s\n", error, toPath); UnixError::throwMe(error); } /* copy */ char buf[COPY_BUF_SIZE]; while(1) { int bytesRead = read(srcFd, buf, COPY_BUF_SIZE); if(bytesRead == 0) { break; } if(bytesRead < 0) { int 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) { int error = errno; MSDebug("Error %d writing user DB file %s\n", error, toPath); UnixError::throwMe(error); } } /* unlock source and close both */ fl.l_type = F_UNLCK; if (::fcntl(srcFd, F_SETLK, reinterpret_cast(&fl)) == -1) { MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath); } close(srcFd); close(destFd); } /* Copy system DB files to specified user dir. */ static void copySystemDbs( const char *userDbFileDir) { char toPath[MAXPATHLEN+1]; sprintf(toPath, "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); safeCopyFile(MDS_OBJECT_DB_PATH, toPath); sprintf(toPath, "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); safeCopyFile(MDS_DIRECT_DB_PATH, toPath); } /* * 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() { bool isRoot = (getuid() == (uid_t)0); bool createdSystemDb = false; /* * The first thing we do is to ensure that system DBs are present. * This call right here is the reason for the purge argument in * systemDatabasesPresent(); if we're a user proc, we can't grab the system * MDS lock. */ if(!systemDatabasesPresent(false)) { if(isRoot || SYSTEM_DBS_VIA_USER) { /* Either doing actual MDS op as root, or development case: * install as current user */ install(); } else { /* This path TBD; it involves either a SecurityServer RPC or * a privileged tool exec'd via AEWP. */ assert(0); } /* remember this - we have to delete possible existing user DBs */ createdSystemDb = true; } /* if we scanned recently, we're done */ double delta = mModule.timeSinceLastScan(); if(delta < (double)MDS_SCAN_INTERVAL) { return; } /* * Obtain various per-user paths. Root is a special case but follows most * of the same logic from here on. */ char userDbFileDir[MAXPATHLEN+1]; char userObjDbFilePath[MAXPATHLEN+1]; char userDirectDbFilePath[MAXPATHLEN+1]; char userBundlePath[MAXPATHLEN+1]; char userDbLockPath[MAXPATHLEN+1]; if(isRoot) { strcat(userDbFileDir, MDS_SYSTEM_DB_DIR); /* no userBundlePath */ } else { char *userHome = getenv("HOME"); if(userHome == NULL) { /* FIXME - what now, batman? */ MSDebug("updateDataBases: no HOME"); userHome = "/"; } sprintf(userBundlePath, "%s/%s", userHome, MDS_USER_BUNDLE); /* DBs go in a per-UID directory in the system MDS DB directory */ sprintf(userDbFileDir, "%s/%d", MDS_SYSTEM_DB_DIR, (int)(getuid())); } sprintf(userObjDbFilePath, "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); sprintf(userDirectDbFilePath, "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); sprintf(userDbLockPath, "%s/%s", userDbFileDir, MDS_LOCK_FILE_NAME); /* * Create the per-user directory first...that's where the lock we'll be using * lives. Our createDir() is tolerant of EEXIST errors. */ if(!isRoot) { if(createDir(userDbFileDir)) { /* We'll just have to limp along using the read-only system DBs */ Syslog::alert("Error creating %s", userDbFileDir); MSDebug("Error creating user DBs; using system DBs"); mModule.setDbPath(MDS_SYSTEM_DB_DIR); return; } } /* always release mLockFd no matter what happens */ if(!obtainLock(userDbLockPath, mLockFd, DB_LOCK_TIMEOUT)) { CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); } try { if(!isRoot) { if(createdSystemDb) { /* initial creation of system DBs by user - start from scratch */ unlink(userObjDbFilePath); unlink(userDirectDbFilePath); } /* * System DBs exist and are as up-to-date as we are allowed to make them. * Create per-user DBs if they don't exist. */ if(createdSystemDb || //Êoptimization - if this is true, the // per-user DBs do not exist since we just // deleted them !doFilesExist(userObjDbFilePath, userDirectDbFilePath, true)) { /* copy system DBs to user DBs */ MSDebug("copying system DBs to user at %s", userDbFileDir); copySystemDbs(userDbFileDir); } else { MSDebug("Using existing user DBs at %s", userDbFileDir); } } else { MSDebug("Using system DBs only"); } /* * Update per-user DBs from all three sources (System.framework, * System bundles, user bundles) as appropriate. Note that if we * just created the system DBs, we don't have to update with * respect to system framework or system bundles. */ DbFilesInfo dbFiles(*this, userDbFileDir); if(!createdSystemDb) { dbFiles.removeOutdatedPlugins(); dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH); } if(!isRoot) { /* root doesn't have user bundles */ if(checkUserBundles(userBundlePath)) { dbFiles.updateForBundleDir(userBundlePath); } } mModule.setDbPath(userDbFileDir); } /* main block protected by mLockFd */ catch(...) { releaseLock(mLockFd); throw; } mModule.lastScanIsNow(); releaseLock(mLockFd); } /* * Remove all records with specified guid (a.k.a. ModuleID) from specified DB. */ void MDSSession::removeRecordsForGuid( const char *guid, CSSM_DB_HANDLE dbHand) { CSSM_QUERY query; CSSM_DB_UNIQUE_RECORD_PTR record = NULL; CSSM_HANDLE resultHand; CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs; CSSM_SELECTION_PREDICATE predicate; CSSM_DATA predData; /* don't want any attributes back, just a record ptr */ recordAttrs.DataRecordType = CSSM_DL_DB_RECORD_ANY; recordAttrs.SemanticInformation = 0; recordAttrs.NumberOfAttributes = 0; recordAttrs.AttributeData = NULL; /* one predicate, == guid */ predicate.DbOperator = CSSM_DB_EQUAL; predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; predicate.Attribute.Info.Label.AttributeName = "ModuleID"; predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; predData.Data = (uint8 *)guid; predData.Length = strlen(guid) + 1; predicate.Attribute.Value = &predData; predicate.Attribute.NumberOfValues = 1; query.RecordType = CSSM_DL_DB_RECORD_ANY; 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? /* * Each search starts from scratch - not sure if we can delete a record * associated with an active query and continue on with that query. */ try { for(;;) { DLQuery perryQuery(query); resultHand = DataGetFirst(dbHand, &perryQuery, &recordAttrs, NULL, // No data record); if(resultHand) { try { MSDebug("...deleting a record for guid %s", guid); DataDelete(dbHand, *record); DataAbortQuery(dbHand, resultHand); } catch(...) { MSDebug("exception (1) while deleting record for guid %s", guid); /* proceed.... */ } } else if(record) { FreeUniqueRecord(dbHand, *record); break; } } /* main loop */ } catch (...) { MSDebug("exception (2) while deleting record for guid %s", guid); } } /* * 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 need to hold MDS_LOCK_FILE_PATH. */ bool MDSSession::systemDatabasesPresent(bool purge) { bool rtn = false; try { /* this can throw on a failed attempt to delete sole existing file */ if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH, purge)) { 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_LOCK_FILE_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to * exist (that's our caller's job, before acquiring MDS_LOCK_FILE_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((getuid() == (uid_t)0) || !SYSTEM_MDS_ROOT_ONLY); if(systemDatabasesPresent(true)) { /* both databases exist as regular files - 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); DbClose(objectDbHand); objectDbHand = 0; createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations, autoCommit, mode, directoryDbHand); 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; 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); 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_TOGGLE_AUTOCOMMIT, reinterpret_cast(CSSM_TRUE), NULL); mSession.DbClose(mObjDbHand); mObjDbHand = 0; } if(mDirectDbHand != 0) { mSession.PassThrough(mDirectDbHand, CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, reinterpret_cast(CSSM_TRUE), NULL); 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); mObjDbHand = mSession.dbOpen(fullPath); 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); mDirectDbHand = mSession.dbOpen(fullPath); return mDirectDbHand; } /* * Update the info for System.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 { /* System.framework - CSSM and built-in modules */ char fullPath[MAXPATHLEN]; sprintf(fullPath, "%s/%s", systemPath, MDS_SYSTEM_FRAME); updateForBundle(fullPath); /* 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; int rtn = ::stat((char *)pathValue.Data, &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", pathValue.Data); } } /* * 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 = "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 = "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? DLQuery 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++) { if(theAttrs[dex].Value) { if(theAttrs[dex].Value->Data) { mSession.free(theAttrs[dex].Value->Data); } mSession.free(theAttrs[dex].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++) { if(theAttrs[dex].Value) { if(theAttrs[dex].Value->Data) { mSession.free(theAttrs[dex].Value->Data); } mSession.free(theAttrs[dex].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 = "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 = "Path"; predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; predData.Data = (uint8 *)path; predData.Length = strlen(path) + 1; 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 { DLQuery 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"); } } if(theAttr.Value) { if(theAttr.Value->Data) { mSession.free(theAttr.Value->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()); parser.parseAttrs(); } /* DB autocommit on/off */ void MDSSession::DbFilesInfo::autoCommit(CSSM_BOOL val) { try { mSession.PassThrough(objDbHand(), CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, reinterpret_cast(val), NULL); mSession.PassThrough(directDbHand(), CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT, reinterpret_cast(val), NULL); } catch (...) { MSDebug("DbFilesInfo::autoCommit error!"); /* but proceed */ } } } // end namespace Security