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