1/*
2 * Copyright (c) 2000-2001 Apple Computer, 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 Apple Computer, 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
34namespace Security
35{
36
37/* heavyweight constructor from file */
38MDSDictionary::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 */
104MDSDictionary::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
122MDSDictionary::~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 */
133const 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
151const 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 */
177bool 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 */
332void 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 */
387const CFPropertyListRef MDSDictionary::lookupWithIndirect(
388	const char *key,
389	CFBundleRef bundle,
390	CFTypeID	desiredType,
391	bool		&fetchedFromDisk)	// true --> caller must CFRelease the returned
392									//     value
393									// false -> it's part of this dictionary
394{
395	CFPropertyListRef ourRtn = NULL;
396	CFDataRef dictData = NULL;
397	CFStringRef cfErr = NULL;
398	SInt32 uerr;
399	Boolean brtn;
400
401
402	assert(key != NULL);
403	assert(bundle != NULL);
404
405	fetchedFromDisk = false;
406
407	/* basic local lookup */
408	CFStringRef cfKey = CFStringCreateWithCString(NULL,
409		key,
410		kCFStringEncodingUTF8);
411	if(cfKey == NULL) {
412		MPDebug("CFStringCreateWithCString error");
413		return NULL;
414	}
415	const void *rtn = CFDictionaryGetValue(mDict, cfKey);
416	CFRelease(cfKey);
417	if(rtn == NULL) {
418		return NULL;
419	}
420	CFTypeID foundType = CFGetTypeID((CFTypeRef)rtn);
421	if(foundType == desiredType) {
422		/* found what we're looking for; done */
423		return (CFPropertyListRef)rtn;
424	}
425
426	/* is it a string which starts with "file:"? */
427	if(foundType != CFStringGetTypeID()) {
428		return NULL;
429	}
430	const char *cVal = MDSCFStringToCString((CFStringRef)rtn);
431	if(cVal == NULL) {
432		MPDebug("MDSCFStringToCString error in lookupWithIndirect");
433		return NULL;
434	}
435	if(strstr(cVal, "file:") != cVal) {
436		delete [] cVal;
437		return NULL;
438	}
439	/* delete [] cval on return */
440
441	/* OK, this specifies a resource file in the bundle. Fetch it. */
442	CFURLRef fileUrl = NULL;
443	CFStringRef cfFileName = CFStringCreateWithCString(NULL,
444		cVal + 5,
445		kCFStringEncodingUTF8);
446	if(cfFileName == NULL) {
447		MPDebug("lookupWithIndirect: bad file name spec");
448		goto abort;
449	}
450	fileUrl = CFBundleCopyResourceURL(bundle,
451                cfFileName,
452                NULL,
453                mSubdir);
454	if(fileUrl == NULL) {
455		MPDebug("lookupWithIndirect: file %s not found", cVal);
456		goto abort;
457	}
458
459	MPDebug("Fetching indirect resource %s", cVal);
460
461	/* Load data from URL */
462	brtn = CFURLCreateDataAndPropertiesFromResource(
463		NULL,
464		fileUrl,
465		&dictData,
466		NULL,		// properties
467		NULL,		// desiredProperties
468		&uerr);
469	if(!brtn) {
470		MPDebug("lookupWithIndirect: error %d reading %s", (int)uerr, cVal);
471		goto abort;
472	}
473
474	/* if it's not a property list, we don't want it */
475	ourRtn = CFPropertyListCreateFromXMLData(NULL,
476			dictData,
477			kCFPropertyListImmutable,
478			&cfErr);
479	if(ourRtn == NULL) {
480		MPDebug("lookupWithIndirect: %s malformed (not a prop list)", cVal);
481		goto abort;
482	}
483
484	/* if it doesn't match the caller's spec, we don't want it */
485	if(CFGetTypeID(ourRtn) != desiredType) {
486		MPDebug("lookupWithIndirect: %s malformed (mismatch)", cVal);
487		CF_RELEASE(ourRtn);
488		ourRtn = NULL;
489		goto abort;
490	}
491
492	MPDebug("lookupWithIndirect: resource %s FOUND", cVal);
493	fetchedFromDisk = true;
494
495abort:
496	delete [] cVal;
497	CF_RELEASE(cfFileName);
498	CF_RELEASE(fileUrl);
499	CF_RELEASE(dictData);
500	CF_RELEASE(cfErr);
501	return ourRtn;
502}
503
504void MDSDictionary::setDefaults(const MDS_InstallDefaults *defaults)
505{
506	mDefaults = defaults;
507
508	/*
509	 * Save the values into (a new) mDict.
510	 */
511	assert(mDict != NULL);
512	CFMutableDictionaryRef tmpDict = CFDictionaryCreateMutableCopy(NULL, 0, mDict);
513	if(tmpDict == NULL) {
514		MPDebug("setDefaults: error copying old dictionary");
515		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
516	}
517
518	CFStringRef tmpStr = NULL;
519
520	/*
521	 * CFDictionaryAddValue() does nothing if the requested key is already
522	 * present.  If you need to call setDefaults() more than once, you'll
523	 * have to add the code to remove the old key/value pairs first.
524	 */
525	if(defaults) {
526		if(defaults->guid) {
527			tmpStr = CFStringCreateWithCString(NULL, defaults->guid, kCFStringEncodingUTF8);
528			if(tmpStr) {
529				CFDictionaryAddValue(tmpDict, CFSTR("ModuleID"), tmpStr);
530				CFRelease(tmpStr);
531			}
532			else {
533				MPDebug("setDefaults: error creating CFString for GUID");
534				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
535			}
536		}
537
538		CFNumberRef tmpNum = CFNumberCreate(NULL, kCFNumberIntType, &defaults->ssid);
539		if(tmpNum) {
540			CFDictionaryAddValue(tmpDict, CFSTR("SSID"), tmpNum);
541			CFRelease(tmpNum);
542		}
543		else {
544			MPDebug("setDefaults: error creating CFString for SSID");
545			CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
546		}
547
548		if(defaults->serial) {
549			tmpStr = CFStringCreateWithCString(NULL, defaults->serial, kCFStringEncodingUTF8);
550			if(tmpStr) {
551				CFDictionaryAddValue(tmpDict, CFSTR("ScSerialNumber"), tmpStr);
552				CFRelease(tmpStr);
553			}
554			else {
555				MPDebug("setDefaults: error creating CFString for serial number");
556				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
557			}
558		}
559
560		if(defaults->printName) {
561			tmpStr = CFStringCreateWithCString(NULL, defaults->printName, kCFStringEncodingUTF8);
562			if(tmpStr) {
563				CFDictionaryAddValue(tmpDict, CFSTR("ScDesc"), tmpStr);
564				CFRelease(tmpStr);
565			}
566			else {
567				MPDebug("setDefaults: error creating CFString for description");
568				CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
569			}
570		}
571	}
572
573	if(mUrlPath) {
574		tmpStr = CFStringCreateWithCString(NULL, mUrlPath, kCFStringEncodingUTF8);
575		if(tmpStr) {
576			CFDictionaryAddValue(tmpDict, CFSTR("Path"), tmpStr);
577			CFRelease(tmpStr);
578		}
579		else {
580			MPDebug("setDefaults: error creating CFString for path");
581			CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
582		}
583	}
584	CFDictionaryRef oldDict = mDict;
585	mDict = CFDictionaryCreateCopy(NULL, tmpDict);
586	if(mDict == NULL) {
587		mDict = oldDict;	// first do no harm
588		CFRelease(tmpDict);
589		MPDebug("setDefaults: error creating new dictionary");
590		CssmError::throwMe(CSSMERR_CSSM_MDS_ERROR);
591	}
592	CFRelease(oldDict);
593	CFRelease(tmpDict);
594}
595
596
597} // end namespace Security
598