/* * 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 using namespace CssmClient; /* * The layout of the various MDS DB files on disk is as follows: * * /var/tmp/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/tmp/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/tmp/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/tmp/mds" #define MDS_SYSTEM_DB_COMP "system" #define MDS_SYSTEM_DB_DIR MDS_BASE_DB_DIR "/" MDS_SYSTEM_DB_COMP #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)01777 #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) /* * 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 */ Syslog::alert("MDS error: %s is not a directory", dirPath); 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). */ Syslog::alert("MDS error: %s with bad owner/mode", dirPath); 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 */ Syslog::alert("MDS error: %s is inaccessible", dirPath); 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(); } /* * Called by security server via MDS_Install(). */ void MDSSession::install () { if(geteuid() != (uid_t)0) { CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); } 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 */ Syslog::alert("MDS error: unable to clean %s", MDS_SYSTEM_DB_DIR); CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); } savedFile = MDS_SYSTEM_DB_COMP; if(cleanDir(MDS_BASE_DB_DIR, &savedFile, 1)) { /* this should never happen - we're root */ Syslog::alert("MDS error: unable to clean %s", MDS_BASE_DB_DIR); 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) { /* 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()"); 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, reinterpret_cast(&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, reinterpret_cast(&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. */ char userDbFileDir[MAXPATHLEN+1]; char userObjDbFilePath[MAXPATHLEN+1]; char userDirectDbFilePath[MAXPATHLEN+1]; char userBundlePath[MAXPATHLEN+1]; char userDbLockPath[MAXPATHLEN+1]; /* this means "no user bundles" */ userBundlePath[0] = '\0'; if(isRoot) { strcpy(userDbFileDir, MDS_SYSTEM_DB_DIR); /* no userBundlePath */ } else { 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); } /* DBs go in a per-UID directory in the base MDS DB directory */ snprintf(userDbFileDir, sizeof(userDbFileDir), "%s/%d", MDS_BASE_DB_DIR, (int)ourUid); } snprintf(userObjDbFilePath, sizeof(userObjDbFilePath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); snprintf(userDirectDbFilePath, sizeof(userDirectDbFilePath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); snprintf(userDbLockPath, sizeof(userDbLockPath), "%s/%s", userDbFileDir, MDS_LOCK_FILE_NAME); /* * Create the per-user directory...that's where the lock we'll be using lives. */ if(!isRoot) { if(createDir(userDbFileDir, 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. */ Syslog::alert("MDS Error: unable to create %s", userDbFileDir); 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, 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, userDirectDbFilePath, ourUid, true, userObjStat, userDirectStat)) { doCopySystem = true; } else { /* compare the two mdsDirectory.db files */ MSIoDbg("stat %s, %s in updateDataBases", MDS_DIRECT_DB_PATH, userDirectDbFilePath); 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); } } } if(doCopySystem) { /* 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); } } catch(const CssmError &cerror) { /* * Bad system DB file detected. Fatal. */ Syslog::alert("MDS Error: bad system DB file"); throw; } catch(...) { /* * Error on delete or create user DBs; fall back on system DBs. */ Syslog::alert("MDS Error: unable to create user DBs in %s", userDbFileDir); 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); 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); } /* 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) { 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 = "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? 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++) { 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); 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"); } } 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(); } // // 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); 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(makeCFURL(file)), CFTempString(subdir)); } // // 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