AuthorizationDBPlist.cpp [plain text]
#include "AuthorizationDBPlist.h"
#include <security_utilities/logging.h>
#include <System/sys/fsctl.h>
namespace Authorization {
AuthorizationDBPlist::AuthorizationDBPlist(const char *configFile) :
mFileName(configFile), mLastChecked(DBL_MIN)
{
memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec));
}
void AuthorizationDBPlist::sync(CFAbsoluteTime now)
{
if (mRules.empty()) {
StLock<Mutex> _(mLock);
load();
} else {
if (mLastChecked > now - 5.0) {
secdebug("authdb", "no sync: last reload %.0f + 5 > %.0f",
mLastChecked, now);
return;
}
{
struct stat st;
{
StLock<Mutex> _(mReadWriteLock);
if (stat(mFileName.c_str(), &st)) {
Syslog::error("Stating rules file \"%s\": %s", mFileName.c_str(),
strerror(errno));
return;
}
}
if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec))) {
StLock<Mutex> _(mLock);
load();
}
}
}
}
void AuthorizationDBPlist::save()
{
if (!mConfig)
return;
StLock<Mutex> _(mReadWriteLock);
secdebug("authdb", "policy db changed, saving to disk.");
int fd = -1;
string tempFile = mFileName + ",";
for (;;) {
fd = open(tempFile.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0644);
if (fd == -1) {
if (errno == EEXIST) {
unlink(tempFile.c_str());
continue;
}
if (errno == EINTR)
continue;
else
break;
} else
break;
}
if (fd == -1) {
Syslog::error("Saving rules file \"%s\": %s", tempFile.c_str(),
strerror(errno));
return;
}
CFDataRef configXML = CFPropertyListCreateXMLData(NULL, mConfig);
if (!configXML)
return;
CFIndex configSize = CFDataGetLength(configXML);
ssize_t bytesWritten = write(fd, CFDataGetBytePtr(configXML), configSize);
CFRelease(configXML);
if (bytesWritten != configSize) {
if (bytesWritten == -1)
Syslog::error("Problem writing rules file \"%s\": (errno=%s)",
tempFile.c_str(), strerror(errno));
else
Syslog::error("Problem writing rules file \"%s\": "
"only wrote %lu out of %ld bytes",
tempFile.c_str(), bytesWritten, configSize);
close(fd);
unlink(tempFile.c_str());
}
else
{
if (-1 == fcntl(fd, F_FULLFSYNC, NULL))
fsync(fd);
close(fd);
int fd2 = open (mFileName.c_str(), O_RDONLY);
if (rename(tempFile.c_str(), mFileName.c_str()))
{
close(fd2);
unlink(tempFile.c_str());
}
else
{
int flags = FSCTL_SYNC_WAIT|FSCTL_SYNC_FULLSYNC;
ffsctl(fd2, FSCTL_SYNC_VOLUME, &flags, sizeof(flags));
close(fd2);
mLastChecked = CFAbsoluteTimeGetCurrent(); }
}
}
void AuthorizationDBPlist::load()
{
StLock<Mutex> _(mReadWriteLock);
CFDictionaryRef configPlist;
secdebug("authdb", "(re)loading policy db from disk.");
int fd = open(mFileName.c_str(), O_RDONLY, 0);
if (fd == -1) {
Syslog::error("Problem opening rules file \"%s\": %s",
mFileName.c_str(), strerror(errno));
return;
}
struct stat st;
if (fstat(fd, &st)) {
int error = errno;
close(fd);
UnixError::throwMe(error);
}
mRulesFileMtimespec = st.st_mtimespec;
off_t fileSize = st.st_size;
CFMutableDataRef xmlData = CFDataCreateMutable(NULL, fileSize);
CFDataSetLength(xmlData, fileSize);
void *buffer = CFDataGetMutableBytePtr(xmlData);
ssize_t bytesRead = read(fd, buffer, fileSize);
if (bytesRead != fileSize) {
if (bytesRead == -1) {
Syslog::error("Problem reading rules file \"%s\": %s",
mFileName.c_str(), strerror(errno));
goto cleanup;
}
Syslog::error("Problem reading rules file \"%s\": "
"only read %ul out of %ul bytes",
bytesRead, fileSize, mFileName.c_str());
goto cleanup;
}
CFStringRef errorString;
configPlist = reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListMutableContainersAndLeaves, &errorString));
if (!configPlist) {
char buffer[512];
const char *error = CFStringGetCStringPtr(errorString,
kCFStringEncodingUTF8);
if (error == NULL) {
if (CFStringGetCString(errorString, buffer, 512,
kCFStringEncodingUTF8))
error = buffer;
}
Syslog::error("Parsing rules file \"%s\": %s",
mFileName.c_str(), error);
if (errorString)
CFRelease(errorString);
goto cleanup;
}
if (CFGetTypeID(configPlist) != CFDictionaryGetTypeID()) {
Syslog::error("Rules file \"%s\": is not a dictionary",
mFileName.c_str());
goto cleanup;
}
parseConfig(configPlist);
cleanup:
if (xmlData)
CFRelease(xmlData);
if (configPlist)
CFRelease(configPlist);
close(fd);
mLastChecked = CFAbsoluteTimeGetCurrent();
}
void AuthorizationDBPlist::parseConfig(CFDictionaryRef config)
{
CFStringRef rightsKey = CFSTR("rights");
CFStringRef rulesKey = CFSTR("rules");
CFMutableDictionaryRef newRights = NULL;
CFMutableDictionaryRef newRules = NULL;
if (!config)
{
Syslog::alert("Failed to parse config, no config");
MacOSError::throwMe(errAuthorizationInternal);
}
if (CFDictionaryContainsKey(config, rulesKey))
newRules = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(config, rulesKey)));
if (CFDictionaryContainsKey(config, rightsKey))
newRights = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(config, rightsKey)));
if (newRules && newRights
&& (CFDictionaryGetTypeID() == CFGetTypeID(newRules))
&& (CFDictionaryGetTypeID() == CFGetTypeID(newRights)))
{
mConfigRights = static_cast<CFMutableDictionaryRef>(newRights);
mConfigRules = static_cast<CFMutableDictionaryRef>(newRules);
mRules.clear();
try {
CFDictionaryApplyFunction(newRights, parseRule, this);
} catch (...) {
Syslog::alert("Failed to parse config and apply dictionary function");
MacOSError::throwMe(errAuthorizationInternal); }
mConfig = config;
}
else
{
Syslog::alert("Failed to parse config, invalid rule file");
MacOSError::throwMe(errAuthorizationInternal); }
}
void AuthorizationDBPlist::parseRule(const void *key, const void *value, void *context)
{
static_cast<AuthorizationDBPlist*>(context)->addRight(static_cast<CFStringRef>(key), static_cast<CFDictionaryRef>(value));
}
void AuthorizationDBPlist::addRight(CFStringRef key, CFDictionaryRef definition)
{
string keyString = cfString(key);
mRules[keyString] = Rule(keyString, definition, mConfigRules);
}
bool
AuthorizationDBPlist::validateRule(string inRightName, CFDictionaryRef inRightDefinition) const
{
if (!mConfigRules ||
0 == CFDictionaryGetCount(mConfigRules)) {
Syslog::error("No rule definitions!");
MacOSError::throwMe(errAuthorizationInternal);
}
try {
Rule newRule(inRightName, inRightDefinition, mConfigRules);
if (newRule->name() == inRightName)
return true;
} catch (...) {
secdebug("authrule", "invalid definition for rule %s.\n",
inRightName.c_str());
}
return false;
}
CFDictionaryRef
AuthorizationDBPlist::getRuleDefinition(string &key)
{
if (!mConfigRights ||
0 == CFDictionaryGetCount(mConfigRights)) {
Syslog::error("No rule definitions!");
MacOSError::throwMe(errAuthorizationInternal);
}
CFStringRef cfKey = makeCFString(key);
StLock<Mutex> _(mLock);
if (CFDictionaryContainsKey(mConfigRights, cfKey)) {
CFDictionaryRef definition = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(mConfigRights, cfKey)));
CFRelease(cfKey);
return CFDictionaryCreateCopy(NULL, definition);
} else {
CFRelease(cfKey);
return NULL;
}
}
bool
AuthorizationDBPlist::existRule(string &ruleName) const
{
AuthItemRef candidateRule(ruleName.c_str());
string ruleForCandidate = getRule(candidateRule)->name();
if ( (ruleName == ruleForCandidate) ||
(*(ruleForCandidate.rbegin()) == '.') )
return true;
return false;
}
Rule
AuthorizationDBPlist::getRule(const AuthItemRef &inRight) const
{
string key(inRight->name());
StLock<Mutex> _(mLock);
secdebug("authdb", "looking up rule %s.", inRight->name());
if (mRules.empty())
return Rule();
for (;;) {
map<string,Rule>::const_iterator rule = mRules.find(key);
if (rule != mRules.end())
return (*rule).second;
assert (key.size());
if (key.size() > 2) {
string::size_type index = key.rfind('.', key.size() - 2);
key = key.substr(0, index == string::npos ? 0 : index + 1);
} else
key.erase();
}
}
void
AuthorizationDBPlist::setRule(const char *inRightName, CFDictionaryRef inRuleDefinition)
{
if (!inRuleDefinition || !mConfigRights)
{
Syslog::alert("Failed to set rule, no definition or rights");
MacOSError::throwMe(errAuthorizationDenied); }
CFRef<CFStringRef> keyRef(CFStringCreateWithCString(NULL, inRightName,
kCFStringEncodingASCII));
if (!keyRef)
return;
{
StLock<Mutex> _(mLock);
secdebug("authdb", "setting up rule %s.", inRightName);
CFDictionarySetValue(mConfigRights, keyRef, inRuleDefinition);
save();
parseConfig(mConfig);
}
}
void
AuthorizationDBPlist::removeRule(const char *inRightName)
{
if (!mConfigRights)
{
Syslog::alert("Failed to remove rule, no rights");
MacOSError::throwMe(errAuthorizationDenied); }
CFRef<CFStringRef> keyRef(CFStringCreateWithCString(NULL, inRightName,
kCFStringEncodingASCII));
if (!keyRef)
return;
{
StLock<Mutex> _(mLock);
secdebug("authdb", "removing rule %s.", inRightName);
CFDictionaryRemoveValue(mConfigRights, keyRef);
save();
parseConfig(mConfig);
}
}
}