]> git.saurik.com Git - apple/security.git/blobdiff - securityd/src/AuthorizationDBPlist.cpp
Security-57031.1.35.tar.gz
[apple/security.git] / securityd / src / AuthorizationDBPlist.cpp
diff --git a/securityd/src/AuthorizationDBPlist.cpp b/securityd/src/AuthorizationDBPlist.cpp
new file mode 100644 (file)
index 0000000..65a6ffa
--- /dev/null
@@ -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 <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