1/* 2 * Copyright (c) 2003-2005,2007-2010 Apple Inc. All Rights Reserved. 3 * 4 * @APPLE_LICENSE_HEADER_START@ 5 * 6 * This file contains Original Code and/or Modifications of Original Code 7 * as defined in and that are subject to the Apple Public Source License 8 * Version 2.0 (the 'License'). You may not use this file except in 9 * compliance with the License. Please obtain a copy of the License at 10 * http://www.opensource.apple.com/apsl/ and read it before using this 11 * file. 12 * 13 * The Original Code and all software distributed under the License are 14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER 15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, 16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, 17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. 18 * Please see the License for the specific language governing rights and 19 * limitations under the License. 20 * 21 * @APPLE_LICENSE_HEADER_END@ 22 * 23 * AuthorizationDBPlist.cpp 24 * Security 25 * 26 */ 27 28#include "AuthorizationDBPlist.h" 29#include <security_utilities/logging.h> 30#include <System/sys/fsctl.h> 31 32// mLock is held when the database is changed 33// mReadWriteLock is held when the file on disk is changed 34// during load(), save() and parseConfig() mLock is assumed 35 36namespace Authorization { 37 38AuthorizationDBPlist::AuthorizationDBPlist(const char *configFile) : 39 mFileName(configFile), mLastChecked(DBL_MIN) 40{ 41 memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec)); 42} 43 44void AuthorizationDBPlist::sync(CFAbsoluteTime now) 45{ 46 if (mRules.empty()) { 47 StLock<Mutex> _(mLock); 48 load(); 49 } else { 50 // Don't do anything if we checked the timestamp less than 5 seconds ago 51 if (mLastChecked > now - 5.0) { 52 secdebug("authdb", "no sync: last reload %.0f + 5 > %.0f", 53 mLastChecked, now); 54 return; 55 } 56 57 { 58 struct stat st; 59 { 60 StLock<Mutex> _(mReadWriteLock); 61 if (stat(mFileName.c_str(), &st)) { 62 Syslog::error("Stating rules file \"%s\": %s", mFileName.c_str(), 63 strerror(errno)); 64 return; 65 } 66 } 67 68 if (memcmp(&st.st_mtimespec, &mRulesFileMtimespec, sizeof(mRulesFileMtimespec))) { 69 StLock<Mutex> _(mLock); 70 load(); 71 } 72 } 73 } 74} 75 76void AuthorizationDBPlist::save() 77{ 78 if (!mConfig) 79 return; 80 81 StLock<Mutex> _(mReadWriteLock); 82 83 secdebug("authdb", "policy db changed, saving to disk."); 84 int fd = -1; 85 string tempFile = mFileName + ","; 86 87 for (;;) { 88 fd = open(tempFile.c_str(), O_WRONLY|O_CREAT|O_EXCL, 0644); 89 if (fd == -1) { 90 if (errno == EEXIST) { 91 unlink(tempFile.c_str()); 92 continue; 93 } 94 if (errno == EINTR) 95 continue; 96 else 97 break; 98 } else 99 break; 100 } 101 102 if (fd == -1) { 103 Syslog::error("Saving rules file \"%s\": %s", tempFile.c_str(), 104 strerror(errno)); 105 return; 106 } 107 108 CFDataRef configXML = CFPropertyListCreateXMLData(NULL, mConfig); 109 if (!configXML) 110 return; 111 112 CFIndex configSize = CFDataGetLength(configXML); 113 ssize_t bytesWritten = write(fd, CFDataGetBytePtr(configXML), configSize); 114 CFRelease(configXML); 115 116 if (bytesWritten != configSize) { 117 if (bytesWritten == -1) 118 Syslog::error("Problem writing rules file \"%s\": (errno=%s)", 119 tempFile.c_str(), strerror(errno)); 120 else 121 Syslog::error("Problem writing rules file \"%s\": " 122 "only wrote %lu out of %ld bytes", 123 tempFile.c_str(), bytesWritten, configSize); 124 125 close(fd); 126 unlink(tempFile.c_str()); 127 } 128 else 129 { 130 if (-1 == fcntl(fd, F_FULLFSYNC, NULL)) 131 fsync(fd); 132 133 close(fd); 134 int fd2 = open (mFileName.c_str(), O_RDONLY); 135 if (rename(tempFile.c_str(), mFileName.c_str())) 136 { 137 close(fd2); 138 unlink(tempFile.c_str()); 139 } 140 else 141 { 142 /* force a sync to flush the journal */ 143 int flags = FSCTL_SYNC_WAIT|FSCTL_SYNC_FULLSYNC; 144 ffsctl(fd2, FSCTL_SYNC_VOLUME, &flags, sizeof(flags)); 145 close(fd2); 146 mLastChecked = CFAbsoluteTimeGetCurrent(); // we have the copy that's on disk now, so don't go loading it right away 147 } 148 } 149} 150 151void AuthorizationDBPlist::load() 152{ 153 StLock<Mutex> _(mReadWriteLock); 154 CFDictionaryRef configPlist; 155 156 secdebug("authdb", "(re)loading policy db from disk."); 157 int fd = open(mFileName.c_str(), O_RDONLY, 0); 158 if (fd == -1) { 159 Syslog::error("Problem opening rules file \"%s\": %s", 160 mFileName.c_str(), strerror(errno)); 161 return; 162 } 163 164 struct stat st; 165 if (fstat(fd, &st)) { 166 int error = errno; 167 close(fd); 168 UnixError::throwMe(error); 169 } 170 171 mRulesFileMtimespec = st.st_mtimespec; 172 off_t fileSize = st.st_size; 173 CFMutableDataRef xmlData = CFDataCreateMutable(NULL, fileSize); 174 CFDataSetLength(xmlData, fileSize); 175 void *buffer = CFDataGetMutableBytePtr(xmlData); 176 ssize_t bytesRead = read(fd, buffer, fileSize); 177 if (bytesRead != fileSize) { 178 if (bytesRead == -1) { 179 Syslog::error("Problem reading rules file \"%s\": %s", 180 mFileName.c_str(), strerror(errno)); 181 goto cleanup; 182 } 183 Syslog::error("Problem reading rules file \"%s\": " 184 "only read %ul out of %ul bytes", 185 bytesRead, fileSize, mFileName.c_str()); 186 goto cleanup; 187 } 188 189 CFStringRef errorString; 190 configPlist = reinterpret_cast<CFDictionaryRef>(CFPropertyListCreateFromXMLData(NULL, xmlData, kCFPropertyListMutableContainersAndLeaves, &errorString)); 191 192 if (!configPlist) { 193 char buffer[512]; 194 const char *error = CFStringGetCStringPtr(errorString, 195 kCFStringEncodingUTF8); 196 if (error == NULL) { 197 if (CFStringGetCString(errorString, buffer, 512, 198 kCFStringEncodingUTF8)) 199 error = buffer; 200 } 201 202 Syslog::error("Parsing rules file \"%s\": %s", 203 mFileName.c_str(), error); 204 if (errorString) 205 CFRelease(errorString); 206 207 goto cleanup; 208 } 209 210 if (CFGetTypeID(configPlist) != CFDictionaryGetTypeID()) { 211 212 Syslog::error("Rules file \"%s\": is not a dictionary", 213 mFileName.c_str()); 214 215 goto cleanup; 216 } 217 218 parseConfig(configPlist); 219 220cleanup: 221 if (xmlData) 222 CFRelease(xmlData); 223 if (configPlist) 224 CFRelease(configPlist); 225 226 close(fd); 227 228 // If all went well, we have the copy that's on disk now, so don't go loading it right away 229 mLastChecked = CFAbsoluteTimeGetCurrent(); 230} 231 232void AuthorizationDBPlist::parseConfig(CFDictionaryRef config) 233{ 234 CFStringRef rightsKey = CFSTR("rights"); 235 CFStringRef rulesKey = CFSTR("rules"); 236 CFMutableDictionaryRef newRights = NULL; 237 CFMutableDictionaryRef newRules = NULL; 238 239 if (!config) 240 { 241 Syslog::alert("Failed to parse config, no config"); 242 MacOSError::throwMe(errAuthorizationInternal); 243 } 244 245 if (CFDictionaryContainsKey(config, rulesKey)) 246 newRules = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(config, rulesKey))); 247 248 if (CFDictionaryContainsKey(config, rightsKey)) 249 newRights = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(config, rightsKey))); 250 251 if (newRules && newRights 252 && (CFDictionaryGetTypeID() == CFGetTypeID(newRules)) 253 && (CFDictionaryGetTypeID() == CFGetTypeID(newRights))) 254 { 255 mConfigRights = static_cast<CFMutableDictionaryRef>(newRights); 256 mConfigRules = static_cast<CFMutableDictionaryRef>(newRules); 257 mRules.clear(); 258 try { 259 CFDictionaryApplyFunction(newRights, parseRule, this); 260 } catch (...) { 261 Syslog::alert("Failed to parse config and apply dictionary function"); 262 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file 263 } 264 mConfig = config; 265 } 266 else 267 { 268 Syslog::alert("Failed to parse config, invalid rule file"); 269 MacOSError::throwMe(errAuthorizationInternal); // XXX/cs invalid rule file 270 } 271} 272 273void AuthorizationDBPlist::parseRule(const void *key, const void *value, void *context) 274{ 275 static_cast<AuthorizationDBPlist*>(context)->addRight(static_cast<CFStringRef>(key), static_cast<CFDictionaryRef>(value)); 276} 277 278void AuthorizationDBPlist::addRight(CFStringRef key, CFDictionaryRef definition) 279{ 280 string keyString = cfString(key); 281 mRules[keyString] = Rule(keyString, definition, mConfigRules); 282} 283 284bool 285AuthorizationDBPlist::validateRule(string inRightName, CFDictionaryRef inRightDefinition) const 286{ 287 if (!mConfigRules || 288 0 == CFDictionaryGetCount(mConfigRules)) { 289 Syslog::error("No rule definitions!"); 290 MacOSError::throwMe(errAuthorizationInternal); 291 } 292 try { 293 Rule newRule(inRightName, inRightDefinition, mConfigRules); 294 if (newRule->name() == inRightName) 295 return true; 296 } catch (...) { 297 secdebug("authrule", "invalid definition for rule %s.\n", 298 inRightName.c_str()); 299 } 300 return false; 301} 302 303CFDictionaryRef 304AuthorizationDBPlist::getRuleDefinition(string &key) 305{ 306 if (!mConfigRights || 307 0 == CFDictionaryGetCount(mConfigRights)) { 308 Syslog::error("No rule definitions!"); 309 MacOSError::throwMe(errAuthorizationInternal); 310 } 311 CFStringRef cfKey = makeCFString(key); 312 StLock<Mutex> _(mLock); 313 if (CFDictionaryContainsKey(mConfigRights, cfKey)) { 314 CFDictionaryRef definition = reinterpret_cast<CFMutableDictionaryRef>(const_cast<void*>(CFDictionaryGetValue(mConfigRights, cfKey))); 315 CFRelease(cfKey); 316 return CFDictionaryCreateCopy(NULL, definition); 317 } else { 318 CFRelease(cfKey); 319 return NULL; 320 } 321} 322 323bool 324AuthorizationDBPlist::existRule(string &ruleName) const 325{ 326 AuthItemRef candidateRule(ruleName.c_str()); 327 string ruleForCandidate = getRule(candidateRule)->name(); 328 // same name or covered by wildcard right -> modification. 329 if ( (ruleName == ruleForCandidate) || 330 (*(ruleForCandidate.rbegin()) == '.') ) 331 return true; 332 333 return false; 334} 335 336Rule 337AuthorizationDBPlist::getRule(const AuthItemRef &inRight) const 338{ 339 string key(inRight->name()); 340 // Lock the rulemap 341 StLock<Mutex> _(mLock); 342 343 secdebug("authdb", "looking up rule %s.", inRight->name()); 344 if (mRules.empty()) 345 return Rule(); 346 347 for (;;) { 348 map<string,Rule>::const_iterator rule = mRules.find(key); 349 350 if (rule != mRules.end()) 351 return (*rule).second; 352 353 // no default rule 354 assert (key.size()); 355 356 // any reduction of a combination of two chars is futile 357 if (key.size() > 2) { 358 // find last dot with exception of possible dot at end 359 string::size_type index = key.rfind('.', key.size() - 2); 360 // cut right after found dot, or make it match default rule 361 key = key.substr(0, index == string::npos ? 0 : index + 1); 362 } else 363 key.erase(); 364 } 365} 366 367void 368AuthorizationDBPlist::setRule(const char *inRightName, CFDictionaryRef inRuleDefinition) 369{ 370 // if mConfig is now a reasonable guard 371 if (!inRuleDefinition || !mConfigRights) 372 { 373 Syslog::alert("Failed to set rule, no definition or rights"); 374 MacOSError::throwMe(errAuthorizationDenied); // ???/gh errAuthorizationInternal instead? 375 } 376 377 CFRef<CFStringRef> keyRef(CFStringCreateWithCString(NULL, inRightName, 378 kCFStringEncodingASCII)); 379 if (!keyRef) 380 return; 381 382 { 383 StLock<Mutex> _(mLock); 384 secdebug("authdb", "setting up rule %s.", inRightName); 385 CFDictionarySetValue(mConfigRights, keyRef, inRuleDefinition); 386 save(); 387 parseConfig(mConfig); 388 } 389} 390 391void 392AuthorizationDBPlist::removeRule(const char *inRightName) 393{ 394 // if mConfig is now a reasonable guard 395 if (!mConfigRights) 396 { 397 Syslog::alert("Failed to remove rule, no rights"); 398 MacOSError::throwMe(errAuthorizationDenied); // ???/gh errAuthorizationInternal instead? 399 } 400 401 CFRef<CFStringRef> keyRef(CFStringCreateWithCString(NULL, inRightName, 402 kCFStringEncodingASCII)); 403 if (!keyRef) 404 return; 405 406 { 407 StLock<Mutex> _(mLock); 408 secdebug("authdb", "removing rule %s.", inRightName); 409 CFDictionaryRemoveValue(mConfigRights, keyRef); 410 save(); 411 parseConfig(mConfig); 412 } 413} 414 415 416} // end namespace Authorization 417