]>
Commit | Line | Data |
---|---|---|
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 | ||
36 | namespace Authorization { | |
37 | ||
38 | AuthorizationDBPlist::AuthorizationDBPlist(const char *configFile) : | |
39 | mFileName(configFile), mLastChecked(DBL_MIN) | |
40 | { | |
41 | memset(&mRulesFileMtimespec, 0, sizeof(mRulesFileMtimespec)); | |
42 | } | |
43 | ||
44 | void 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 | ||
76 | void 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 | ||
151 | void 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 | ||
220 | cleanup: | |
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 | ||
232 | void 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 | ||
273 | void 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 | ||
278 | void AuthorizationDBPlist::addRight(CFStringRef key, CFDictionaryRef definition) | |
279 | { | |
280 | string keyString = cfString(key); | |
281 | mRules[keyString] = Rule(keyString, definition, mConfigRules); | |
282 | } | |
283 | ||
284 | bool | |
285 | AuthorizationDBPlist::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 | ||
303 | CFDictionaryRef | |
304 | AuthorizationDBPlist::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 | ||
323 | bool | |
324 | AuthorizationDBPlist::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 | ||
336 | Rule | |
337 | AuthorizationDBPlist::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 | ||
367 | void | |
368 | AuthorizationDBPlist::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 | ||
391 | void | |
392 | AuthorizationDBPlist::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 |