2  * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved. 
   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 
  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. 
  20    File:      MDSAttrParser.cpp 
  22    Contains:  Classes to parse XML plists and fill in MDS DBs with the 
  23               attributes found there.   
  25    Copyright (c) 2001,2011-2014 Apple Inc. All Rights Reserved. 
  28 #include "MDSAttrParser.h" 
  29 #include "MDSAttrUtils.h" 
  30 #include "MDSDictionary.h" 
  31 #include <security_utilities/logging.h> 
  32 #include <security_utilities/cfutilities.h> 
  33 #include <Security/mds_schema.h> 
  38 MDSAttrParser::MDSAttrParser( 
  39         const char *bundlePath
, 
  41         CSSM_DB_HANDLE objectHand
, 
  42         CSSM_DB_HANDLE cdsaDirHand
) : 
  46                 mObjectHand(objectHand
), 
  47                 mCdsaDirHand(cdsaDirHand
), 
  51         /* Only task here is to cook up a CFBundle for the specified path */ 
  52         size_t pathLen 
= strlen(bundlePath
); 
  53         CFURLRef url 
= CFURLCreateFromFileSystemRepresentation(NULL
, 
  54                 (unsigned char *)bundlePath
, 
  58                 Syslog::alert("CFURLCreateFromFileSystemRepresentation(%s) failure", mPath
); 
  59                 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
); 
  62         /* FIXME - this leaks 28 bytes each time thru, even though we CFRelease the 
  63          * mBundle in out destructor. I think this is a CF leak. */ 
  64         mBundle 
= CFBundleCreate(NULL
, url
); 
  67                 Syslog::alert("CFBundleCreate(%s) failure", mPath
); 
  68                 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
); 
  70         mPath 
= new char[pathLen 
+ 1]; 
  71         strcpy(mPath
, bundlePath
); 
  74 MDSAttrParser::~MDSAttrParser() 
  81 /********************* 
  85         get all *.mdsinfo files; 
  87                 get contents of that file as dictionary; 
  90                         parse this mdsinfo --> MDS_OBJECT_RECORDTYPE, MDS_CDSADIR_CSSM_RECORDTYPE; 
  93                         parse this info --> MDS_OBJECT_RECORDTYPE, MDS_CDSADIR_COMMON_RECORDTYPE; 
  95                         recordType = lookup("MdsRecordType"); 
  96                         dispatch to recordtype-specific parsing; 
 102 void MDSAttrParser::parseAttrs(CFStringRef subdir
) 
 104         /* get all *.mdsinfo files */ 
 105         CFRef
