1/*
2 * Copyright (c) 2002-2004,2011,2014 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
24/*
25 * simpleprefs.cpp - plist support for a bare bones Preferences implementation,
26 *                 using only Darwin-avaialble CoreFoundation classes.
27 */
28
29#include "simpleprefs.h"
30#include "errors.h"
31#include <sys/param.h>
32#include <stdlib.h>
33#include <assert.h>
34#include <stdexcept>
35#include <security_utilities/debugging.h>
36#include <CoreFoundation/CFData.h>
37#include <CoreFoundation/CFNumber.h>
38#include <CoreFoundation/CFURLAccess.h>
39#include <CoreFoundation/CFPropertyList.h>
40#include <sys/stat.h>
41
42#define prefsDebug(args...)		secdebug("simpleprefs", ## args)
43
44#define kSecUserPrefsDir			"Library/Preferences"		/* relative to $HOME */
45#define kSecSystemPrefsDir			"/Library/Preferences"
46
47#pragma mark ----- (immutable) Dictionary -----
48
49static void pathForDomain(const char *domain, Dictionary::UserOrSystem userSys, std::string &path)
50{
51	path.clear();
52	if(userSys == Dictionary::US_User) {
53		const char *home = getenv("HOME");
54		if(home == NULL) {
55			home = "";
56		}
57		path = std::string(home) + "/" + kSecUserPrefsDir + "/" + domain + ".plist";
58	}
59	else {
60		path = std::string(kSecSystemPrefsDir) + "/" + domain + ".plist";
61	}
62}
63
64static bool FileExists(const char* s)
65{
66	// this isn't very efficient, either, but orders are to get rid of exceptions...
67	struct stat st;
68	int result = stat(s, &st);
69	return result == 0;
70}
71
72// use factory functions to create the dictionaries so that we can test for the presence of the dictionaries
73// without throwing
74Dictionary* Dictionary::CreateDictionary(const char* path)
75{
76	if (!FileExists(path))
77	{
78		return NULL;
79	}
80	else
81	{
82		return new Dictionary(path);
83	}
84}
85
86Dictionary* Dictionary::CreateDictionary(const char* domain, UserOrSystem userSys, bool loose)
87{
88	std::string path;
89	pathForDomain(domain, userSys, path);
90	bool exists = FileExists(path.c_str());
91	if (!loose && !exists)
92	{
93		return NULL;
94	}
95
96	if (!exists)
97	{
98		return new Dictionary();
99	}
100
101	return new Dictionary(path.c_str());
102}
103
104Dictionary::Dictionary() : mDict(NULL)
105{
106}
107
108Dictionary::Dictionary(
109	const char		*path)
110		: mDict(NULL)
111{
112	initFromFile(path);
113}
114
115Dictionary::Dictionary(
116	CFDictionaryRef	dict)
117		: mDict(dict)
118{
119	if (mDict)
120		CFRetain(mDict);
121}
122
123Dictionary::~Dictionary()
124{
125	if(mDict) {
126		CFRelease(mDict);
127	}
128}
129
130/* basic lookup */
131const void *Dictionary::getValue(
132	CFStringRef key)
133{
134	return CFDictionaryGetValue(dict(), key);
135}
136
137/* lookup, value must be CFString (we check) */
138CFStringRef Dictionary::getStringValue(
139	CFStringRef	key)
140{
141	CFStringRef val = (CFStringRef)CFDictionaryGetValue(dict(), key);
142	if(val == NULL) {
143		return NULL;
144	}
145	if(CFGetTypeID(val) != CFStringGetTypeID()) {
146		return NULL;
147	}
148	return val;
149}
150
151/* lookup, value must be CFData (we check) */
152CFDataRef Dictionary::getDataValue(
153	CFStringRef	key)
154{
155	CFDataRef val = (CFDataRef)CFDictionaryGetValue(dict(), key);
156	if(val == NULL) {
157		return NULL;
158	}
159	if(CFGetTypeID(val) != CFDataGetTypeID()) {
160		return NULL;
161	}
162	return val;
163}
164
165/* lookup, value must be CFDictionary (we check) */
166CFDictionaryRef Dictionary::getDictValue(
167	CFStringRef key)
168{
169	CFDictionaryRef val = (CFDictionaryRef)CFDictionaryGetValue(dict(), key);
170	if(val == NULL) {
171		return NULL;
172	}
173	if(CFGetTypeID(val) != CFDictionaryGetTypeID()) {
174		return NULL;
175	}
176	return val;
177}
178
179/*
180 * Lookup, value is a dictionary, we return value as Dictionary
181 * if found, else return NULL.
182 */
183Dictionary *Dictionary::copyDictValue(
184	CFStringRef key)
185{
186	CFDictionaryRef cfDict = getDictValue(key);
187	if(cfDict == NULL) {
188		return NULL;
189	}
190	Dictionary *rtnDict = new Dictionary(cfDict);
191	/*
192	 * mDict has one ref count
193	 * cfDict has one ref count
194	 */
195	return rtnDict;
196}
197
198/*
199 * boolean lookup, tolerate many different forms of value.
200 * Default if value not present is false.
201 */
202bool Dictionary::getBoolValue(
203	CFStringRef key)
204{
205	CFTypeRef val = CFDictionaryGetValue(dict(), key);
206	if(val == NULL) {
207		return false;
208	}
209	CFComparisonResult res;
210	if(CFGetTypeID(val) == CFStringGetTypeID()) {
211		res = CFStringCompare((CFStringRef)val, CFSTR("YES"),
212				kCFCompareCaseInsensitive);
213		if(res == kCFCompareEqualTo) {
214			return true;
215		}
216		else {
217			return false;
218		}
219	}
220	if(CFGetTypeID(val) == CFBooleanGetTypeID()) {
221		return CFBooleanGetValue((CFBooleanRef)val) ? true : false;
222	}
223	if(CFGetTypeID(val) == CFNumberGetTypeID()) {
224		char cval = 0;
225		CFNumberGetValue((CFNumberRef)val, kCFNumberCharType, &cval);
226		return (cval == 0) ? false : true;
227	}
228	return false;
229}
230
231CFIndex Dictionary::count()
232{
233	return CFDictionaryGetCount(dict());
234}
235
236void Dictionary::setDict(
237	CFDictionaryRef	newDict)
238{
239	if(mDict != NULL)
240		CFRelease(mDict);
241	mDict = newDict;
242	CFRetain(mDict);
243}
244
245/* fundamental routine to init from a plist file; throws a UnixError on error */
246void Dictionary::initFromFile(
247	const char *path,
248	bool		loose /* = false */)
249{
250	if(mDict != NULL) {
251		CFRelease(mDict);
252		mDict = NULL;
253	}
254	CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path,
255		strlen(path), false);
256	if(url == NULL) {
257        UnixError::throwMe(EIO);
258	}
259
260	CFDataRef fileData = NULL;
261	CFPropertyListRef propList = NULL;
262	CFStringRef errorString = NULL;
263	SInt32 errorCode;
264
265	Boolean success = CFURLCreateDataAndPropertiesFromResource(
266		NULL,
267		url,
268		&fileData,
269		NULL,			// properties
270		NULL,			// desiredProperties
271		&errorCode);
272	CFRelease(url);
273	if(success) {
274		propList = CFPropertyListCreateFromXMLData(
275			NULL,
276			fileData,
277			kCFPropertyListImmutable,
278			&errorString);
279		if(propList != NULL) {
280			/*
281			 * Note don't use setDict() here to avoid the extra
282			 * refcount that would entail. We own the dictionary now.
283			 */
284			mDict = (CFDictionaryRef)propList;
285		}
286		else {
287			success = false;
288		}
289	}
290	if(fileData != NULL) {
291		CFRelease(fileData);
292	}
293	if(errorString != NULL) {
294		CFRelease(errorString);
295	}
296	if(!success) {
297		if (loose)
298			return;
299		else
300			UnixError::throwMe(EIO);
301	}
302}
303
304#pragma mark ----- Mutable Dictionary -----
305
306// factory functions
307MutableDictionary* MutableDictionary::CreateMutableDictionary(const char* fileName)
308{
309	std::string path;
310
311	if (!FileExists(path.c_str()))
312	{
313		return NULL;
314	}
315	else
316	{
317		return new MutableDictionary(path.c_str());
318	}
319}
320
321MutableDictionary* MutableDictionary::CreateMutableDictionary(const char *domain, UserOrSystem userSys)
322{
323	std::string path;
324	pathForDomain(domain, userSys, path);
325
326	if (!FileExists(path.c_str()))
327	{
328		return NULL;
329	}
330
331	return new MutableDictionary(path.c_str());
332}
333
334/* Create an empty mutable dictionary */
335MutableDictionary::MutableDictionary()
336	: Dictionary((CFDictionaryRef)CFDictionaryCreateMutable(NULL, 0,
337		&kCFCopyStringDictionaryKeyCallBacks,
338		&kCFTypeDictionaryValueCallBacks))
339{
340	/* lose one of those two retain counts.... */
341	CFRelease(mDict);
342}
343
344MutableDictionary::MutableDictionary(
345	const char		*filename)
346		: Dictionary(filename)
347{
348	/*
349	 * Dictionary's contructor read the plist from disk. Now
350	 * replace that dictionary with a mutable copy.
351	 */
352	makeMutable();
353}
354
355/*
356 * Create from existing CFDictionary (OR CFMutableDictionary).
357 * I don't see any way the CF runtime will let us differentiate an
358 * immutable from a mutable dictionary here, so caller has to tell us.
359 */
360MutableDictionary::MutableDictionary(
361	CFDictionaryRef	dict,
362	bool isMutable)
363		: Dictionary(dict)
364{
365	if(!isMutable) {
366		makeMutable();
367	}
368}
369
370MutableDictionary::~MutableDictionary()
371{
372	/* nothing for now */
373}
374
375/*
376 * Lookup, value must be CFDictionary (we check). We return a
377 * mutable copy, or if key not found, we return a new mutable dictionary
378 * with a ref count of one.
379 * If you want a NULL return if it's not there, use getDictValue().
380 */
381CFMutableDictionaryRef MutableDictionary::getMutableDictValue(
382	CFStringRef key)
383{
384	CFDictionaryRef dict = getDictValue(key);
385	if(dict == NULL) {
386		prefsDebug("getMutableDictValue returning new empty dict; this %p", this);
387		return CFDictionaryCreateMutable(NULL, 0,
388					&kCFCopyStringDictionaryKeyCallBacks,
389					&kCFTypeDictionaryValueCallBacks);
390	}
391	else {
392		prefsDebug("getMutableDictValue returning copy; this %p", this);
393		return CFDictionaryCreateMutableCopy(NULL, 0, dict);
394	}
395}
396
397/*
398 * Lookup, value is a dictionary, we return a MutableDictionary, even if
399 * no value found.
400 */
401MutableDictionary *MutableDictionary::copyMutableDictValue(
402	CFStringRef key)
403{
404	CFMutableDictionaryRef cfDict = getMutableDictValue(key);
405	assert(CFGetRetainCount(cfDict) == 1);
406	MutableDictionary *rtnDict = new MutableDictionary(cfDict, true);
407	CFRelease(cfDict);
408	/* rtnDict->mDict now holds the only ref count */
409	return rtnDict;
410}
411
412/*
413 * Basic setter. Does a replace if present, add if not present op.
414 */
415void MutableDictionary::setValue(
416	CFStringRef		key,
417	CFTypeRef		val)
418{
419	CFDictionarySetValue(mutableDict(), key, val);
420}
421
422/*
423 * Set key/value pair, data as CFData in the dictionary but passed to us as CSSM_DATA.
424 */
425void MutableDictionary::setDataValue(
426	CFStringRef		key,
427	const void *valData, CFIndex valLength)
428{
429	CFDataRef cfVal = CFDataCreate(NULL, reinterpret_cast<const UInt8 *>(valData), valLength);
430	setValue(key, cfVal);
431	CFRelease(cfVal);
432}
433
434/* remove key/value, if present; not an error if it's not */
435void MutableDictionary::removeValue(
436	CFStringRef		key)
437{
438	CFDictionaryRemoveValue(mutableDict(), key);
439}
440
441/* write as XML property list, both return true on success */
442bool MutableDictionary::writePlistToFile(
443	const char	*path)
444{
445	assert(mDict != NULL);
446	CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path,
447		strlen(path), false);
448	if(url == NULL) {
449        UnixError::throwMe(EIO);
450	}
451
452	CFDataRef xmlData = CFPropertyListCreateXMLData(NULL, dict());
453	bool ourRtn = false;
454	SInt32 errorCode;
455	if(xmlData == NULL) {
456		goto errOut;
457	}
458	if(CFURLWriteDataAndPropertiesToResource(url, xmlData, NULL, &errorCode)) {
459		ourRtn = true;
460	}
461errOut:
462	if(url) {
463		CFRelease(url);
464	}
465	if(xmlData) {
466		CFRelease(xmlData);
467	}
468	return ourRtn;
469}
470
471/* write XML property list to preferences file */
472bool MutableDictionary::writePlistToPrefs(
473	const char		*domain,		// e.g., com.apple.security
474	UserOrSystem	userSys)		// US_User  : ~/Library/Preferences/domain.plist
475									// US_System: /Library/Preferences/domain.plist
476{
477	std::string path;
478	pathForDomain(domain, userSys, path);
479	return writePlistToFile(path.c_str());
480}
481
482/*
483 * Called after Dictionary reads plist from file, resulting in an immutable
484 * mDict. We replace that with a mutable copy.
485 */
486void MutableDictionary::makeMutable()
487{
488	CFMutableDictionaryRef mutDict = CFDictionaryCreateMutableCopy(NULL, 0, dict());
489	if(mutDict == NULL) {
490		throw std::bad_alloc();
491	}
492	setDict(mutDict);
493	/* we own the dictionary now */
494	CFRelease(mutDict);
495}
496
497