/ OSX / libsecurity_mds / lib / MDSDictionary.cpp
MDSDictionary.cpp
  1  /*
  2   * Copyright (c) 2000-2001,2011-2012,2014 Apple Inc. All Rights Reserved.
  3   * 
  4   * The contents of this file constitute Original Code as defined in and are
  5   * subject to the Apple Public Source License Version 1.2 (the 'License').
  6   * You may not use this file except in compliance with the License. Please obtain
  7   * a copy of the License at http://www.apple.com/publicsource and read it before
  8   * using this file.
  9   * 
 10   * This Original Code and all software distributed under the License are
 11   * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
 12   * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
 13   * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
 14   * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
 15   * specific language governing rights and limitations under the License.
 16   */
 17  
 18  
 19  /*
 20     File:      MDSDictionary.cpp
 21  
 22     Contains:  Internal representation of one MDS info file in the form of 
 23                a CFDictionary. 
 24  
 25     Copyright (c) 2001,2011-2012,2014 Apple Inc. All Rights Reserved.
 26  */
 27  
 28  #include "MDSDictionary.h"
 29  #include "MDSAttrParser.h"
 30  #include "MDSAttrUtils.h"
 31  #include <security_utilities/logging.h>
 32  #include <security_utilities/cfutilities.h>
 33  
 34  namespace Security
 35  {
 36  
 37  /* heavyweight constructor from file */
 38  MDSDictionary::MDSDictionary(
 39  	CFURLRef fileUrl,
 40  	CFStringRef subdir,
 41  	const char *fullPath)		// could get from fileUrl, but very messy!
 42  	: mDict(NULL),
 43  	  mWeOwnDict(false),
 44  	  mUrlPath(NULL),
 45  	  mFileDesc(NULL),
 46  	  mSubdir(subdir),
 47  	  mDefaults(NULL)
 48  {
 49  	CFDataRef dictData = NULL;
 50  	CFStringRef cfErr = NULL;
 51  	
 52  	assert(fileUrl != NULL);
 53  	mUrlPath = MDSCopyCstring(fullPath);
 54  	MPDebug("Creating MDSDictionary from %s", mUrlPath);
 55  	
 56  	/* Load data from URL */
 57  	SInt32 uerr;
 58  	Boolean brtn = CFURLCreateDataAndPropertiesFromResource(
 59  		NULL,
 60  		fileUrl,
 61  		&dictData,
 62  		NULL,		// properties
 63  		NULL,		// desiredProperties
 64  		&uerr);
 65  	if(!brtn) {
 66  		Syslog::alert("Error reading MDS file %s: %d", mUrlPath, (int)uerr);
 67  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
 68  	}
 69  	
 70  	/* if it's not a dictionary, we don't want it */
 71  	mDict = reinterpret_cast<CFDictionaryRef>(
 72  		CFPropertyListCreateFromXMLData(NULL,
 73  			dictData,
 74  			kCFPropertyListImmutable,
 75  			&cfErr));
 76  	CFRelease(dictData);
 77  	if(mDict == NULL) {
 78  		Syslog::alert("Malformed MDS file %s (1)", mUrlPath);
 79  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
 80  	}
 81  	
 82  	/* henceforth we must release this dictionary */
 83  	mWeOwnDict = true;
 84  	if(CFGetTypeID(mDict) != CFDictionaryGetTypeID()) {
 85  		Syslog::alert("Malformed MDS file %s (2)", mUrlPath);
 86  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
 87  	}
 88  	CF_RELEASE(cfErr);
 89  	
 90  	/* get file description for error logging and debugging */
 91  	CFStringRef cfStr = (CFStringRef)lookup(CFSTR(MDS_INFO_FILE_DESC), 
 92  		true, CFStringGetTypeID());
 93  	if(cfStr) {
 94  		CFIndex len = CFStringGetMaximumSizeForEncoding(CFStringGetLength(cfStr), kCFStringEncodingUTF8) + 1/*nul terminator*/;
 95  		mFileDesc = new char[len];
 96  		if(mFileDesc) {
 97  			CFStringGetCString(cfStr, mFileDesc, len, 
 98  				kCFStringEncodingUTF8);
 99  		}
100  	}
101  }
102  
103  /* lightweight constructor from existing CFDictionary */
104  MDSDictionary::MDSDictionary(CFDictionaryRef theDict)
105  	: mDict(theDict),
106  	  mWeOwnDict(false),
107  	  mUrlPath(NULL),
108  	  mFileDesc(NULL),
109  	  mDefaults(NULL)
110  {
111  	/* note caller owns and releases the dictionary */ 
112  	if(mDict == NULL) {
113  		MPDebug("Malformed MDS file (3)");
114  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
115  	}
116  	if(CFGetTypeID(mDict) != CFDictionaryGetTypeID()) {
117  		MPDebug("Malformed MDS file (4)");
118  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
119  	}
120  }
121  
122  MDSDictionary::~MDSDictionary()
123  {
124  	if(mWeOwnDict) {
125  		CF_RELEASE(mDict);
126  	}
127  	mDict = NULL;
128  	delete [] mUrlPath;
129  	delete [] mFileDesc;
130  }
131  
132  /* lookup by either C string or CFStringRef - returns NULL on error */
133  const void *MDSDictionary::lookup(
134  	const char *key,
135  	bool checkType,
136  	CFTypeID type)
137  {
138  	CFStringRef cfKey = CFStringCreateWithCString(NULL,
139  		key,
140  		kCFStringEncodingUTF8);
141  	if(cfKey == NULL) {
142  		MPDebug("MDSDictionary::lookup: error creating CFString for key");
143  		return NULL;
144  	}
145  	const void *rtn = lookup(cfKey, checkType, type);
146  	CFRelease(cfKey);
147  	return rtn;
148  
149  }
150  
151  const void *MDSDictionary::lookup(
152  	CFStringRef key,
153  	bool checkType,
154  	CFTypeID type)
155  {
156  	assert(mDict != NULL);
157  	const void *rtn = CFDictionaryGetValue(mDict, key);
158  	if(rtn && checkType) {
159  		if(CFGetTypeID((CFTypeRef)rtn) != type) {
160  			return NULL;
161  		}
162  	}
163  	return rtn;
164  }
165  
166  /*
167   * Common means to perform a lookup in a dictionary given a C-string key and
168   * placing the value - if present - in a CSSM_DB_ATTRIBUTE_DATA. Any errors
169   * are only logged via MPDebug. Returns true if the value was found and 
170   * successfully placed in supplied CSSM_DB_ATTRIBUTE_DATA.
171   *
172   * For now we assume that the key in the dictionary is the same as the key
173   * in the DB to which we're writing. 
174   *
175   * We're also assuming that all DB keys are of format CSSM_DB_ATTRIBUTE_NAME_AS_STRING.
176   */
177  bool MDSDictionary::lookupToDbAttr(
178  	const char *key,
179  	CSSM_DB_ATTRIBUTE_DATA &attr,
180  	CSSM_DB_ATTRIBUTE_FORMAT attrFormat,
181  	const MDSNameValuePair *nameValues)	// optional for converting strings to numbers
182  {
183  	assert(mDict != NULL);
184  	assert(&attr != NULL);
185  	
186  	CFTypeRef	value;				// polymorphic dictionary value
187  	bool		ourRtn = false;
188  	const void 	*srcPtr = NULL;		// polymorphic raw source bytes
189  	size_t		srcLen = 0;
190  	uint32 		ival = 0;
191  	uint32		*ivalArray = NULL;
192  	uint32		numValues = 1;		// the default for MDSRawValueToDbAttr
193  	string		stringVal;
194  
195  	value = (CFTypeRef)lookup(key);
196  	if(value == NULL) {
197  		return false;
198  	}
199  	CFTypeID valueType = CFGetTypeID(value);
200  	
201  	/* 
202  	 * We have the value; could be any type. Handle it based on caller's 
203  	 * CSSM_DB_ATTRIBUTE_FORMAT.
204  	 */
205  	switch(attrFormat) {
206  		case CSSM_DB_ATTRIBUTE_FORMAT_STRING:
207  		{
208  			if(valueType != CFStringGetTypeID()) {
209  				MPDebug("lookupToDbAttr: string format mismatch");
210  				break;
211  			}
212  			stringVal = cfString((CFStringRef)value, false);
213  			srcPtr = stringVal.c_str();
214  			srcLen = stringVal.size();
215  			if(srcLen) {
216  				ourRtn = true;
217  			}
218  			break;
219  		}
220  		case CSSM_DB_ATTRIBUTE_FORMAT_UINT32:
221  		{
222  			bool brtn = MDSCfTypeToUInt32(value, nameValues, key, ival, srcLen);
223  			if(!brtn) {
224  				MPDebug("MDS lookupToDbAttr: Bad number conversion");
225  				return false;
226  			}
227  			srcPtr = &ival;
228  			ourRtn = true;
229  			break;
230  		}	 
231  		case CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32:	
232  		{
233  			/* 
234  			 * This is expressed in the dictionary as an array of numbers. 
235  			 * as in CSSM_DB_ATTRIBUTE_FORMAT_UINT32, each number can be
236  			 * expressed as either a string or a number.
237  			 */
238  			if(valueType != CFArrayGetTypeID()) {
239  				/*
240  				 * Let's be extremely slick and allow one number here, either 
241  				 * in string or number form....
242  				 */
243  				bool brtn = MDSCfTypeToUInt32(value, nameValues, key, ival, srcLen);
244  				if(!brtn) {
245  					MPDebug("MDS lookupToDbAttr: Bad array element");
246  					return false;
247  				}
248  				srcPtr = &ival;
249  				ourRtn = true;
250  				break;
251  			}
252  			CFArrayRef cfArray = (CFArrayRef)value;
253  			numValues = (uint32)CFArrayGetCount(cfArray);
254  			if(numValues == 0) {
255  				/* degenerate case, legal - right? Can AppleDatabase do this? */
256  				srcPtr = NULL;
257  				srcLen = 0;
258  				ourRtn = true;
259  				break;
260  			}
261  			
262  			/* 
263  			 * malloc an array of uint32s
264  			 * convert each element in cfArray to a uint32
265  			 * store as CSSM_DB_ATTRIBUTE_FORMAT_MULTI_UINT32
266  			 *
267  			 * Note this does not have to be endian independent; the MDS DBs
268  			 * are not portable across machines let alone platforms. 
269  			 */
270  			ivalArray = new uint32[numValues];
271  			unsigned dex;
272  			bool brtn;
273  			for(dex=0; dex<numValues; dex++) {
274  				CFTypeRef elmt = (CFTypeRef)CFArrayGetValueAtIndex(cfArray, dex);
275  				if(elmt == NULL) {
276  					MPDebug("MDS lookupToDbAttr: key %s: Bad array element (1)", key);
277  					delete [] ivalArray;
278  					return false;
279  				}
280                  size_t itemLen = 0;
281  				brtn =  MDSCfTypeToUInt32(elmt, nameValues, key, ivalArray[dex], itemLen);
282  				if(!brtn) {
283  					MPDebug("MDS lookupToDbAttr: key %s Bad element at index %d",
284  						key, dex);
285  					delete [] ivalArray;
286  					return false;
287  				}
288                  srcLen += itemLen;
289  			}
290  			srcPtr = ivalArray;
291  			ourRtn = true;
292  			/*
293  			 * FIXME - numValues as used by MDSRawValueToDbAttr and placed in 
294  			 * CSSM_DB_ATTRIBUTE_DATA.NumberOfValues, appears to need to be
295  			 * one even for MULTI_UINT32 format; the number of ints in inferred
296  			 * from Value.Length....
297  			 */
298  			numValues = 1;
299  			break;
300  		}
301  		case CSSM_DB_ATTRIBUTE_FORMAT_BLOB:			// CFData
302  		{
303  			if(valueType != CFDataGetTypeID()) {
304  				MPDebug("lookupToDbAttr: blob/CFData format mismatch");
305  				break;
306  			}
307  			CFDataRef cfData = (CFDataRef)value;
308  			srcLen = CFDataGetLength(cfData);
309  			srcPtr = CFDataGetBytePtr(cfData);
310  			ourRtn = true;
311  			break;
312  		}
313  		case CSSM_DB_ATTRIBUTE_FORMAT_SINT32:		// I don't think we support this
314  		default:
315  			MPDebug("lookupToDbAttr: bad attrForm(%d)", (int)attrFormat);
316  			return false;
317  	}
318  	if(ourRtn) {
319  		MDSRawValueToDbAttr(srcPtr, srcLen, attrFormat, key, attr, numValues);
320  	}
321  	if(ivalArray) {
322  		delete [] ivalArray;
323  	}
324  	return ourRtn;
325  }
326  
327  /*
328   * Given a RelationInfo and an array of CSSM_DB_ATTRIBUTE_DATAs, fill in 
329   * the CSSM_DB_ATTRIBUTE_DATA array with as many fields as we can find in 
330   * the dictionary. All fields are treated as optional. 
331   */
332  void MDSDictionary::lookupAttributes(
333  	const RelationInfo 			*relInfo,
334  	CSSM_DB_ATTRIBUTE_DATA_PTR	outAttrs,		// filled in on return
335  	uint32						&numAttrs)		// RETURNED
336  {
337  	unsigned 						dex;
338  	const CSSM_DB_ATTRIBUTE_INFO 	*inAttr = relInfo->AttributeInfo;
339  	const MDSNameValuePair 			**nameValues	= relInfo->nameValues;
340  
341  	assert(relInfo != NULL);
342  	numAttrs = 0;
343  	for(dex=0; dex<relInfo->NumberOfAttributes; dex++) {
344  		bool brtn;
345  		const MDSNameValuePair *nvp;
346  		
347  		/* the array itself, or any element in it, can be NULL */
348  		if(nameValues != NULL) {
349  			nvp = nameValues[dex];
350  		}
351  		else {
352  			nvp = NULL;
353  		}
354  		brtn = lookupToDbAttr(inAttr->Label.AttributeName,
355  			*outAttrs, 
356  			inAttr->AttributeFormat,
357  			nvp);
358  		if(brtn) {
359  			/* successfully added to dbAttrs */
360  			outAttrs++;
361  			numAttrs++;
362  		}
363  		inAttr++;		// regardless
364  	}
365  }
366  
367  /*
368   * Lookup with file-based indirection. Allows multiple mdsinfo files to share commmon
369   * info from a separate plist file.
370   *
371   * Do a lookup for specified key. If not found, return NULL. If found:
372   * {
373   *		if type of value matches desiredType {
374   * 			return the value;
375   *		}
376   *		else if type of value is string {
377   *			if string starts with "file:" {
378   *				attempt to read property list with that filename relative to 
379   *					specified bundle;
380   *				if CFType of that propList matches desiredType {
381   *					return newly read propList;
382   *				}
383   *			}
384   *		}
385   *		...else return error;
386   */
387  CFPropertyListRef MDSDictionary::lookupWithIndirect(
388  	const char *key,
389  	CFBundleRef bundle,
390  	CFTypeID	desiredType)
391  {
392  	CFPropertyListRef ourRtn = NULL;
393  	CFDataRef dictData = NULL;
394  	CFStringRef cfErr = NULL;
395  	SInt32 uerr;
396  	Boolean brtn;
397  	
398  	
399  	assert(key != NULL);
400  	assert(bundle != NULL);
401  	
402  	/* basic local lookup */
403  	CFStringRef cfKey = CFStringCreateWithCString(NULL,
404  		key,
405  		kCFStringEncodingUTF8);
406  	if(cfKey == NULL) {
407  		MPDebug("CFStringCreateWithCString error");
408  		return NULL;
409  	}
410  	CFCopyRef<CFStringRef> rtn = (CFStringRef)CFDictionaryGetValue(mDict, cfKey);
411  	CFRelease(cfKey);
412  	if(!rtn) {
413  		return NULL;
414  	}
415  	CFTypeID foundType = CFGetTypeID(rtn);
416  	if(foundType == desiredType) {
417  		/* found what we're looking for; done */
418  		return (CFPropertyListRef)rtn.yield();
419  	}
420  	
421  	/* is it a string which starts with "file:"? */
422  	if(foundType != CFStringGetTypeID()) {
423  		return NULL;
424  	}
425  	const char *cVal = MDSCFStringToCString(rtn);
426  	if(cVal == NULL) {
427  		MPDebug("MDSCFStringToCString error in lookupWithIndirect");
428  		return NULL;
429  	}
430  	if(strstr(cVal, "file:") != cVal) {
431  		delete [] cVal;
432  		return NULL;
433  	}
434  	/* delete [] cval on return */
435  	
436  	/* OK, this specifies a resource file in the bundle. Fetch it. */
437  	CFURLRef fileUrl = NULL;
438  	CFStringRef cfFileName = CFStringCreateWithCString(NULL,
439  		cVal + 5,
440  		kCFStringEncodingUTF8);
441  	if(cfFileName == NULL) {
442  		MPDebug("lookupWithIndirect: bad file name spec");
443  		goto abort;
444  	}
445  	fileUrl = CFBundleCopyResourceURL(bundle, 
446                  cfFileName, 
447                  NULL, 
448                  mSubdir);
449  	if(fileUrl == NULL) {
450  		MPDebug("lookupWithIndirect: file %s not found", cVal);
451  		goto abort;
452  	}
453  
454  	MPDebug("Fetching indirect resource %s", cVal);
455  	
456  	/* Load data from URL */
457  	brtn = CFURLCreateDataAndPropertiesFromResource(
458  		NULL,
459  		fileUrl,
460  		&dictData,
461  		NULL,		// properties
462  		NULL,		// desiredProperties
463  		&uerr);
464  	if(!brtn) {
465  		MPDebug("lookupWithIndirect: error %d reading %s", (int)uerr, cVal);
466  		goto abort;
467  	}
468  	
469  	/* if it's not a property list, we don't want it */
470  	ourRtn = CFPropertyListCreateFromXMLData(NULL,
471  			dictData,
472  			kCFPropertyListImmutable,
473  			&cfErr);
474  	if(ourRtn == NULL) {
475  		MPDebug("lookupWithIndirect: %s malformed (not a prop list)", cVal);
476  		goto abort;
477  	}
478  	
479  	/* if it doesn't match the caller's spec, we don't want it */
480  	if(CFGetTypeID(ourRtn) != desiredType) {
481  		MPDebug("lookupWithIndirect: %s malformed (mismatch)", cVal);
482  		CF_RELEASE(ourRtn);
483  		ourRtn = NULL;
484  		goto abort;
485  	}
486  
487  	MPDebug("lookupWithIndirect: resource %s FOUND", cVal);
488  
489  abort:
490  	delete [] cVal;
491  	CF_RELEASE(cfFileName);
492  	CF_RELEASE(fileUrl);
493  	CF_RELEASE(dictData);
494  	CF_RELEASE(cfErr);
495  	return ourRtn;
496  }
497  
498  void MDSDictionary::setDefaults(const MDS_InstallDefaults *defaults) 
499  { 
500  	mDefaults = defaults;
501  
502  	/*
503  	 * Save the values into (a new) mDict.  
504  	 */
505  	assert(mDict != NULL);
506  	CFMutableDictionaryRef tmpDict = CFDictionaryCreateMutableCopy(NULL, 0, mDict);
507  	if(tmpDict == NULL) {
508  		MPDebug("setDefaults: error copying old dictionary");
509  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
510  	}
511  
512  	CFStringRef tmpStr = NULL;
513  
514  	/*
515  	 * CFDictionaryAddValue() does nothing if the requested key is already 
516  	 * present.  If you need to call setDefaults() more than once, you'll 
517  	 * have to add the code to remove the old key/value pairs first.  
518  	 */
519  	if(defaults) {
520  		if(defaults->guid) {
521  			tmpStr = CFStringCreateWithCString(NULL, defaults->guid, kCFStringEncodingUTF8);
522  			if(tmpStr) {
523  				CFDictionaryAddValue(tmpDict, CFSTR("ModuleID"), tmpStr);
524  				CFRelease(tmpStr);
525  			}
526  			else {
527  				MPDebug("setDefaults: error creating CFString for GUID");
528  				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
529  			}
530  		}
531  
532  		CFNumberRef tmpNum = CFNumberCreate(NULL, kCFNumberIntType, &defaults->ssid);
533  		if(tmpNum) {
534  			CFDictionaryAddValue(tmpDict, CFSTR("SSID"), tmpNum);
535  			CFRelease(tmpNum);
536  		}
537  		else {
538  			MPDebug("setDefaults: error creating CFString for SSID");
539  			CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
540  		}
541  
542  		if(defaults->serial) {
543  			tmpStr = CFStringCreateWithCString(NULL, defaults->serial, kCFStringEncodingUTF8);
544  			if(tmpStr) {
545  				CFDictionaryAddValue(tmpDict, CFSTR("ScSerialNumber"), tmpStr);
546  				CFRelease(tmpStr);
547  			}
548  			else {
549  				MPDebug("setDefaults: error creating CFString for serial number");
550  				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
551  			}
552  		}
553  
554  		if(defaults->printName) {
555  			tmpStr = CFStringCreateWithCString(NULL, defaults->printName, kCFStringEncodingUTF8);
556  			if(tmpStr) {
557  				CFDictionaryAddValue(tmpDict, CFSTR("ScDesc"), tmpStr);
558  				CFRelease(tmpStr);
559  			}
560  			else {
561  				MPDebug("setDefaults: error creating CFString for description");
562  				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
563  			}
564  		}
565  	}
566  
567  	if(mUrlPath) {
568  		tmpStr = CFStringCreateWithCString(NULL, mUrlPath, kCFStringEncodingUTF8);
569  		if(tmpStr) {
570  			CFDictionaryAddValue(tmpDict, CFSTR("Path"), tmpStr);
571  			CFRelease(tmpStr);
572  		}
573  		else {
574  			MPDebug("setDefaults: error creating CFString for path");
575  			CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
576  		}
577  	}
578  	CFDictionaryRef oldDict = mDict;
579  	mDict = CFDictionaryCreateCopy(NULL, tmpDict);
580  	if(mDict == NULL) {
581  		mDict = oldDict;	// first do no harm
582  		CFRelease(tmpDict);
583  		MPDebug("setDefaults: error creating new dictionary");
584  		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
585  	}
586  	CFRelease(oldDict);
587  	CFRelease(tmpDict);
588  }
589  
590  
591  } // end namespace Security