/* * 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