--- /dev/null
+/*
+ * 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 <security_utilities/logging.h>
+#include <System/sys/fsctl.h>
+
+// 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<Mutex> _(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<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
+ {
+ /* 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<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);
+
+ // 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<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); // 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<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();
+ // 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<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;
+
+ // 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<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 mConfig is now a reasonable guard
+ if (!mConfigRights)
+ {
+ Syslog::alert("Failed to remove rule, no rights");
+ MacOSError::throwMe(errAuthorizationDenied); // ???/gh errAuthorizationInternal instead?
+ }
+
+ 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);
+ }
+}
+
+
+} // end namespace Authorization