/* * Copyright (c) 2002-2004,2011,2014 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@ */ /* * simpleprefs.cpp - plist support for a bare bones Preferences implementation, * using only Darwin-avaialble CoreFoundation classes. */ #include "simpleprefs.h" #include "errors.h" #include #include #include #include #include #include #include #include #include #include #define prefsDebug(args...) secdebug("simpleprefs", ## args) #define kSecUserPrefsDir "Library/Preferences" /* relative to $HOME */ #define kSecSystemPrefsDir "/Library/Preferences" #pragma mark ----- (immutable) Dictionary ----- static void pathForDomain(const char *domain, Dictionary::UserOrSystem userSys, std::string &path) { path.clear(); if(userSys == Dictionary::US_User) { const char *home = getenv("HOME"); if(home == NULL) { home = ""; } path = std::string(home) + "/" + kSecUserPrefsDir + "/" + domain + ".plist"; } else { path = std::string(kSecSystemPrefsDir) + "/" + domain + ".plist"; } } static bool FileExists(const char* s) { // this isn't very efficient, either, but orders are to get rid of exceptions... struct stat st; int result = stat(s, &st); return result == 0; } // use factory functions to create the dictionaries so that we can test for the presence of the dictionaries // without throwing Dictionary* Dictionary::CreateDictionary(const char* path) { if (!FileExists(path)) { return NULL; } else { return new Dictionary(path); } } Dictionary* Dictionary::CreateDictionary(const char* domain, UserOrSystem userSys, bool loose) { std::string path; pathForDomain(domain, userSys, path); bool exists = FileExists(path.c_str()); if (!loose && !exists) { return NULL; } if (!exists) { return new Dictionary(); } return new Dictionary(path.c_str()); } Dictionary::Dictionary() : mDict(NULL) { } Dictionary::Dictionary( const char *path) : mDict(NULL) { initFromFile(path); } Dictionary::Dictionary( CFDictionaryRef dict) : mDict(dict) { if (mDict) CFRetain(mDict); } Dictionary::~Dictionary() { if(mDict) { CFRelease(mDict); } } /* basic lookup */ const void *Dictionary::getValue( CFStringRef key) { return CFDictionaryGetValue(dict(), key); } /* lookup, value must be CFString (we check) */ CFStringRef Dictionary::getStringValue( CFStringRef key) { CFStringRef val = (CFStringRef)CFDictionaryGetValue(dict(), key); if(val == NULL) { return NULL; } if(CFGetTypeID(val) != CFStringGetTypeID()) { return NULL; } return val; } /* lookup, value must be CFData (we check) */ CFDataRef Dictionary::getDataValue( CFStringRef key) { CFDataRef val = (CFDataRef)CFDictionaryGetValue(dict(), key); if(val == NULL) { return NULL; } if(CFGetTypeID(val) != CFDataGetTypeID()) { return NULL; } return val; } /* lookup, value must be CFDictionary (we check) */ CFDictionaryRef Dictionary::getDictValue( CFStringRef key) { CFDictionaryRef val = (CFDictionaryRef)CFDictionaryGetValue(dict(), key); if(val == NULL) { return NULL; } if(CFGetTypeID(val) != CFDictionaryGetTypeID()) { return NULL; } return val; } /* * Lookup, value is a dictionary, we return value as Dictionary * if found, else return NULL. */ Dictionary *Dictionary::copyDictValue( CFStringRef key) { CFDictionaryRef cfDict = getDictValue(key); if(cfDict == NULL) { return NULL; } Dictionary *rtnDict = new Dictionary(cfDict); /* * mDict has one ref count * cfDict has one ref count */ return rtnDict; } /* * boolean lookup, tolerate many different forms of value. * Default if value not present is false. */ bool Dictionary::getBoolValue( CFStringRef key) { CFTypeRef val = CFDictionaryGetValue(dict(), key); if(val == NULL) { return false; } CFComparisonResult res; if(CFGetTypeID(val) == CFStringGetTypeID()) { res = CFStringCompare((CFStringRef)val, CFSTR("YES"), kCFCompareCaseInsensitive); if(res == kCFCompareEqualTo) { return true; } else { return false; } } if(CFGetTypeID(val) == CFBooleanGetTypeID()) { return CFBooleanGetValue((CFBooleanRef)val) ? true : false; } if(CFGetTypeID(val) == CFNumberGetTypeID()) { char cval = 0; CFNumberGetValue((CFNumberRef)val, kCFNumberCharType, &cval); return (cval == 0) ? false : true; } return false; } CFIndex Dictionary::count() { return CFDictionaryGetCount(dict()); } void Dictionary::setDict( CFDictionaryRef newDict) { if(mDict != NULL) CFRelease(mDict); mDict = newDict; CFRetain(mDict); } /* fundamental routine to init from a plist file; throws a UnixError on error */ void Dictionary::initFromFile( const char *path, bool loose /* = false */) { if(mDict != NULL) { CFRelease(mDict); mDict = NULL; } CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path, strlen(path), false); if(url == NULL) { UnixError::throwMe(EIO); } CFDataRef fileData = NULL; CFPropertyListRef propList = NULL; CFStringRef errorString = NULL; SInt32 errorCode; Boolean success = CFURLCreateDataAndPropertiesFromResource( NULL, url, &fileData, NULL, // properties NULL, // desiredProperties &errorCode); CFRelease(url); if(success) { propList = CFPropertyListCreateFromXMLData( NULL, fileData, kCFPropertyListImmutable, &errorString); if(propList != NULL) { /* * Note don't use setDict() here to avoid the extra * refcount that would entail. We own the dictionary now. */ mDict = (CFDictionaryRef)propList; } else { success = false; } } if(fileData != NULL) { CFRelease(fileData); } if(errorString != NULL) { CFRelease(errorString); } if(!success) { if (loose) return; else UnixError::throwMe(EIO); } } #pragma mark ----- Mutable Dictionary ----- // factory functions MutableDictionary* MutableDictionary::CreateMutableDictionary(const char* fileName) { std::string path; if (!FileExists(path.c_str())) { return NULL; } else { return new MutableDictionary(path.c_str()); } } MutableDictionary* MutableDictionary::CreateMutableDictionary(const char *domain, UserOrSystem userSys) { std::string path; pathForDomain(domain, userSys, path); if (!FileExists(path.c_str())) { return NULL; } return new MutableDictionary(path.c_str()); } /* Create an empty mutable dictionary */ MutableDictionary::MutableDictionary() : Dictionary((CFDictionaryRef)CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)) { /* lose one of those two retain counts.... */ CFRelease(mDict); } MutableDictionary::MutableDictionary( const char *filename) : Dictionary(filename) { /* * Dictionary's contructor read the plist from disk. Now * replace that dictionary with a mutable copy. */ makeMutable(); } /* * Create from existing CFDictionary (OR CFMutableDictionary). * I don't see any way the CF runtime will let us differentiate an * immutable from a mutable dictionary here, so caller has to tell us. */ MutableDictionary::MutableDictionary( CFDictionaryRef dict, bool isMutable) : Dictionary(dict) { if(!isMutable) { makeMutable(); } } MutableDictionary::~MutableDictionary() { /* nothing for now */ } /* * Lookup, value must be CFDictionary (we check). We return a * mutable copy, or if key not found, we return a new mutable dictionary * with a ref count of one. * If you want a NULL return if it's not there, use getDictValue(). */ CFMutableDictionaryRef MutableDictionary::getMutableDictValue( CFStringRef key) { CFDictionaryRef dict = getDictValue(key); if(dict == NULL) { prefsDebug("getMutableDictValue returning new empty dict; this %p", this); return CFDictionaryCreateMutable(NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); } else { prefsDebug("getMutableDictValue returning copy; this %p", this); return CFDictionaryCreateMutableCopy(NULL, 0, dict); } } /* * Lookup, value is a dictionary, we return a MutableDictionary, even if * no value found. */ MutableDictionary *MutableDictionary::copyMutableDictValue( CFStringRef key) { CFMutableDictionaryRef cfDict = getMutableDictValue(key); assert(CFGetRetainCount(cfDict) == 1); MutableDictionary *rtnDict = new MutableDictionary(cfDict, true); CFRelease(cfDict); /* rtnDict->mDict now holds the only ref count */ return rtnDict; } /* * Basic setter. Does a replace if present, add if not present op. */ void MutableDictionary::setValue( CFStringRef key, CFTypeRef val) { CFDictionarySetValue(mutableDict(), key, val); } /* * Set key/value pair, data as CFData in the dictionary but passed to us as CSSM_DATA. */ void MutableDictionary::setDataValue( CFStringRef key, const void *valData, CFIndex valLength) { CFDataRef cfVal = CFDataCreate(NULL, reinterpret_cast(valData), valLength); setValue(key, cfVal); CFRelease(cfVal); } /* remove key/value, if present; not an error if it's not */ void MutableDictionary::removeValue( CFStringRef key) { CFDictionaryRemoveValue(mutableDict(), key); } /* write as XML property list, both return true on success */ bool MutableDictionary::writePlistToFile( const char *path) { assert(mDict != NULL); CFURLRef url = CFURLCreateFromFileSystemRepresentation(NULL, (const UInt8 *)path, strlen(path), false); if(url == NULL) { UnixError::throwMe(EIO); } CFDataRef xmlData = CFPropertyListCreateXMLData(NULL, dict()); bool ourRtn = false; SInt32 errorCode; if(xmlData == NULL) { goto errOut; } if(CFURLWriteDataAndPropertiesToResource(url, xmlData, NULL, &errorCode)) { ourRtn = true; } errOut: if(url) { CFRelease(url); } if(xmlData) { CFRelease(xmlData); } return ourRtn; } /* write XML property list to preferences file */ bool MutableDictionary::writePlistToPrefs( const char *domain, // e.g., com.apple.security UserOrSystem userSys) // US_User : ~/Library/Preferences/domain.plist // US_System: /Library/Preferences/domain.plist { std::string path; pathForDomain(domain, userSys, path); return writePlistToFile(path.c_str()); } /* * Called after Dictionary reads plist from file, resulting in an immutable * mDict. We replace that with a mutable copy. */ void MutableDictionary::makeMutable() { CFMutableDictionaryRef mutDict = CFDictionaryCreateMutableCopy(NULL, 0, dict()); if(mutDict == NULL) { throw std::bad_alloc(); } setDict(mutDict); /* we own the dictionary now */ CFRelease(mutDict); }