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