#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;
namespace Security
{
#define MDS_SYSTEM_PATH "/System/Library/Frameworks"
#define MDS_SYSTEM_FRAME "Security.framework"
#define MDS_BUNDLE_PATH "/System/Library/Security"
#define MDS_BUNDLE_EXTEN ".bundle"
#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
#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
#define MDS_USER_BUNDLE "Library/Security"
#define DB_LOCK_TIMEOUT (2 * 1000)
#define MDS_SCAN_INTERVAL 5
#define MSIoDbg(args...) secdebug("MDS_IO", ## args)
#define MSCleanDirDbg(args...) secdebug("MDS_CleanDir", ## args)
static std::string GetMDSBaseDBDir(bool isRoot)
{
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)
{
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;
}
static int cleanDir(
const char *dirPath,
const char **keepFileNames, 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;
do {
errno = 0;
dp = readdir(dirp);
if(dp == NULL) {
rtn = errno;
if(rtn) {
MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath, rtn);
}
break;
}
d_name = dp->d_name;
if( (d_name[0] == '.') &&
( (d_name[1] == '\0') ||
( (d_name[1] == '.') && (d_name[2] == '\0') ) ) ) {
skip = true;
break;
}
for(unsigned dex=0; dex<numKeepFileNames; dex++) {
if(!strcmp(keepFileNames[dex], d_name)) {
skip = true;
break;
}
}
} while(0);
if(rtn || (dp == NULL)) {
break;
}
if(skip) {
continue;
}
snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, d_name);
if(dp->d_type == DT_DIR) {
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;
}
}
closedir(dirp);
if ((dirp = opendir(dirPath)) == NULL) {
rtn = errno;
MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn);
return rtn;
}
}
closedir(dirp);
return rtn;
}
static bool doesFileExist(
const char *filePath,
uid_t forUid,
bool purge,
struct stat &sb) {
MSIoDbg("stat %s in doesFileExist", filePath);
if(lstat(filePath, &sb)) {
if(errno == ENOENT) {
return false;
}
if(purge) {
CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
}
return false;
}
mode_t fileType = sb.st_mode & S_IFMT;
if((fileType == S_IFREG) && (sb.st_uid == forUid)) {
return true;
}
if(!purge) {
return false;
}
if(fileType == S_IFDIR) {
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);
}
}
return false;
}
static bool doFilesExist(
const char *objDbFile,
const char *directDbFile,
uid_t forUid,
bool purge, struct stat &objDbSb, struct stat &directDbSb)
{
bool objectExist = doesFileExist(objDbFile, forUid, purge, objDbSb);
bool directExist = doesFileExist(directDbFile, forUid, purge, directDbSb);
if(objectExist && directExist) {
return true;
}
else if(!purge) {
return false;
}
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;
}
typedef enum {
MDS_NotPresent,
MDS_NotDirectory,
MDS_BadOwnerMode,
MDS_Access
} MdsDirectStatus;
static bool doesDirectExist(
const char *dirPath,
uid_t forUid,
mode_t mode,
MdsDirectStatus &directStatus)
{
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;
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;
}
static int createDir(
const char *dirPath,
uid_t forUid, mode_t dirMode)
{
MdsDirectStatus directStatus;
if(doesDirectExist(dirPath, forUid, dirMode, directStatus)) {
return 0;
}
int rtn;
switch(directStatus) {
case MDS_NotPresent:
break;
case MDS_NotDirectory:
if(unlink(dirPath)) {
rtn = errno;
MSDebug("createDir(%s): unlink() returned %d", dirPath, rtn);
return rtn;
}
break;
case MDS_BadOwnerMode:
rtn = cleanDir(dirPath, NULL, 0);
if(rtn) {
return rtn;
}
if(rmdir(dirPath)) {
rtn = errno;
MSDebug("createDir(%s): rmdir() returned %d", dirPath, rtn);
return rtn;
}
break;
case MDS_Access:
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 {
rtn = chmod(dirPath, dirMode);
if(rtn) {
MSDebug("chmod(%s) returned %d", dirPath, errno);
}
}
return rtn;
}
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";
void
MDSSession::install ()
{
if(geteuid() != (uid_t)0) {
CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
}
mModule.setServerMode();
try {
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);
}
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);
}
const char *savedFile = MDS_INSTALL_LOCK_NAME;
if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) {
CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
}
const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath};
if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) {
CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
}
createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE);
DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH);
}
catch(...) {
throw;
}
}
void
MDSSession::uninstall ()
{
CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
}
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, 0 };
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, CSSM_DB_ACCESS_READ,
NULL, batched ? &batchOpenParams : NULL,
dbHand);
return dbHand;
}
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()) {
SecurityServer::ClientSession client(Allocator::standard(), Allocator::standard());
client.activate();
}
updateDataBases();
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);
}
bool
MDSSession::LockHelper::obtainLock(
const char *lockFile, int timeout) {
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) {
continue;
}
else {
return false;
}
}
else {
secdebug("mdslock", "obtainLock: success");
return true;
}
}
return false;
}
MDSSession::LockHelper::~LockHelper()
{
secdebug("mdslock", "releaseLock");
if (mFD == -1)
{
return;
}
flock(mFD, LOCK_UN);
close(mFD);
mFD = -1;
}
void MDSSession::dbFullPath(
const char *dbName,
char fullPath[MAXPATHLEN+1])
{
mModule.getDbPath(fullPath);
assert(fullPath[0] != '\0');
strcat(fullPath, "/");
strcat(fullPath, dbName);
}
static bool isBundle(
const struct dirent *dp)
{
if(dp == NULL) {
return false;
}
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);
}
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)) {
rtn = true;
break;
}
}
closedir(dir);
MSDebug("...%s bundle(s) found", rtn ? "" : "No");
return rtn;
}
#define COPY_BUF_SIZE 1024
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);
}
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 {
if(fchmod(destFd, toMode)) {
error = errno;
MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath);
UnixError::throwMe(error);
}
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);
}
fl.l_start = 0;
fl.l_len = 1;
fl.l_pid = getpid();
fl.l_type = F_RDLCK; fl.l_whence = SEEK_SET;
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;
}
}
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(...) {
}
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) {
if(::rename(tmpToPath, toPath)) {
error = errno;
MSDebug("Error %d committing %s\n", error, toPath);
}
}
if(error) {
UnixError::throwMe(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);
}
void MDSSession::updateDataBases()
{
RecursionBlock::Once once(mUpdating);
if (once())
return;
uid_t ourUid = geteuid();
bool isRoot = (ourUid == 0);
double delta = mModule.timeSinceLastScan();
if(delta < (double)MDS_SCAN_INTERVAL) {
return;
}
if(isRoot && !systemDatabasesPresent(false)) {
install();
mModule.setDbPath(MDS_SYSTEM_DB_DIR);
mModule.lastScanIsNow();
return;
}
std::string userDBFileDir = GetMDSDBDir();
std::string userObjDBFilePath = GetMDSObjectDBPath();
std::string userDirectDBFilePath = GetMDSDirectDBPath();
char userBundlePath[MAXPATHLEN+1];
std::string userDbLockPath = GetMDSDBLockPath();
userBundlePath[0] = '\0';
if(!isRoot) {
char *userHome = getenv("HOME");
if((userHome == NULL) ||
(strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) {
MSDebug("missing or invalid HOME; skipping user bundle check");
}
else {
snprintf(userBundlePath, sizeof(userBundlePath),
"%s/%s", userHome, MDS_USER_BUNDLE);
}
}
if(!isRoot) {
if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) {
MSDebug("Error creating user DBs; using system DBs");
mModule.setDbPath(MDS_SYSTEM_DB_DIR);
return;
}
}
LockHelper lh;
if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) {
CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
}
try {
if(!isRoot) {
try {
bool doCopySystem = false;
struct stat userObjStat, userDirectStat;
if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true,
userObjStat, userDirectStat)) {
doCopySystem = true;
}
else {
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) {
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) {
throw;
}
catch(...) {
MSDebug("doFilesExist(purge) error; using system DBs");
mModule.setDbPath(MDS_SYSTEM_DB_DIR);
return;
}
}
else {
MSDebug("Using system DBs only");
}
DbFilesInfo dbFiles(*this, userDBFileDir.c_str());
dbFiles.removeOutdatedPlugins();
dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH);
if(userBundlePath[0]) {
if(checkUserBundles(userBundlePath)) {
dbFiles.updateForBundleDir(userBundlePath);
}
}
mModule.setDbPath(userDBFileDir.c_str());
}
catch(...) {
throw;
}
mModule.lastScanIsNow();
}
void MDSSession::removeRecordsForGuid(
const char *guid,
CSSM_DB_HANDLE dbHand)
{
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, record);
if (resultHand == CSSM_INVALID_HANDLE)
return; 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);
}
}
bool MDSSession::systemDatabasesPresent(bool purge)
{
bool rtn = false;
try {
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;
}
void
MDSSession::createSystemDatabase(
const char *dbName,
const RelationInfo *relationInfo,
unsigned numRelations,
CSSM_BOOL autoCommit,
mode_t mode,
CSSM_DB_HANDLE &dbHand) {
CSSM_DBINFO dbInfo;
CSSM_DBINFO_PTR dbInfoP = &dbInfo;
memset(dbInfoP, 0, sizeof(CSSM_DBINFO));
dbInfoP->NumberOfRecordTypes = numRelations;
dbInfoP->IsLocal = CSSM_TRUE; dbInfoP->AccessPath = NULL;
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);
unsigned relation;
for(relation=0; relation<numRelations; relation++) {
const struct RelationInfo *relp = &relationInfo[relation]; CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
&dbInfoP->RecordAttributeNames[relation]; CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
&dbInfoP->RecordIndexes[relation];
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;
}
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, *dbInfoP,
CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
NULL, &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);
}
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)) {
MSDebug("system DBs already exist");
return false;
}
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;
}
MDSSession::DbFilesInfo::DbFilesInfo(
MDSSession &session,
const char *dbPath) :
mSession(session),
mObjDbHand(0),
mDirectDbHand(0),
mLaterTimestamp(0)
{
assert(strlen(dbPath) < MAXPATHLEN);
strcpy(mDbPath, dbPath);
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) {
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;
}
}
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); 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); return mDirectDbHand;
}
void MDSSession::DbFilesInfo::updateSystemDbInfo(
const char *systemPath, const char *bundlePath) {
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);
if (path.empty()) path = string(systemPath) + "/" MDS_SYSTEM_FRAME;
updateForBundle(path.c_str());
}
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';
}
}
void MDSSession::DbFilesInfo::checkOutdatedPlugin(
const CSSM_DATA &pathValue,
const CSSM_DATA &guidValue,
TbdVector &tbdVector)
{
struct stat sb;
bool obsolete = false;
string path = CssmData::overlay(pathValue).toString();
if (!path.empty() && path[0] == '*') {
return;
}
MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str());
int rtn = ::stat(path.c_str(), &sb);
if(rtn) {
obsolete = true;
}
else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
obsolete = true;
}
if(obsolete) {
TbdRecord *tbdRecord = new TbdRecord(guidValue);
tbdVector.push_back(tbdRecord);
MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str());
}
}
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;
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;
query.RecordType = MDS_OBJECT_RECORDTYPE;
query.Conjunctive = CSSM_DB_NONE;
query.NumSelectionPredicates = 0;
query.SelectionPredicate = NULL;
query.QueryLimits.TimeLimit = 0; query.QueryLimits.SizeLimit = 1; query.QueryFlags = 0;
CssmQuery perryQuery(query);
try {
resultHand = mSession.DataGetFirst(objDbHand(),
&perryQuery,
&recordAttrs,
NULL, 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 {
MSDebug("removeOutdatedPlugins: empty object DB");
return;
}
for(;;) {
bool brtn = mSession.DataGetNext(objDbHand(),
resultHand,
&recordAttrs,
NULL,
record);
if(!brtn) {
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);
}
}
}
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];
}
}
void MDSSession::DbFilesInfo::updateForBundleDir(
const char *bundleDirPath)
{
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);
}
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; query.QueryLimits.SizeLimit = 1; query.QueryFlags = 0;
bool ourRtn = true;
try {
CssmQuery perryQuery(query);
resultHand = mSession.DataGetFirst(objDbHand(),
&perryQuery,
&recordAttrs,
NULL, record);
}
catch (...) {
ourRtn = false;
}
if(record) {
mSession.FreeUniqueRecord(mObjDbHand, *record);
}
else {
ourRtn = false;
}
if(resultHand && ourRtn) {
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;
}
void MDSSession::DbFilesInfo::updateForBundle(
const char *bundlePath)
{
MSDebug("...updating DBs for bundle %s", bundlePath);
if(lookupForPath(bundlePath)) {
return;
}
MDSAttrParser parser(bundlePath,
mSession,
objDbHand(),
directDbHand());
try {
parser.parseAttrs();
}
catch (const CssmError &err) {
const char *guid = parser.guid();
if (guid) {
mSession.removeRecordsForGuid(guid, objDbHand());
mSession.removeRecordsForGuid(guid, directDbHand());
}
}
}
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) if (subdir) parser.parseAttrs(CFTempString(subdir));
else parser.parseAttrs(NULL);
else 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());
}
}
}
void MDSSession::removeSubservice(const char *guid, uint32 ssid)
{
DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
CssmClient::Query query =
Attribute("ModuleID") == guid &&
Attribute("SSID") == ssid;
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));
}
}