<CFArrayRef
> bundleInfoFiles 
= CFBundleCopyResourceURLsOfType(mBundle
, 
 106                 CFSTR(MDS_INFO_TYPE
), 
 108         if(!bundleInfoFiles
) { 
 109                 Syslog::alert("MDSAttrParser: no mdsattr files for %s", mPath
); 
 112         assert(CFGetTypeID(bundleInfoFiles
) == CFArrayGetTypeID()); 
 114         /* process each .mdsinfo file */ 
 115         CFIndex numFiles 
= CFArrayGetCount(bundleInfoFiles
); 
 116         for(CFIndex i
=0; i
<numFiles
; i
++) { 
 117                 /* get filename as CFURL */ 
 118                 CFURLRef infoUrl 
= NULL
; 
 120                 infoUrl 
= reinterpret_cast<CFURLRef
>( 
 121                         CFArrayGetValueAtIndex(bundleInfoFiles
, i
)); 
 122                 if(infoUrl 
== NULL
) { 
 123                         MPDebug("MDSAttrParser: CFBundleCopyResourceURLsOfType screwup 1"); 
 126                 if(CFGetTypeID(infoUrl
) != CFURLGetTypeID()) { 
 127                         MPDebug("MDSAttrParser: CFBundleCopyResourceURLsOfType screwup 2"); 
 131                 // @@@  Workaround for 4234967: skip any filename beginning with "._" 
 132                 CFRef
<CFStringRef
> lastComponent 
= CFURLCopyLastPathComponent(infoUrl
); 
 134                         CFStringRef resFilePfx 
= CFSTR("._"); 
 135                         // setting the search length and location like this permits,  
 136                         // e.g., ".foo.mdsinfo" to be valid 
 137                         CFIndex resFilePfxLen 
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(resFilePfx
), kCFStringEncodingUTF8
); 
 138                         CFRange range 
= CFRangeMake(0, resFilePfxLen
); 
 139                         Boolean skip 
= CFStringFindWithOptions(lastComponent
,  
 143                                                                                                    NULL
/*returned substr*/); 
 145                                 Syslog::warning("MDSAttrParser: ignoring resource file"); 
 150                 parseFile(infoUrl
, subdir
); 
 151         } /* for each mdsinfo */ 
 154 void MDSAttrParser::parseFile(CFURLRef infoUrl
, CFStringRef subdir
) 
 156         CFStringRef infoType 
= NULL
; 
 158         /* Get contents of mdsinfo file as dictionary */ 
 159         MDSDictionary 
mdsDict(infoUrl
, subdir
, mPath
); 
 160         /* Make sure we set all possible MDS values before checking for GUID */ 
 161         mdsDict
.setDefaults(mDefaults
); 
 163                 CFStringRef guid 
= (CFStringRef
)mdsDict
.lookup("ModuleID", true, CFStringGetTypeID()); 
 165                         CFIndex copylen 
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(guid
), kCFStringEncodingUTF8
) + 1/*nul terminator*/; 
 166                         mGuid 
= new char[copylen
]; 
 167                         if (false == CFStringGetCString(guid
, mGuid
, copylen
, kCFStringEncodingUTF8
)) { 
 168                                 logFileError("Error copying GUID", infoUrl
, NULL
, NULL
); 
 171                                 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
); 
 175                         logFileError("No GUID associated with plugin?", infoUrl
, NULL
, NULL
); 
 176                         CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
); 
 180         MPDebug("Parsing mdsinfo file %s", mdsDict
.fileDesc()); 
 182         /* Determine what kind of info file this is and dispatch accordingly */ 
 183         infoType 
= (CFStringRef
)mdsDict
.lookup(CFSTR(MDS_INFO_FILE_TYPE
), 
 184                 true, CFStringGetTypeID()); 
 185         if(infoType 
== NULL
) { 
 186                 logFileError("Malformed MDS Info file", infoUrl
, NULL
, NULL
); 
 187                 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
); 
 190         /* be robust here, errors in these low-level routines do not affect 
 191          * the rest of our task */ 
 193                 if(CFStringCompare(infoType
, CFSTR(MDS_INFO_FILE_TYPE_CSSM
), 0)  
 194                                 == kCFCompareEqualTo
) { 
 195                         parseCssmInfo(&mdsDict
); 
 197                 else if(CFStringCompare(infoType
, CFSTR(MDS_INFO_FILE_TYPE_PLUGIN
), 0)  
 198                                 == kCFCompareEqualTo
) { 
 199                         parsePluginCommon(&mdsDict
); 
 201                 else if(CFStringCompare(infoType
, CFSTR(MDS_INFO_FILE_TYPE_RECORD
), 0)  
 202                                 == kCFCompareEqualTo
) { 
 203                         parsePluginSpecific(&mdsDict
); 
 206                         logFileError("Malformed MDS Info file", infoUrl
, NULL
, NULL
); 
 214 void MDSAttrParser::logFileError( 
 217         CFStringRef errStr
,             // optional if you have it 
 218         SInt32 
*errNo
)                  // optional if you have it 
 220         CFStringRef urlStr 
= CFURLGetString(fileUrl
); 
 221         const char *cUrlStr 
= CFStringGetCStringPtr(urlStr
, kCFStringEncodingUTF8
); 
 222         char* stringBuffer 
= NULL
; 
 226         CFIndex maxLen 
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(urlStr
), kCFStringEncodingUTF8
) + 1; 
 227         stringBuffer 
= (char*) malloc(maxLen
); 
 228         CFStringGetCString(urlStr
, stringBuffer
, maxLen
, kCFStringEncodingUTF8
); 
 229         cUrlStr 
= stringBuffer
; 
 233         const char *cerrStr 
= CFStringGetCStringPtr(errStr
, kCFStringEncodingUTF8
); 
 238             CFIndex maxLen 
= CFStringGetMaximumSizeForEncoding(CFStringGetLength(errStr
), kCFStringEncodingUTF8
) + 1; 
 239             sbuf2 
= (char*) malloc(maxLen
); 
 240             CFStringGetCString(urlStr
, sbuf2
, maxLen
, kCFStringEncodingUTF8
); 
 244                 Syslog::alert("MDS: %s: bundle %s url %s: error %s", 
 245                         op
, mPath
, cUrlStr
, cerrStr
); 
 253                 Syslog::alert("MDS: %s: bundle %s url %s: error %d", 
 254                         op
, mPath
, cUrlStr
, (int)(errNo 
? *errNo 
: 0)); 
 257     if (stringBuffer 
!= NULL
) 
 264  * Parse a CSSM info file. 
 266 void MDSAttrParser::parseCssmInfo( 
 267         MDSDictionary 
*mdsDict
) 
 269         /* first get object info */ 
 270         parseObjectRecord(mdsDict
); 
 272         /* now CSSM relation */ 
 273         const RelationInfo 
*relationInfo 
=  
 274                 MDSRecordTypeToRelation(MDS_CDSADIR_CSSM_RECORDTYPE
); 
 275         assert(relationInfo 
!= NULL
); 
 276         parseMdsRecord(mdsDict
, relationInfo
, mCdsaDirHand
); 
 280  * Parse a PluginCommon file. 
 282 void MDSAttrParser::parsePluginCommon( 
 283         MDSDictionary 
*mdsDict
) 
 286         /* first get object info */ 
 287         parseObjectRecord(mdsDict
); 
 289         /* now common relation */ 
 290         const RelationInfo 
*relationInfo 
=  
 291                 MDSRecordTypeToRelation(MDS_CDSADIR_COMMON_RECORDTYPE
); 
 292         assert(relationInfo 
!= NULL
); 
 293         parseMdsRecord(mdsDict
, relationInfo
, mCdsaDirHand
); 
 297  * Parse a Plugin Specific file. 
 299 void MDSAttrParser::parsePluginSpecific( 
 300         MDSDictionary 
*mdsDict
) 
 302         /* determine record type from the file itself */ 
 303         CFStringRef recordTypeStr 
=  
 304                 (CFStringRef
)mdsDict
->lookup(MDS_INFO_FILE_RECORD_TYPE
, 
 305                         true, CFStringGetTypeID()); 
 306         if(recordTypeStr 
== NULL
) { 
 307                 MPDebug("%s: no %s record found\n", mdsDict
->fileDesc(), 
 308                         MDS_INFO_FILE_RECORD_TYPE
); 
 312         /* convert to a known schema */ 
 313         const char *recordTypeCStr 
= MDSCFStringToCString(recordTypeStr
); 
 314         const RelationInfo 
*relationInfo 
= MDSRecordTypeNameToRelation(recordTypeCStr
); 
 315         if(relationInfo 
== NULL
) { 
 316                 Syslog::alert("MDS file %s has unsupported record type %s",  
 317                         mdsDict
->fileDesc(), recordTypeCStr
); 
 318                 MPDebug("MDS file %s has unsupported record type %s",  
 319                         mdsDict
->fileDesc(), recordTypeCStr
); 
 320                 delete [] recordTypeCStr
; 
 323         MPDebug("Parsing MDS file %s, recordType %s", mdsDict
->fileDesc(), recordTypeCStr
); 
 324         delete [] recordTypeCStr
; 
 326         /* handle special cases here */ 
 327         switch(relationInfo
->DataRecordType
) { 
 328                 case MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE
: 
 329                         parseCspCapabilitiesRecord(mdsDict
); 
 331                 case MDS_CDSADIR_TP_OIDS_RECORDTYPE
: 
 332                         parseTpPolicyOidsRecord(mdsDict
); 
 335                         /* all (normal) linear schema */ 
 336                         parseMdsRecord(mdsDict
, relationInfo
, mCdsaDirHand
); 
 342  * Given an open MDSDictionary, create an MDS_OBJECT_RECORDTYPE record and  
 343  * add it to mObjectHand. Used when parsing both CSSM records and MOduleCommon 
 346 void MDSAttrParser::parseObjectRecord( 
 347         MDSDictionary 
*mdsDict
) 
 349         assert(mdsDict 
!= NULL
); 
 350         assert(mObjectHand 
!= 0); 
 351         parseMdsRecord(mdsDict
, &kObjectRelation
, mObjectHand
); 
 356  * Given an open dictionary and a RelationInfo defining a schema, fetch all 
 357  * attributes associated with the specified schema from the dictionary 
 358  * and write them to specified DB. 
 360 void MDSAttrParser::parseMdsRecord( 
 361         MDSDictionary                           
*mdsDict
, 
 362         const RelationInfo                      
*relInfo
, 
 363         CSSM_DB_HANDLE                          dbHand
) 
 365         assert(mdsDict 
!= NULL
); 
 366         assert(relInfo 
!= NULL
); 
 370          * malloc an CSSM_DB_ATTRIBUTE_DATA array associated with specified schema. 
 372         unsigned numSchemaAttrs 
= relInfo
->NumberOfAttributes
; 
 373         CSSM_DB_ATTRIBUTE_DATA 
*dbAttrs 
= new CSSM_DB_ATTRIBUTE_DATA
[numSchemaAttrs
]; 
 376          * Grind thru the attributes in the specified schema. Do not assume the presence 
 377          * of any given attribute. 
 379         uint32 foundAttrs 
= 0;   
 380         mdsDict
->lookupAttributes(relInfo
, dbAttrs
, foundAttrs
); 
 382         /* write to the DB */ 
 383         MDSInsertRecord(dbAttrs
, foundAttrs
, relInfo
->DataRecordType
, mDl
, dbHand
); 
 385         MDSFreeDbRecordAttrs(dbAttrs
, foundAttrs
); 
 390  * Parse CSP capabilities. This is much more complicated than most records.  
 391  * The propertly list (*.mdsinfo) is set up like this: 
 396  *    Capabilities(Array) { 
 397  *       index 0(Dictionary) { 
 398  *           AlgType(String)                                    -- CSSM_ALGID_SHA1 
 399  *           ContextType(String)                                -- CSSM_ALGCLASS_DIGEST 
 400  *           UseeTag(String)                                    -- CSSM_USEE_NONE 
 401  *           Description(String)                                -- "SHA1 Digest" 
 403  *              index 0(Dictionary) 
 404  *                 AttributeType(String)                -- CSSM_ATTRIBUTE_OUTPUT_SIZE 
 405  *                 AttributeValue(Array) { 
 406  *                    index 0(Number)                   -- 20 
 415  * The plist can specify multiple Capabilities, multiple Attributes for each 
 416  * Capability, and multiple values for each Attribute. (Note that MULTI_UINT32 
 417  * in the DB is represented in the plist as an Array of Numbers.) Each element  
 418  * of each Attributes array maps to one record in the DB. The GroupID attribute  
 419  * of a record is the index into the plist's Capabilities array.  
 421 void MDSAttrParser::parseCspCapabilitiesRecord( 
 422         MDSDictionary 
*mdsDict
) 
 425          * Malloc an attribute array big enough for the whole schema. We're going  
 426          * to re-use this array every time we write a new record. Portions of  
 427          * the array are invariant for some inner loops. 
 429         const RelationInfo 
*topRelInfo 
=  
 430                 MDSRecordTypeToRelation(MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE
); 
 431         assert(topRelInfo 
!= NULL
); 
 432         uint32 numInAttrs 
= topRelInfo
->NumberOfAttributes
; 
 433         CSSM_DB_ATTRIBUTE_DATA_PTR outAttrs 
= new CSSM_DB_ATTRIBUTE_DATA
[numInAttrs
]; 
 435         /* these attrs are only set once, then they remain invariant */ 
 436         uint32 numTopLevelAttrs
; 
 437         mdsDict
->lookupAttributes(&CSPCapabilitiesDict1RelInfo
, outAttrs
,  
 440         /* obtain Capabilities array */ 
 441         CFRef
<CFArrayRef
> capArray 
= (CFArrayRef
)mdsDict
->lookupWithIndirect("Capabilities", 
 445                 /* well we did not get very far.... */ 
 446                 MPDebug("parseCspCapabilitiesRecord: no (or bad) Capabilities"); 
 452          * Descend into Capabilities array. Each element is a dictionary defined  
 453          * by CSPCapabilitiesDict2RelInfo. 
 455         CFIndex capArraySize 
= CFArrayGetCount(capArray
); 
 457         for(capDex
=0; capDex
<capArraySize
; capDex
++) { 
 458                 MPDebug("...parsing Capability %d", (int)capDex
); 
 459                 CFDictionaryRef capDict 
=  
 460                         (CFDictionaryRef
)CFArrayGetValueAtIndex(capArray
, capDex
); 
 461                 if((capDict 
== NULL
) ||  
 462                    (CFGetTypeID(capDict
) != CFDictionaryGetTypeID())) { 
 463                         MPDebug("parseCspCapabilitiesRecord: bad Capabilities element"); 
 466                 MDSDictionary 
capDictMds(capDict
); 
 469                  * Append this dictionary's attributes to outAttrs, after the fixed 
 470                  * attributes from CSPCapabilitiesDict1RelInfo. 
 472                 uint32 numCapDictAttrs
; 
 473                 capDictMds
.lookupAttributes(&CSPCapabilitiesDict2RelInfo
, 
 474                         &outAttrs
[numTopLevelAttrs
], 
 478                  * Append the GroupId attribute, which we infer from the current index  
 479                  * into Capabilitites.  However, thou shalt not use > 4-byte values  
 480          * for MDS, so convert from CFIndex first.   
 482         if (capDex 
> uint32(~0)) { 
 483             MPDebug("parseCspCapabilitiesRecord: too large an index for MDS"); 
 486         uint32 index32 
= uint32(capDex
); 
 487                 MDSRawValueToDbAttr(&index32
, sizeof(index32
), CSSM_DB_ATTRIBUTE_FORMAT_UINT32
,  
 488                         "GroupId", outAttrs
[numTopLevelAttrs 
+ numCapDictAttrs
]); 
 492                  * Now descend into the array of this capability's attributes. 
 493                  * Each element is a dictionary defined by 
 494                  * by CSPCapabilitiesDict3RelInfo. 
 496                 CFArrayRef attrArray 
= (CFArrayRef
)capDictMds
.lookup("Attributes", 
 497                         true, CFArrayGetTypeID()); 
 498                 if(attrArray 
== NULL
) { 
 499                         MPDebug("parseCspCapabilitiesRecord: no (or bad) Attributes"); 
 502                 CFIndex attrArraySize 
= CFArrayGetCount(attrArray
); 
 504                 for(attrDex
=0; attrDex
<attrArraySize
; attrDex
++) { 
 505                         MPDebug("   ...parsing Attribute %d", (int)attrDex
); 
 506                         CFDictionaryRef attrDict 
=  
 507                                 (CFDictionaryRef
)CFArrayGetValueAtIndex(attrArray
, attrDex
); 
 508                         if((attrDict 
== NULL
) ||  
 509                            (CFGetTypeID(attrDict
) != CFDictionaryGetTypeID())) { 
 510                                 MPDebug("parseCspCapabilitiesRecord: bad Attributes element"); 
 513                         MDSDictionary 
attrDictMds(attrDict
); 
 516                          * Append this dictionary's attributes to outAttrs, after the fixed 
 517                          * attributes from CSPCapabilitiesDict1RelInfo and this capability's 
 518                          * CSPCapabilitiesDict2RelInfo. 
 520                         uint32 numAttrDictAttrs
; 
 521                         attrDictMds
.lookupAttributes(&CSPCapabilitiesDict3RelInfo
, 
 522                                 &outAttrs
[numTopLevelAttrs 
+ numCapDictAttrs
], 
 526                         MDSInsertRecord(outAttrs
, 
 527                                 numTopLevelAttrs 
+ numCapDictAttrs 
+ numAttrDictAttrs
, 
 528                                 MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE
,  
 532                         /* just free the attrs we allocated in this loop */ 
 533                         MDSFreeDbRecordAttrs(&outAttrs
[numTopLevelAttrs 
+ numCapDictAttrs
], 
 535                 }       /* for each attribute */ 
 536                 /* just free the attrs we allocated in this loop */ 
 537                 MDSFreeDbRecordAttrs(&outAttrs
[numTopLevelAttrs
], numCapDictAttrs
); 
 538         }               /* for each capability */ 
 540         MDSFreeDbRecordAttrs(outAttrs
, numTopLevelAttrs
); 
 545  * Parse TP Policy OIDs.  
 546  * The propertly list (*.mdsinfo) is set up like this: 
 552  *       index 0(Dictionary) { 
 553  *           OID(Data)                                                  -- <092a8648 86f76364 0102> 
 554  *           Value(Data)                                                -- optional, OID-specific  
 559  * The plist can specify multiple Policies. Each element of the Policies  
 560  * array maps to one record in the DB.   
 562 void MDSAttrParser::parseTpPolicyOidsRecord( 
 563         MDSDictionary 
*mdsDict
) 
 566          * Malloc an attribute array big enough for the whole schema. We're going  
 567          * to re-use this array every time we write a new record. Portions of  
 568          * the array are invariant for some inner loops. 
 570         const RelationInfo 
*topRelInfo 
=  
 571                 MDSRecordTypeToRelation(MDS_CDSADIR_TP_OIDS_RECORDTYPE
); 
 572         assert(topRelInfo 
!= NULL
); 
 573         uint32 numInAttrs 
= topRelInfo
->NumberOfAttributes
; 
 574         CSSM_DB_ATTRIBUTE_DATA_PTR outAttrs 
= new CSSM_DB_ATTRIBUTE_DATA
[numInAttrs
]; 
 576         /* these attrs are only set once, then they remain invariant */ 
 577         uint32 numTopLevelAttrs
; 
 578         mdsDict
->lookupAttributes(&TpPolicyOidsDict1RelInfo
, outAttrs
,  
 581         /* obtain Policies array */ 
 582         CFArrayRef policyArray 
= (CFArrayRef
)mdsDict
->lookup("Policies", 
 583                 true, CFArrayGetTypeID()); 
 584         if(policyArray 
== NULL
) { 
 585                 /* well we did not get very far.... */ 
 586                 MPDebug("parseTpPolicyOidsRecord: no (or bad) Policies"); 
 592          * Descend into Policies array. Each element is a dictionary defined  
 593          * by TpPolicyOidsDict2RelInfo. 
 595         CFIndex policyArraySize 
= CFArrayGetCount(policyArray
); 
 597         for(policyDex
=0; policyDex
<policyArraySize
; policyDex
++) { 
 598                 MPDebug("...parsing Policy %d", (int)policyDex
); 
 599                 CFDictionaryRef policyDict 
=  
 600                         (CFDictionaryRef
)CFArrayGetValueAtIndex(policyArray
, policyDex
); 
 601                 if((policyDict 
== NULL
) ||  
 602                    (CFGetTypeID(policyDict
) != CFDictionaryGetTypeID())) { 
 603                         MPDebug("parseTpPolicyOidsRecord: bad Policies element"); 
 606                 MDSDictionary 
policyDictMds(policyDict
); 
 609                  * Append this dictionary's attributes to outAttrs, after the fixed 
 610                  * attributes from TpPolicyOidsDict1RelInfo. 
 612                 uint32 numPolicyDictAttrs
; 
 613                 policyDictMds
.lookupAttributes(&TpPolicyOidsDict2RelInfo
, 
 614                         &outAttrs
[numTopLevelAttrs
], 
 619                 MDSInsertRecord(outAttrs
, 
 620                         numTopLevelAttrs 
+ numPolicyDictAttrs
, 
 621                         MDS_CDSADIR_TP_OIDS_RECORDTYPE
,  
 625                 /* free the attrs allocated in this loop */ 
 626                 MDSFreeDbRecordAttrs(outAttrs 
+ numTopLevelAttrs
, numPolicyDictAttrs
); 
 627         }               /* for each policy */ 
 628         MDSFreeDbRecordAttrs(outAttrs
, numTopLevelAttrs
); 
 633 } // end namespace Security