X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/securityd/src/AuthorizationDBPlist.cpp diff --git a/securityd/src/AuthorizationDBPlist.cpp b/securityd/src/AuthorizationDBPlist.cpp new file mode 100644 index 00000000..65a6ffa7 --- /dev/null +++ b/securityd/src/AuthorizationDBPlist.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (c) 2003-2005,2007-2010 Apple Inc. All Rights Reserved. + * + * @APPLE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this + * file. + * + * The 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. + * + * @APPLE_LICENSE_HEADER_END@ + * + * AuthorizationDBPlist.cpp + * Security + * + */ + +#include "AuthorizationDBPlist.h" +#include +#include + +// mLock is held when the database is changed +// mReadWriteLock is held when the file on disk is changed +// during load(), save() and parseConfig() mLock is assumed + +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 _(mLock); + load(); + } else { + // Don't do anything if we checked the timestamp less than 5 seconds ago + if (mLastChecked > now - 5.0) { + secdebug("authdb", "no sync: last reload %.0f + 5 > %.0f", + mLastChecked, now); + return; + } + + { + struct stat st; + { + StLock _(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 _(mLock); + load(); + } + } + } +} + +void AuthorizationDBPlist::save() +{ + if (!mConfig) + return; + + StLock _(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 + { + /* force a sync to flush the journal */ + int flags = FSCTL_SYNC_WAIT|FSCTL_SYNC_FULLSYNC; + ffsctl(fd2, FSCTL_SYNC_VOLUME, &flags, sizeof(flags)); + close(fd2); + mLastChecked = CFAbsoluteTimeGetCurrent(); // we have the copy that's on disk now, so don't go loading it right away + } + } +} + +void AuthorizationDBPlist::load() +{ + StLock _(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(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); + + // If all went well, we have the copy that's on disk now, so don't go loading it right away + 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(const_cast(CFDictionaryGetValue(config, rulesKey))); + + if (CFDictionaryContainsKey(config, rightsKey)) + newRights = reinterpret_cast(const_cast(CFDictionaryGetValue(config, rightsKey))); + + if (newRules && newRights + && (CFDictionaryGetTypeID() == CFGetTypeID(newRules)) + && (CFDictionaryGetTypeID() == CFGetTypeID(newRights))) + { + mConfigRights = static_cast(newRights); + mConfigRules = static_cast(newRules); + mRules.clear(); + try { + CFDictionaryApplyFunction(newRights, parseRule, this); + } catch (...) { + Syslog::alert("Failed to parse config and apply dictionary function"); + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file + } + mConfig = config; + } + else + { + Syslog::alert("Failed to parse config, invalid rule file"); + MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file + } +} + +void AuthorizationDBPlist::parseRule(const void *key, const void *value, void *context) +{ + static_cast(context)->addRight(static_cast(key), static_cast(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 _(mLock); + if (CFDictionaryContainsKey(mConfigRights, cfKey)) { + CFDictionaryRef definition = reinterpret_cast(const_cast(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(); + // same name or covered by wildcard right -> modification. + if ( (ruleName == ruleForCandidate) || + (*(ruleForCandidate.rbegin()) == '.') ) + return true; + + return false; +} + +Rule +AuthorizationDBPlist::getRule(const AuthItemRef &inRight) const +{ + string key(inRight->name()); + // Lock the rulemap + StLock _(mLock); + + secdebug("authdb", "looking up rule %s.", inRight->name()); + if (mRules.empty()) + return Rule(); + + for (;;) { + map::const_iterator rule = mRules.find(key); + + if (rule != mRules.end()) + return (*rule).second; + + // no default rule + assert (key.size()); + + // any reduction of a combination of two chars is futile + if (key.size() > 2) { + // find last dot with exception of possible dot at end + string::size_type index = key.rfind('.', key.size() - 2); + // cut right after found dot, or make it match default rule + key = key.substr(0, index == string::npos ? 0 : index + 1); + } else + key.erase(); + } +} + +void +AuthorizationDBPlist::setRule(const char *inRightName, CFDictionaryRef inRuleDefinition) +{ + // if mConfig is now a reasonable guard + if (!inRuleDefinition || !mConfigRights) + { + Syslog::alert("Failed to set rule, no definition or rights"); + MacOSError::throwMe(errAuthorizationDenied); // ???/gh errAuthorizationInternal instead? + } + + CFRef keyRef(CFStringCreateWithCString(NULL, inRightName, + kCFStringEncodingASCII)); + if (!keyRef) + return; + + { + StLock _(mLock); + secdebug("authdb", "setting up rule %s.", inRightName); + CFDictionarySetValue(mConfigRights, keyRef, inRuleDefinition); + save(); + parseConfig(mConfig); + } +} + +void +AuthorizationDBPlist::removeRule(const char *inRightName) +{ + // if mConfig is now a reasonable guard + if (!mConfigRights) + { + Syslog::alert("Failed to remove rule, no rights"); + MacOSError::throwMe(errAuthorizationDenied); // ???/gh errAuthorizationInternal instead? + } + + CFRef keyRef(CFStringCreateWithCString(NULL, inRightName, + kCFStringEncodingASCII)); + if (!keyRef) + return; + + { + StLock _(mLock); + secdebug("authdb", "removing rule %s.", inRightName); + CFDictionaryRemoveValue(mConfigRights, keyRef); + save(); + parseConfig(mConfig); + } +} + + +} // end namespace Authorization