MDSSession.cpp
1 /* 2 * Copyright (c) 2000-2001,2011-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 #include "MDSSession.h" 20 21 #include <security_cdsa_plugin/DbContext.h> 22 #include "MDSModule.h" 23 #include "MDSAttrParser.h" 24 #include "MDSAttrUtils.h" 25 26 #include <memory> 27 #include <Security/cssmerr.h> 28 #include <security_utilities/logging.h> 29 #include <security_utilities/debugging.h> 30 #include <security_utilities/cfutilities.h> 31 #include <security_cdsa_client/dlquery.h> 32 #include <securityd_client/ssclient.h> 33 #include <Security/mds_schema.h> 34 #include <CoreFoundation/CFBundle.h> 35 36 #include <sys/types.h> 37 #include <sys/param.h> 38 #include <dirent.h> 39 #include <fcntl.h> 40 #include <security_utilities/simulatecrash_assert.h> 41 #include <time.h> 42 #include <string> 43 #include <unistd.h> 44 #include <syslog.h> 45 46 #include "LegacyAPICounts.h" 47 48 using namespace CssmClient; 49 50 /* 51 * The layout of the various MDS DB files on disk is as follows: 52 * 53 * /var/db/mds -- owner = root, mode = 01777, world writable, sticky 54 * system/ -- owner = root, mode = 0755 55 * mdsObject.db -- owner = root, mode = 0644, object DB 56 * mdsDirectory.db -- owner = root, mode = 0644, MDS directory DB 57 * mds.lock -- temporary, owner = root, protects creation of and 58 * updates to previous two files 59 * mds.install.lock -- owner = root, protects MDS_Install operation 60 * <uid>/ -- owner = <uid>, mode = 0700 61 * mdsObject.db -- owner = <uid>, mode = 000, object DB 62 * mdsDirectory.db -- owner = <uid>, mode = 000, MDS directory DB 63 * mds.lock -- owner = <uid>, protects updates of previous two files 64 * 65 * The /var/db/mds/system directory is created at OS install time. The DB files in 66 * it are created by root at MDS_Install time. The ownership and mode of this directory and 67 * of its parent is also re-checked and forced to the correct state at MDS_Install time. 68 * Each user has their own private directory with two DB files and a lock. The first time 69 * a user accesses MDS, the per-user directory is created and the per-user DB files are 70 * created as copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system 71 * DB files when they are the source of these copies; this is the same mechanism 72 * used by the underlying AtomicFile. 73 * 74 * The sticky bit in /var/db/mds ensures that users cannot modify other users' private 75 * MDS directories. 76 */ 77 namespace Security 78 { 79 80 /* 81 * Nominal location of Security.framework. 82 */ 83 #define MDS_SYSTEM_PATH "/System/Library/Frameworks" 84 #define MDS_SYSTEM_FRAME "Security.framework" 85 86 /* 87 * Nominal location of standard plugins. 88 */ 89 #define MDS_BUNDLE_PATH "/System/Library/Security" 90 #define MDS_BUNDLE_EXTEN ".bundle" 91 92 93 /* 94 * Location of MDS database and lock files. 95 */ 96 #define MDS_BASE_DB_DIR "/private/var/db/mds" 97 #define MDS_SYSTEM_DB_COMP "system" 98 #define MDS_SYSTEM_DB_DIR MDS_BASE_DB_DIR "/" MDS_SYSTEM_DB_COMP 99 #define MDS_USER_DB_COMP "mds" 100 101 #define MDS_LOCK_FILE_NAME "mds.lock" 102 #define MDS_INSTALL_LOCK_NAME "mds.install.lock" 103 #define MDS_OBJECT_DB_NAME "mdsObject.db" 104 #define MDS_DIRECT_DB_NAME "mdsDirectory.db" 105 106 #define MDS_INSTALL_LOCK_PATH MDS_SYSTEM_DB_DIR "/" MDS_INSTALL_LOCK_NAME 107 #define MDS_OBJECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_OBJECT_DB_NAME 108 #define MDS_DIRECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_DIRECT_DB_NAME 109 110 /* hard coded modes and a symbolic UID for root */ 111 #define MDS_BASE_DB_DIR_MODE (mode_t)0755 112 #define MDS_SYSTEM_DB_DIR_MODE (mode_t)0755 113 #define MDS_SYSTEM_DB_MODE (mode_t)0644 114 #define MDS_USER_DB_DIR_MODE (mode_t)0700 115 #define MDS_USER_DB_MODE (mode_t)0600 116 #define MDS_SYSTEM_UID (uid_t)0 117 118 /* 119 * Location of per-user bundles, relative to home directory. 120 * Per-user DB files are in MDS_BASE_DB_DIR/<uid>/. 121 */ 122 #define MDS_USER_BUNDLE "Library/Security" 123 124 /* time to wait in ms trying to acquire lock */ 125 #define DB_LOCK_TIMEOUT (2 * 1000) 126 127 /* Minimum interval, in seconds, between rescans for plugin changes */ 128 #define MDS_SCAN_INTERVAL 5 129 130 /* trace file I/O */ 131 #define MSIoDbg(args...) secinfo("MDS_IO", ## args) 132 133 /* Trace cleanDir() */ 134 #define MSCleanDirDbg(args...) secinfo("MDS_CleanDir", ## args) 135 136 static std::string GetMDSBaseDBDir(bool isRoot) 137 { 138 // what we return depends on whether or not we are root 139 string retValue; 140 if (isRoot) 141 { 142 retValue = MDS_SYSTEM_DB_DIR; 143 } 144 else 145 { 146 char strBuffer[PATH_MAX + 1]; 147 size_t result = confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer)); 148 if (result == 0) 149 { 150 // we have an error, log it 151 syslog(LOG_CRIT, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error: %d", errno); 152 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 153 } 154 155 retValue = strBuffer; 156 } 157 158 return retValue; 159 } 160 161 162 163 static std::string GetMDSDBDir() 164 { 165 string retValue; 166 bool isRoot = geteuid() == 0; 167 168 if (isRoot) 169 { 170 retValue = MDS_SYSTEM_DB_DIR; 171 } 172 else 173 { 174 retValue = GetMDSBaseDBDir(isRoot) + "/" + MDS_USER_DB_COMP; 175 } 176 177 return retValue; 178 } 179 180 181 182 static std::string GetMDSObjectDBPath() 183 { 184 return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME; 185 } 186 187 188 189 static std::string GetMDSDirectDBPath() 190 { 191 return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH; 192 } 193 194 195 196 static std::string GetMDSDBLockPath() 197 { 198 return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME; 199 } 200 201 202 203 /* 204 * Given a path to a directory, remove everything in the directory except for the optional 205 * keepFileNames. Returns 0 on success, else an errno. 206 */ 207 static int cleanDir( 208 const char *dirPath, 209 const char **keepFileNames, // array of strings, size numKeepFileNames 210 unsigned numKeepFileNames) 211 { 212 DIR *dirp; 213 struct dirent *dp; 214 char fullPath[MAXPATHLEN]; 215 int rtn = 0; 216 217 MSCleanDirDbg("cleanDir(%s) top", dirPath); 218 if ((dirp = opendir(dirPath)) == NULL) { 219 rtn = errno; 220 MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn); 221 return rtn; 222 } 223 224 for(;;) { 225 bool skip = false; 226 const char *d_name = NULL; 227 228 /* this block is for breaking on unqualified entries */ 229 do { 230 errno = 0; 231 dp = readdir(dirp); 232 if(dp == NULL) { 233 /* end of directory or error */ 234 rtn = errno; 235 if(rtn) { 236 MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath, rtn); 237 } 238 break; 239 } 240 d_name = dp->d_name; 241 242 /* skip "." and ".." */ 243 if( (d_name[0] == '.') && 244 ( (d_name[1] == '\0') || 245 ( (d_name[1] == '.') && (d_name[2] == '\0') ) ) ) { 246 skip = true; 247 break; 248 } 249 250 /* skip entries in keepFileNames */ 251 for(unsigned dex=0; dex<numKeepFileNames; dex++) { 252 if(!strcmp(keepFileNames[dex], d_name)) { 253 skip = true; 254 break; 255 } 256 } 257 } while(0); 258 if(rtn || (dp == NULL)) { 259 /* one way or another, we're done */ 260 break; 261 } 262 if(skip) { 263 /* try again */ 264 continue; 265 } 266 267 /* We have an entry to delete. Delete it, or recurse. */ 268 269 snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, d_name); 270 if(dp->d_type == DT_DIR) { 271 /* directory. Clean it, then delete. */ 272 MSCleanDirDbg("cleanDir recursing for dir %s", fullPath); 273 rtn = cleanDir(fullPath, NULL, 0); 274 if(rtn) { 275 break; 276 } 277 MSCleanDirDbg("cleanDir deleting dir %s", fullPath); 278 if(rmdir(fullPath)) { 279 rtn = errno; 280 MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn); 281 break; 282 } 283 } 284 else { 285 MSCleanDirDbg("cleanDir deleting file %s", fullPath); 286 if(unlink(fullPath)) { 287 rtn = errno; 288 MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn); 289 break; 290 } 291 } 292 293 /* 294 * Back to beginning of directory for clean scan. 295 * Normally we'd just do a rewinddir() here but the RAMDisk filesystem, 296 * used when booting from DVD, does not implement that properly. 297 */ 298 closedir(dirp); 299 if ((dirp = opendir(dirPath)) == NULL) { 300 rtn = errno; 301 MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn); 302 return rtn; 303 } 304 } /* main loop */ 305 306 closedir(dirp); 307 return rtn; 308 } 309 310 /* 311 * Determine if a file exists as regular file with specified owner. Returns true if so. 312 * If the purge argument is true, and there is something at the specified path that 313 * doesn't meet spec, we do everything we can to delete it. If that fails we throw 314 * CssmError(CSSM_ERRCODE_MDS_ERROR). If the delete succeeds we return false. 315 * Returns the stat info on success for further processing by caller. 316 */ 317 static bool doesFileExist( 318 const char *filePath, 319 uid_t forUid, 320 bool purge, 321 struct stat &sb) // RETURNED 322 { 323 MSIoDbg("stat %s in doesFileExist", filePath); 324 if(lstat(filePath, &sb)) { 325 /* doesn't exist or we can't even get to it. */ 326 if(errno == ENOENT) { 327 return false; 328 } 329 if(purge) { 330 /* If we can't stat it we sure can't delete it. */ 331 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 332 } 333 return false; 334 } 335 336 /* it's there...how does it look? */ 337 mode_t fileType = sb.st_mode & S_IFMT; 338 #ifdef DARLING 339 // we don't care about UIDs in Darling 340 if((fileType == S_IFREG)) { 341 #else 342 if((fileType == S_IFREG) && (sb.st_uid == forUid)) { 343 #endif 344 return true; 345 } 346 if(!purge) { 347 return false; 348 } 349 350 /* not what we want: get rid of it. */ 351 if(fileType == S_IFDIR) { 352 /* directory: clean then remove */ 353 if(cleanDir(filePath, NULL, 0)) { 354 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 355 } 356 if(rmdir(filePath)) { 357 MSDebug("rmdir(%s) returned %d", filePath, errno); 358 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 359 } 360 } 361 else { 362 if(unlink(filePath)) { 363 MSDebug("unlink(%s) returned %d", filePath, errno); 364 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 365 } 366 } 367 368 /* caller should be somewhat happy */ 369 return false; 370 } 371 372 /* 373 * Determine if both of the specified DB files exist as accessible regular files with specified 374 * owner. Returns true if they do. 375 * 376 * If the purge argument is true, we'll ensure that either both files exist with 377 * the right owner, or neither of the files exist on exit. An error on that operation 378 * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed). 379 * Returns the stat info for both files on success for further processing by caller. 380 */ 381 static bool doFilesExist( 382 const char *objDbFile, 383 const char *directDbFile, 384 uid_t forUid, 385 bool purge, // false means "passive" check 386 struct stat &objDbSb, // RETURNED 387 struct stat &directDbSb) // RETURNED 388 389 { 390 bool objectExist = doesFileExist(objDbFile, forUid, purge, objDbSb); 391 bool directExist = doesFileExist(directDbFile, forUid, purge, directDbSb); 392 if(objectExist && directExist) { 393 return true; 394 } 395 else if(!purge) { 396 return false; 397 } 398 399 /* 400 * At least one does not exist - ensure neither of them do. 401 * Note that if we got this far, we know the one that exists is a regular file 402 * so it's safe to just unlink it. 403 */ 404 if(objectExist) { 405 if(unlink(objDbFile)) { 406 MSDebug("unlink(%s) returned %d", objDbFile, errno); 407 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 408 } 409 } 410 if(directExist) { 411 if(unlink(directDbFile)) { 412 MSDebug("unlink(%s) returned %d", directDbFile, errno); 413 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 414 } 415 } 416 return false; 417 } 418 419 /* 420 * Determine if specified directory exists with specified owner and mode. 421 * Returns true if copacetic, else returns false and also indicates 422 * via the directStatus out param what went wrong. 423 */ 424 typedef enum { 425 MDS_NotPresent, /* nothing there */ 426 MDS_NotDirectory, /* not a directory */ 427 MDS_BadOwnerMode, /* wrong owner or mode */ 428 MDS_Access /* couldn't search the directories */ 429 } MdsDirectStatus; 430 431 static bool doesDirectExist( 432 const char *dirPath, 433 uid_t forUid, 434 mode_t mode, 435 MdsDirectStatus &directStatus) /* RETURNED */ 436 { 437 struct stat sb; 438 439 MSIoDbg("stat %s in doesDirectExist", dirPath); 440 if (lstat(dirPath, &sb)) { 441 int err = errno; 442 switch(err) { 443 case EACCES: 444 directStatus = MDS_Access; 445 break; 446 case ENOENT: 447 directStatus = MDS_NotPresent; 448 break; 449 /* Any others? Is this a good SWAG to handle the default? */ 450 default: 451 directStatus = MDS_NotDirectory; 452 break; 453 } 454 return false; 455 } 456 mode_t fileType = sb.st_mode & S_IFMT; 457 if(fileType != S_IFDIR) { 458 directStatus = MDS_NotDirectory; 459 return false; 460 } 461 #ifndef DARLING 462 if(sb.st_uid != forUid) { 463 directStatus = MDS_BadOwnerMode; 464 return false; 465 } 466 #endif 467 if((sb.st_mode & 07777) != mode) { 468 directStatus = MDS_BadOwnerMode; 469 return false; 470 } 471 return true; 472 } 473 474 /* 475 * Create specified directory if it doesn't already exist. If there is something 476 * already there that doesn't meet spec (not a directory, wrong mode, wrong owner) 477 * we'll do everything we can do delete what is there and then try to create it 478 * correctly. 479 * 480 * Returns an errno on any unrecoverable error. 481 */ 482 static int createDir( 483 const char *dirPath, 484 uid_t forUid, // for checking - we don't try to set this 485 mode_t dirMode) 486 { 487 MdsDirectStatus directStatus; 488 489 if(doesDirectExist(dirPath, forUid, dirMode, directStatus)) { 490 /* we're done */ 491 return 0; 492 } 493 494 /* 495 * Attempt recovery if there is *something* there. 496 * Anything other than "not present" should be considered to be a possible 497 * attack; syslog it. 498 */ 499 int rtn; 500 switch(directStatus) { 501 case MDS_NotPresent: 502 /* normal trivial case: proceed. */ 503 break; 504 505 case MDS_NotDirectory: 506 /* there's a file or symlink in the way */ 507 if(unlink(dirPath)) { 508 rtn = errno; 509 MSDebug("createDir(%s): unlink() returned %d", dirPath, rtn); 510 return rtn; 511 } 512 break; 513 514 case MDS_BadOwnerMode: 515 /* 516 * It's a directory; try to clean it out (which may well fail if we're 517 * not root). 518 */ 519 rtn = cleanDir(dirPath, NULL, 0); 520 if(rtn) { 521 return rtn; 522 } 523 if(rmdir(dirPath)) { 524 rtn = errno; 525 MSDebug("createDir(%s): rmdir() returned %d", dirPath, rtn); 526 return rtn; 527 } 528 /* good to go */ 529 break; 530 531 case MDS_Access: /* hopeless */ 532 MSDebug("createDir(%s): access failure, bailing", dirPath); 533 return EACCES; 534 } 535 rtn = mkdir(dirPath, dirMode); 536 if(rtn) { 537 rtn = errno; 538 MSDebug("createDir(%s): mkdir() returned %d", dirPath, errno); 539 } 540 else { 541 /* make sure umask does't trick us */ 542 rtn = chmod(dirPath, dirMode); 543 if(rtn) { 544 MSDebug("chmod(%s) returned %d", dirPath, errno); 545 } 546 } 547 return rtn; 548 } 549 550 /* 551 * Create an MDS session. 552 */ 553 MDSSession::MDSSession (const Guid *inCallerGuid, 554 const CSSM_MEMORY_FUNCS &inMemoryFunctions) : 555 DatabaseSession(MDSModule::get().databaseManager()), 556 mCssmMemoryFunctions (inMemoryFunctions), 557 mModule(MDSModule::get()) 558 { 559 MSDebug("MDSSession::MDSSession"); 560 561 mCallerGuidPresent = inCallerGuid != nil; 562 if (mCallerGuidPresent) { 563 mCallerGuid = *inCallerGuid; 564 } 565 } 566 567 MDSSession::~MDSSession () 568 { 569 MSDebug("MDSSession::~MDSSession"); 570 } 571 572 void 573 MDSSession::terminate () 574 { 575 MSDebug("MDSSession::terminate"); 576 closeAll(); 577 } 578 579 const char* kExceptionDeletePath = "messages"; 580 581 582 /* 583 * Called by security server via MDS_Install(). 584 */ 585 void 586 MDSSession::install () 587 { 588 // 589 // Installation requires root 590 // 591 if(geteuid() != (uid_t)0) { 592 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 593 } 594 595 // 596 // install() is only (legitimately) called from securityd. 597 // Mark "server mode" so we don't end up waiting for ourselves when the databases open. 598 // 599 mModule.setServerMode(); 600 601 try { 602 /* ensure MDS base directory exists with correct permissions */ 603 if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) { 604 MSDebug("Error creating base MDS dir; aborting."); 605 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 606 } 607 608 /* ensure the the system MDS DB directory exists with correct permissions */ 609 if(createDir(MDS_SYSTEM_DB_DIR, MDS_SYSTEM_UID, MDS_SYSTEM_DB_DIR_MODE)) { 610 MSDebug("Error creating system MDS dir; aborting."); 611 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 612 } 613 614 LockHelper lh; 615 616 if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, DB_LOCK_TIMEOUT)) { 617 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 618 } 619 620 /* 621 * We own the whole MDS system. Clean everything out except for our lock 622 * (and the directory it's in :-) 623 */ 624 625 const char *savedFile = MDS_INSTALL_LOCK_NAME; 626 if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) { 627 /* this should never happen - we're root */ 628 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 629 } 630 631 const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath}; 632 if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) { 633 /* this should never happen - we're root */ 634 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 635 } 636 637 /* 638 * Do initial population of system DBs. 639 */ 640 createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE); 641 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); 642 dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH); 643 } 644 catch(...) { 645 throw; 646 } 647 } 648 649 // 650 // In this implementation, the uninstall() call is not supported since 651 // we do not allow programmatic deletion of the MDS databases. 652 // 653 654 void 655 MDSSession::uninstall () 656 { 657 CssmError::throwMeNoLogging(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); 658 } 659 660 /* 661 * Common private open routine given a full specified path. 662 */ 663 CSSM_DB_HANDLE MDSSession::dbOpen(const char *dbName, bool batched) 664 { 665 static CSSM_APPLEDL_OPEN_PARAMETERS batchOpenParams = { 666 sizeof(CSSM_APPLEDL_OPEN_PARAMETERS), 667 CSSM_APPLEDL_OPEN_PARAMETERS_VERSION, 668 CSSM_FALSE, // do not auto-commit 669 0 // mask - do not use following fields 670 }; 671 672 MSDebug("Opening %s%s", dbName, batched ? " in batch mode" : ""); 673 MSIoDbg("open %s in dbOpen(name, batched)", dbName); 674 CSSM_DB_HANDLE dbHand; 675 DatabaseSession::DbOpen(dbName, 676 NULL, // DbLocation 677 CSSM_DB_ACCESS_READ, 678 NULL, // AccessCred - hopefully optional 679 batched ? &batchOpenParams : NULL, 680 dbHand); 681 return dbHand; 682 } 683 684 /* DatabaseSession routines we need to override */ 685 void MDSSession::DbOpen(const char *DbName, 686 const CSSM_NET_ADDRESS *DbLocation, 687 CSSM_DB_ACCESS_TYPE AccessRequest, 688 const AccessCredentials *AccessCred, 689 const void *OpenParameters, 690 CSSM_DB_HANDLE &DbHandle) 691 { 692 if (!mModule.serverMode()) { 693 /* 694 * Make sure securityd has finished initializing (system) MDS data. 695 * Note that activate() only does IPC once and retains global state after that. 696 */ 697 SecurityServer::ClientSession client(Allocator::standard(), Allocator::standard()); 698 client.activate(); /* contact securityd - won't return until MDS is ready */ 699 } 700 701 /* make sure DBs are up-to-date */ 702 updateDataBases(); 703 704 /* 705 * Only task here is map incoming DbName - specified in the CDSA 706 * spec - to a filename we actually use (which is a path to either 707 * a system MDS DB file or a per-user MDS DB file). 708 */ 709 if(DbName == NULL) { 710 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME); 711 } 712 const char *dbName; 713 if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) { 714 dbName = MDS_OBJECT_DB_NAME; 715 } 716 else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) { 717 dbName = MDS_DIRECT_DB_NAME; 718 } 719 else { 720 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME); 721 } 722 char fullPath[MAXPATHLEN]; 723 dbFullPath(dbName, fullPath); 724 MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName); 725 DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred, 726 OpenParameters, DbHandle); 727 } 728 729 CSSM_HANDLE MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle, 730 const CssmQuery *Query, 731 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes, 732 CssmData *Data, 733 CSSM_DB_UNIQUE_RECORD_PTR &UniqueId) 734 { 735 updateDataBases(); 736 return DatabaseSession::DataGetFirst(DBHandle, Query, Attributes, Data, UniqueId); 737 } 738 739 740 void 741 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList) 742 { 743 outNameList = new CSSM_NAME_LIST[1]; 744 outNameList->NumStrings = 2; 745 outNameList->String = new char*[2]; 746 outNameList->String[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME); 747 outNameList->String[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME); 748 } 749 750 void 751 MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList) 752 { 753 delete [] inNameList.String[0]; 754 delete [] inNameList.String[1]; 755 delete [] inNameList.String; 756 } 757 758 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle, 759 char **DbName) 760 { 761 printf("GetDbNameFromHandle: code on demand\n"); 762 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 763 } 764 765 // 766 // Attempt to obtain an exclusive lock over the the MDS databases. The 767 // parameter is the maximum amount of time, in milliseconds, to spend 768 // trying to obtain the lock. A value of zero means to return failure 769 // right away if the lock cannot be obtained. 770 // 771 bool 772 MDSSession::LockHelper::obtainLock( 773 const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH 774 int timeout) // default 0 775 { 776 mFD = -1; 777 for(;;) { 778 secinfo("mdslock", "obtainLock: calling open(%s)", lockFile); 779 mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644); 780 if(mFD == -1) { 781 int err = errno; 782 secinfo("mdslock", "obtainLock: open error %d", errno); 783 if(err == EINTR) { 784 /* got a signal, go again */ 785 continue; 786 } 787 else { 788 /* theoretically should never happen */ 789 return false; 790 } 791 } 792 else { 793 secinfo("mdslock", "obtainLock: success"); 794 return true; 795 } 796 } 797 } 798 799 // 800 // Release the exclusive lock over the MDS databases. If this session 801 // does not hold the lock, this method does nothing. 802 // 803 804 MDSSession::LockHelper::~LockHelper() 805 { 806 secinfo("mdslock", "releaseLock"); 807 if (mFD == -1) 808 { 809 return; 810 } 811 812 flock(mFD, LOCK_UN); 813 close(mFD); 814 mFD = -1; 815 } 816 817 /* given DB file name, fill in fully specified path */ 818 void MDSSession::dbFullPath( 819 const char *dbName, 820 char fullPath[MAXPATHLEN+1]) 821 { 822 mModule.getDbPath(fullPath); 823 assert(fullPath[0] != '\0'); 824 strcat(fullPath, "/"); 825 strcat(fullPath, dbName); 826 } 827 828 /* 829 * See if any per-user bundles exist in specified directory. Returns true if so. 830 * First the check for one entry.... 831 */ 832 static bool isBundle( 833 const struct dirent *dp) 834 { 835 if(dp == NULL) { 836 return false; 837 } 838 /* NFS directories show up as DT_UNKNOWN */ 839 switch(dp->d_type) { 840 case DT_UNKNOWN: 841 case DT_DIR: 842 break; 843 default: 844 return false; 845 } 846 int suffixLen = strlen(MDS_BUNDLE_EXTEN); 847 size_t len = strlen(dp->d_name); 848 849 return (len >= suffixLen) && 850 !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN); 851 } 852 853 /* now the full directory search */ 854 static bool checkUserBundles( 855 const char *bundlePath) 856 { 857 MSDebug("searching for user bundles in %s", bundlePath); 858 DIR *dir = opendir(bundlePath); 859 if (dir == NULL) { 860 return false; 861 } 862 struct dirent *dp; 863 bool rtn = false; 864 while ((dp = readdir(dir)) != NULL) { 865 if(isBundle(dp)) { 866 /* any other checking to do? */ 867 rtn = true; 868 break; 869 } 870 } 871 closedir(dir); 872 MSDebug("...%s bundle(s) found", rtn ? "" : "No"); 873 return rtn; 874 } 875 876 #define COPY_BUF_SIZE 65536 877 878 /* 879 * Single file copy with locking. 880 * Ensures that the source is a regular file with specified owner. 881 * Caller specifies mode of destination file. 882 * Throws a CssmError if the source file doesn't meet spec; throws a 883 * UnixError on any other error (which is generally recoverable by 884 * having the user MDS session use the system DB files). 885 */ 886 static void safeCopyFile( 887 const char *fromPath, 888 uid_t fromUid, 889 const char *toPath, 890 mode_t toMode) 891 { 892 int error = 0; 893 bool haveLock = false; 894 int destFd = 0; 895 int srcFd = 0; 896 struct stat sb; 897 char tmpToPath[MAXPATHLEN+1]; 898 899 MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath); 900 901 if(!doesFileExist(fromPath, fromUid, false, sb)) { 902 MSDebug("safeCopyFile: bad system DB file %s", fromPath); 903 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 904 } 905 906 /* create temp destination */ 907 snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath); 908 destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode); 909 if(destFd < 0) { 910 error = errno; 911 MSDebug("Error %d opening user DB file %s\n", error, tmpToPath); 912 UnixError::throwMeNoLogging(error); 913 } 914 915 struct flock fl; 916 try { 917 /* don't get tripped up by umask */ 918 if(fchmod(destFd, toMode)) { 919 error = errno; 920 MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath); 921 UnixError::throwMeNoLogging(error); 922 } 923 924 /* open source for reading */ 925 srcFd = open(fromPath, O_RDONLY, 0); 926 if(srcFd < 0) { 927 error = errno; 928 MSDebug("Error %d opening system DB file %s\n", error, fromPath); 929 UnixError::throwMeNoLogging(error); 930 } 931 932 /* acquire the same kind of lock AtomicFile uses */ 933 fl.l_start = 0; 934 fl.l_len = 1; 935 fl.l_pid = getpid(); 936 fl.l_type = F_RDLCK; // AtomicFile gets F_WRLCK 937 fl.l_whence = SEEK_SET; 938 939 // Keep trying to obtain the lock if we get interupted. 940 for (;;) { 941 if (::fcntl(srcFd, F_SETLKW, &fl) == -1) { 942 error = errno; 943 if (error == EINTR) { 944 error = 0; 945 continue; 946 } 947 MSDebug("Error %d locking system DB file %s\n", error, fromPath); 948 UnixError::throwMeNoLogging(error); 949 } 950 else { 951 break; 952 //haveLock = true; 953 } 954 } 955 956 /* copy */ 957 char *buf = new char[COPY_BUF_SIZE]; 958 while(1) { 959 ssize_t bytesRead; 960 961 do { 962 bytesRead = read(srcFd, buf, COPY_BUF_SIZE); 963 } while (bytesRead < 0 && errno == EINTR); 964 965 if(bytesRead == 0) { 966 break; 967 } 968 if(bytesRead < 0) { 969 delete [] buf; 970 error = errno; 971 MSDebug("Error %d reading system DB file %s\n", error, fromPath); 972 UnixError::throwMeNoLogging(error); 973 } 974 975 ssize_t bytesWritten; 976 977 do { 978 bytesWritten = write(destFd, buf, bytesRead); 979 } while (bytesWritten < 0 && errno == EINTR); 980 981 if(bytesWritten < 0) { 982 delete [] buf; 983 error = errno; 984 MSDebug("Error %d writing user DB file %s\n", error, tmpToPath); 985 UnixError::throwMeNoLogging(error); 986 } 987 } 988 delete [] buf; 989 } 990 catch(...) { 991 /* error is nonzero, we'll re-throw below...still have some cleanup */ 992 } 993 994 /* unlock source and close both */ 995 if(haveLock) { 996 fl.l_type = F_UNLCK; 997 if (::fcntl(srcFd, F_SETLK, &fl) == -1) { 998 MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath); 999 } 1000 } 1001 MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath); 1002 if(srcFd) { 1003 close(srcFd); 1004 } 1005 if(destFd) { 1006 close(destFd); 1007 } 1008 if(error == 0) { 1009 /* commit temp file */ 1010 if(::rename(tmpToPath, toPath)) { 1011 error = errno; 1012 MSDebug("Error %d committing %s\n", error, toPath); 1013 } 1014 } 1015 if(error) { 1016 UnixError::throwMeNoLogging(error); 1017 } 1018 } 1019 1020 /* 1021 * Copy system DB files to specified user dir. Caller holds user DB lock. 1022 * Throws a UnixError on error. 1023 */ 1024 static void copySystemDbs( 1025 const char *userDbFileDir) 1026 { 1027 char toPath[MAXPATHLEN+1]; 1028 1029 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); 1030 safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE); 1031 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); 1032 safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE); 1033 } 1034 1035 /* 1036 * Ensure current DB files exist and are up-to-date. 1037 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any 1038 * other public functions which access a DB from scratch. 1039 */ 1040 void MDSSession::updateDataBases() 1041 { 1042 RecursionBlock::Once once(mUpdating); 1043 if (once()) 1044 return; // already updating; don't recurse 1045 1046 uid_t ourUid = geteuid(); 1047 bool isRoot = (ourUid == 0); 1048 1049 /* if we scanned recently, we're done */ 1050 double delta = mModule.timeSinceLastScan(); 1051 if(delta < (double)MDS_SCAN_INTERVAL) { 1052 return; 1053 } 1054 1055 /* 1056 * If we're root, the first thing we do is to ensure that system DBs are present. 1057 * Note that this is a necessary artifact of the problem behind Radar 3800811. 1058 * When that is fixed, install() should ONLY be called from the public MDS_Install() 1059 * routine. 1060 * Anyway, if we *do* have to install here, we're done. 1061 */ 1062 if(isRoot && !systemDatabasesPresent(false)) { 1063 install(); 1064 mModule.setDbPath(MDS_SYSTEM_DB_DIR); 1065 mModule.lastScanIsNow(); 1066 return; 1067 } 1068 1069 /* 1070 * Obtain various per-user paths. Root is a special case but follows most 1071 * of the same logic from here on. 1072 */ 1073 std::string userDBFileDir = GetMDSDBDir(); 1074 std::string userObjDBFilePath = GetMDSObjectDBPath(); 1075 std::string userDirectDBFilePath = GetMDSDirectDBPath(); 1076 char userBundlePath[MAXPATHLEN+1]; 1077 std::string userDbLockPath = GetMDSDBLockPath(); 1078 1079 /* this means "no user bundles" */ 1080 userBundlePath[0] = '\0'; 1081 if(!isRoot) { 1082 char *userHome = getenv("HOME"); 1083 if((userHome == NULL) || 1084 (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) { 1085 /* Can't check for user bundles */ 1086 MSDebug("missing or invalid HOME; skipping user bundle check"); 1087 } 1088 /* TBD: any other checking of userHome? */ 1089 else { 1090 snprintf(userBundlePath, sizeof(userBundlePath), 1091 "%s/%s", userHome, MDS_USER_BUNDLE); 1092 } 1093 } 1094 1095 /* 1096 * Create the per-user directory...that's where the lock we'll be using lives. 1097 */ 1098 if(!isRoot) { 1099 if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) { 1100 /* 1101 * We'll just have to limp along using the read-only system DBs. 1102 * Note that this protects (somewhat) against the DoS attack in 1103 * Radar 3801292. The only problem is that this user won't be able 1104 * to use per-user bundles. 1105 */ 1106 MSDebug("Error creating user DBs; using system DBs"); 1107 mModule.setDbPath(MDS_SYSTEM_DB_DIR); 1108 return; 1109 } 1110 } 1111 1112 /* always release userLockFd no matter what happens */ 1113 LockHelper lh; 1114 1115 if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) { 1116 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 1117 } 1118 try { 1119 if(!isRoot) { 1120 try { 1121 /* 1122 * We copy the system DBs to the per-user DBs in two cases: 1123 * -- user DBs don't exist, or 1124 * -- system DBs have changed since the the last update to the user DBs. 1125 * This happens on smart card insertion and removal. 1126 */ 1127 bool doCopySystem = false; 1128 struct stat userObjStat, userDirectStat; 1129 if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true, 1130 userObjStat, userDirectStat)) { 1131 doCopySystem = true; 1132 } 1133 else { 1134 /* compare the two mdsDirectory.db files */ 1135 MSIoDbg("stat %s, %s in updateDataBases", 1136 MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str()); 1137 struct stat sysStat; 1138 if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) { 1139 doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) || 1140 ((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) && 1141 (sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec)); 1142 if(doCopySystem) { 1143 MSDebug("user DB files obsolete at %s", userDBFileDir.c_str()); 1144 } 1145 } 1146 } 1147 if(doCopySystem) { 1148 /* copy system DBs to user DBs */ 1149 MSDebug("copying system DBs to user at %s", userDBFileDir.c_str()); 1150 copySystemDbs(userDBFileDir.c_str()); 1151 } 1152 else { 1153 MSDebug("Using existing user DBs at %s", userDBFileDir.c_str()); 1154 } 1155 } 1156 catch(const CssmError &cerror) { 1157 /* 1158 * Bad system DB file detected. Fatal. 1159 */ 1160 throw; 1161 } 1162 catch(...) { 1163 /* 1164 * Error on delete or create user DBs; fall back on system DBs. 1165 */ 1166 MSDebug("doFilesExist(purge) error; using system DBs"); 1167 mModule.setDbPath(MDS_SYSTEM_DB_DIR); 1168 return; 1169 } 1170 } 1171 else { 1172 MSDebug("Using system DBs only"); 1173 } 1174 1175 /* 1176 * Update per-user DBs from both bundle sources (System bundles, user bundles) 1177 * as appropriate. 1178 */ 1179 DbFilesInfo dbFiles(*this, userDBFileDir.c_str()); 1180 dbFiles.removeOutdatedPlugins(); 1181 dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH); 1182 if(userBundlePath[0]) { 1183 /* skip for invalid or missing $HOME... */ 1184 if(checkUserBundles(userBundlePath)) { 1185 dbFiles.updateForBundleDir(userBundlePath); 1186 } 1187 } 1188 mModule.setDbPath(userDBFileDir.c_str()); 1189 } /* main block protected by mLockFd */ 1190 catch(...) { 1191 throw; 1192 } 1193 mModule.lastScanIsNow(); 1194 } 1195 1196 /* 1197 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB. 1198 */ 1199 void MDSSession::removeRecordsForGuid( 1200 const char *guid, 1201 CSSM_DB_HANDLE dbHand) 1202 { 1203 // tell the DB to flush its intermediate data to disk 1204 PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL); 1205 CssmClient::Query query = Attribute("ModuleID") == guid; 1206 clearRecords(dbHand, query.cssmQuery()); 1207 } 1208 1209 1210 void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query) 1211 { 1212 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 1213 CSSM_HANDLE resultHand = DataGetFirst(dbHand, 1214 &query, 1215 NULL, 1216 NULL, // No data 1217 record); 1218 if (resultHand == CSSM_INVALID_HANDLE) 1219 return; // no matches 1220 try { 1221 do { 1222 DataDelete(dbHand, *record); 1223 FreeUniqueRecord(dbHand, *record); 1224 record = NULL; 1225 } while (DataGetNext(dbHand, 1226 resultHand, 1227 NULL, 1228 NULL, 1229 record)); 1230 } catch (...) { 1231 if (record) 1232 FreeUniqueRecord(dbHand, *record); 1233 DataAbortQuery(dbHand, resultHand); 1234 } 1235 } 1236 1237 1238 /* 1239 * Determine if system databases are present. 1240 * If the purge argument is true, we'll ensure that either both or neither 1241 * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH. 1242 */ 1243 bool MDSSession::systemDatabasesPresent(bool purge) 1244 { 1245 bool rtn = false; 1246 1247 try { 1248 /* 1249 * This can throw on a failed attempt to delete sole existing file.... 1250 * But if that happens while we're root, our goose is fully cooked. 1251 */ 1252 struct stat objDbSb, directDbSb; 1253 if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH, 1254 MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) { 1255 rtn = true; 1256 } 1257 } 1258 catch(...) { 1259 1260 } 1261 return rtn; 1262 } 1263 1264 /* 1265 * Given a DB name (which is used as an absolute path) and an array of 1266 * RelationInfos, create a DB. 1267 */ 1268 void 1269 MDSSession::createSystemDatabase( 1270 const char *dbName, 1271 const RelationInfo *relationInfo, 1272 unsigned numRelations, 1273 CSSM_BOOL autoCommit, 1274 mode_t mode, 1275 CSSM_DB_HANDLE &dbHand) // RETURNED 1276 { 1277 CSSM_DBINFO dbInfo; 1278 CSSM_DBINFO_PTR dbInfoP = &dbInfo; 1279 1280 memset(dbInfoP, 0, sizeof(CSSM_DBINFO)); 1281 dbInfoP->NumberOfRecordTypes = numRelations; 1282 dbInfoP->IsLocal = CSSM_TRUE; // TBD - what does this mean? 1283 dbInfoP->AccessPath = NULL; // TBD 1284 1285 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex 1286 * info arrays */ 1287 unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations; 1288 dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size); 1289 memset(dbInfoP->DefaultParsingModules, 0, size); 1290 size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations; 1291 dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size); 1292 memset(dbInfoP->RecordAttributeNames, 0, size); 1293 size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations; 1294 dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size); 1295 memset(dbInfoP->RecordIndexes, 0, size); 1296 1297 /* cook up attribute and index info for each relation */ 1298 unsigned relation; 1299 for(relation=0; relation<numRelations; relation++) { 1300 const struct RelationInfo *relp = &relationInfo[relation]; // source 1301 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo = 1302 &dbInfoP->RecordAttributeNames[relation]; // dest 1 1303 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo = 1304 &dbInfoP->RecordIndexes[relation]; // dest 2 1305 1306 attrInfo->DataRecordType = relp->DataRecordType; 1307 attrInfo->NumberOfAttributes = relp->NumberOfAttributes; 1308 attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo; 1309 1310 indexInfo->DataRecordType = relp->DataRecordType; 1311 indexInfo->NumberOfIndexes = relp->NumberOfIndexes; 1312 indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo; 1313 } 1314 1315 /* set autocommit and mode */ 1316 CSSM_APPLEDL_OPEN_PARAMETERS openParams; 1317 memset(&openParams, 0, sizeof(openParams)); 1318 openParams.length = sizeof(openParams); 1319 openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION; 1320 openParams.autoCommit = autoCommit; 1321 openParams.mask = kCSSM_APPLEDL_MASK_MODE; 1322 openParams.mode = mode; 1323 1324 try { 1325 DbCreate(dbName, 1326 NULL, // DbLocation 1327 *dbInfoP, 1328 CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE, 1329 NULL, // CredAndAclEntry 1330 &openParams, 1331 dbHand); 1332 } 1333 catch(...) { 1334 MSDebug("Error on DbCreate"); 1335 free(dbInfoP->DefaultParsingModules); 1336 free(dbInfoP->RecordAttributeNames); 1337 free(dbInfoP->RecordIndexes); 1338 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR); 1339 } 1340 free(dbInfoP->DefaultParsingModules); 1341 free(dbInfoP->RecordAttributeNames); 1342 free(dbInfoP->RecordIndexes); 1343 1344 } 1345 1346 /* 1347 * Create system databases from scratch if they do not already exist. 1348 * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to 1349 * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH). 1350 * Returns true if we actually built the files, false if they already 1351 * existed. 1352 */ 1353 bool MDSSession::createSystemDatabases( 1354 CSSM_BOOL autoCommit, 1355 mode_t mode) 1356 { 1357 CSSM_DB_HANDLE objectDbHand = 0; 1358 CSSM_DB_HANDLE directoryDbHand = 0; 1359 1360 assert(geteuid() == (uid_t)0); 1361 if(systemDatabasesPresent(true)) { 1362 /* both databases exist as regular files with correct owner - we're done */ 1363 MSDebug("system DBs already exist"); 1364 return false; 1365 } 1366 1367 /* create two DBs - any exception here results in deleting both of them */ 1368 MSDebug("Creating MDS DBs"); 1369 try { 1370 createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1, 1371 autoCommit, mode, objectDbHand); 1372 MSIoDbg("close objectDbHand in createSystemDatabases"); 1373 DbClose(objectDbHand); 1374 objectDbHand = 0; 1375 createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations, 1376 autoCommit, mode, directoryDbHand); 1377 MSIoDbg("close directoryDbHand in createSystemDatabases"); 1378 DbClose(directoryDbHand); 1379 directoryDbHand = 0; 1380 } 1381 catch (...) { 1382 MSDebug("Error creating MDS DBs - deleting both DB files"); 1383 unlink(MDS_OBJECT_DB_PATH); 1384 unlink(MDS_DIRECT_DB_PATH); 1385 throw; 1386 } 1387 return true; 1388 } 1389 1390 /* 1391 * DbFilesInfo helper class 1392 */ 1393 1394 /* Note both DB files MUST exist at construction time */ 1395 MDSSession::DbFilesInfo::DbFilesInfo( 1396 MDSSession &session, 1397 const char *dbPath) : 1398 mSession(session), 1399 mObjDbHand(0), 1400 mDirectDbHand(0), 1401 mLaterTimestamp(0) 1402 { 1403 assert(strlen(dbPath) < MAXPATHLEN); 1404 strcpy(mDbPath, dbPath); 1405 1406 /* stat the two DB files, snag the later timestamp */ 1407 char path[MAXPATHLEN]; 1408 sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME); 1409 struct stat sb; 1410 MSIoDbg("stat %s in DbFilesInfo()", path); 1411 int rtn = ::stat(path, &sb); 1412 if(rtn) { 1413 int error = errno; 1414 MSDebug("Error %d statting DB file %s", error, path); 1415 UnixError::throwMeNoLogging(error); 1416 } 1417 mLaterTimestamp = sb.st_mtimespec.tv_sec; 1418 sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME); 1419 MSIoDbg("stat %s in DbFilesInfo()", path); 1420 rtn = ::stat(path, &sb); 1421 if(rtn) { 1422 int error = errno; 1423 MSDebug("Error %d statting DB file %s", error, path); 1424 UnixError::throwMeNoLogging(error); 1425 } 1426 if(sb.st_mtimespec.tv_sec > mLaterTimestamp) { 1427 mLaterTimestamp = sb.st_mtimespec.tv_sec; 1428 } 1429 } 1430 1431 MDSSession::DbFilesInfo::~DbFilesInfo() 1432 { 1433 if(mObjDbHand != 0) { 1434 /* autocommit on, henceforth */ 1435 mSession.PassThrough(mObjDbHand, 1436 CSSM_APPLEFILEDL_COMMIT, NULL, NULL); 1437 MSIoDbg("close objectDbHand in ~DbFilesInfo()"); 1438 mSession.DbClose(mObjDbHand); 1439 mObjDbHand = 0; 1440 } 1441 if(mDirectDbHand != 0) { 1442 mSession.PassThrough(mDirectDbHand, 1443 CSSM_APPLEFILEDL_COMMIT, NULL, NULL); 1444 MSIoDbg("close mDirectDbHand in ~DbFilesInfo()"); 1445 mSession.DbClose(mDirectDbHand); 1446 mDirectDbHand = 0; 1447 } 1448 } 1449 1450 /* lazy evaluation of both DB handles�*/ 1451 CSSM_DB_HANDLE MDSSession::DbFilesInfo::objDbHand() 1452 { 1453 if(mObjDbHand != 0) { 1454 return mObjDbHand; 1455 } 1456 char fullPath[MAXPATHLEN + 1]; 1457 sprintf(fullPath, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME); 1458 MSIoDbg("open %s in objDbHand()", fullPath); 1459 mObjDbHand = mSession.dbOpen(fullPath, true); // batch mode 1460 return mObjDbHand; 1461 } 1462 1463 CSSM_DB_HANDLE MDSSession::DbFilesInfo::directDbHand() 1464 { 1465 if(mDirectDbHand != 0) { 1466 return mDirectDbHand; 1467 } 1468 char fullPath[MAXPATHLEN + 1]; 1469 sprintf(fullPath, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME); 1470 MSIoDbg("open %s in directDbHand()", fullPath); 1471 mDirectDbHand = mSession.dbOpen(fullPath, true); // batch mode 1472 return mDirectDbHand; 1473 } 1474 1475 /* 1476 * Update the info for Security.framework and the system bundles. 1477 */ 1478 void MDSSession::DbFilesInfo::updateSystemDbInfo( 1479 const char *systemPath, // e.g., /System/Library/Frameworks 1480 const char *bundlePath) // e.g., /System/Library/Security 1481 { 1482 /* 1483 * Security.framework - CSSM and built-in modules - only for initial population of 1484 * system DB files. 1485 */ 1486 if (systemPath) { 1487 string path; 1488 if (CFRef<CFBundleRef> me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"))) 1489 if (CFRef<CFURLRef> url = CFBundleCopyBundleURL(me)) 1490 if (CFRef<CFStringRef> cfpath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)) 1491 path = cfString(cfpath); // path to my bundle 1492 1493 if (path.empty()) // use system default 1494 path = string(systemPath) + "/" MDS_SYSTEM_FRAME; 1495 updateForBundle(path.c_str()); 1496 } 1497 1498 /* Standard loadable bundles */ 1499 updateForBundleDir(bundlePath); 1500 } 1501 1502 1503 MDSSession::DbFilesInfo::TbdRecord::TbdRecord( 1504 const CSSM_DATA &guid) 1505 { 1506 if (guid.Length != 0 && guid.Length < MAX_GUID_LEN) { 1507 memmove(mGuid, guid.Data, guid.Length); 1508 // mGuid is treated as a string elsewhere; terminate 1509 mGuid[guid.Length] = '\0'; 1510 } 1511 } 1512 1513 /* 1514 * Test if plugin specified by pluginPath needs to be deleted from DBs. 1515 * If so, add to tbdVector. 1516 */ 1517 void MDSSession::DbFilesInfo::checkOutdatedPlugin( 1518 const CSSM_DATA &pathValue, 1519 const CSSM_DATA &guidValue, 1520 TbdVector &tbdVector) 1521 { 1522 /* stat the specified plugin */ 1523 struct stat sb; 1524 bool obsolete = false; 1525 string path = CssmData::overlay(pathValue).toString(); 1526 if (!path.empty() && path[0] == '*') { 1527 /* builtin pseudo-path; never obsolete this */ 1528 return; 1529 } 1530 MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str()); 1531 int rtn = ::stat(path.c_str(), &sb); 1532 if(rtn) { 1533 /* not there or inaccessible; delete */ 1534 obsolete = true; 1535 } 1536 else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) { 1537 /* timestamp of plugin's main directory later than that of DBs */ 1538 obsolete = true; 1539 } 1540 if(obsolete) { 1541 if (guidValue.Length != 0 && guidValue.Length < MAX_GUID_LEN) { 1542 TbdRecord *tbdRecord = new TbdRecord(guidValue); 1543 tbdVector.push_back(tbdRecord); 1544 MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str()); 1545 } else { 1546 MSDebug("checkOutdatedPlugin: flagging %s obsolete, but guid length is invalid (%zu)", path.c_str(), guidValue.Length); 1547 } 1548 } 1549 } 1550 1551 /* 1552 * Examine dbFiles.objDbHand; remove all fields associated with any bundle 1553 * i.e., with any path) which are either not present on disk, or which 1554 * have changed since dbFiles.laterTimestamp(). 1555 */ 1556 void MDSSession::DbFilesInfo::removeOutdatedPlugins() 1557 { 1558 CSSM_QUERY query; 1559 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 1560 CSSM_HANDLE resultHand; 1561 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs; 1562 CSSM_DB_ATTRIBUTE_DATA theAttrs[2]; 1563 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo; 1564 TbdVector tbdRecords; 1565 1566 __block bool countPlugins = false; 1567 static dispatch_once_t onceToken; 1568 dispatch_once(&onceToken, ^{ 1569 // We will set this to true exactly once 1570 countPlugins = true; 1571 }); 1572 1573 // The clang analyzer does not understand that DataGetNext fills in theAttrs with malloc'ed pointers, 1574 // through a chain of structs, pointers, and confusing names. Then, it has false-positive use-after-free 1575 // warnings. So, disable the analyzer for this function. 1576 #ifndef __clang_analyzer__ 1577 1578 /* 1579 * First, scan object directory. All we need are the path and GUID attributes. 1580 */ 1581 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE; 1582 recordAttrs.SemanticInformation = 0; 1583 recordAttrs.NumberOfAttributes = 2; 1584 recordAttrs.AttributeData = theAttrs; 1585 1586 attrInfo = &theAttrs[0].Info; 1587 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 1588 attrInfo->Label.AttributeName = (char*) "ModuleID"; 1589 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; 1590 theAttrs[0].NumberOfValues = 0; 1591 theAttrs[0].Value = NULL; 1592 attrInfo = &theAttrs[1].Info; 1593 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 1594 attrInfo->Label.AttributeName = (char*) "Path"; 1595 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; 1596 theAttrs[1].NumberOfValues = 0; 1597 theAttrs[1].Value = NULL; 1598 1599 /* just search by recordType, no predicates */ 1600 query.RecordType = MDS_OBJECT_RECORDTYPE; 1601 query.Conjunctive = CSSM_DB_NONE; 1602 query.NumSelectionPredicates = 0; 1603 query.SelectionPredicate = NULL; 1604 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 1605 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 1606 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used? 1607 1608 CssmQuery perryQuery(query); 1609 try { 1610 resultHand = mSession.DataGetFirst(objDbHand(), 1611 &perryQuery, 1612 &recordAttrs, 1613 NULL, // No data 1614 record); 1615 } 1616 catch(...) { 1617 MSDebug("removeOutdatedPlugins: DataGetFirst threw"); 1618 return; // ??? 1619 } 1620 if(record) { 1621 mSession.FreeUniqueRecord(mObjDbHand, *record); 1622 } 1623 if(resultHand) { 1624 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) { 1625 if(countPlugins) { 1626 string path = CssmData::overlay(*theAttrs[1].Value).toString(); 1627 string guid = CssmData::overlay(*theAttrs[0].Value).toString(); 1628 1629 countLegacyMDSPlugin(path.c_str(), guid.c_str()); 1630 } 1631 1632 checkOutdatedPlugin(*theAttrs[1].Value, *theAttrs[0].Value, 1633 tbdRecords); 1634 } 1635 else { 1636 MSDebug("removeOutdatedPlugins: incomplete record found (1)!"); 1637 } 1638 for(unsigned dex=0; dex<2; dex++) { 1639 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex]; 1640 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) { 1641 if(attr->Value[attrDex].Data) { 1642 mSession.free(attr->Value[attrDex].Data); 1643 } 1644 } 1645 if(attr->Value) { 1646 mSession.free(attr->Value); 1647 } 1648 } 1649 } 1650 else { 1651 /* empty Object DB - we're done */ 1652 MSDebug("removeOutdatedPlugins: empty object DB"); 1653 return; 1654 } 1655 1656 /* now the rest of the object DB records */ 1657 for(;;) { 1658 bool brtn = mSession.DataGetNext(objDbHand(), 1659 resultHand, 1660 &recordAttrs, 1661 NULL, 1662 record); 1663 if(!brtn) { 1664 /* end of data */ 1665 break; 1666 } 1667 if(record) { 1668 mSession.FreeUniqueRecord(mObjDbHand, *record); 1669 } 1670 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) { 1671 if(countPlugins) { 1672 string path = CssmData::overlay(*theAttrs[1].Value).toString(); 1673 string guid = CssmData::overlay(*theAttrs[0].Value).toString(); 1674 1675 countLegacyMDSPlugin(path.c_str(), guid.c_str()); 1676 } 1677 1678 checkOutdatedPlugin(*theAttrs[1].Value, 1679 *theAttrs[0].Value, 1680 tbdRecords); 1681 } 1682 else { 1683 MSDebug("removeOutdatedPlugins: incomplete record found (2)!"); 1684 } 1685 for(unsigned dex=0; dex<2; dex++) { 1686 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex]; 1687 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) { 1688 if(attr->Value[attrDex].Data) { 1689 mSession.free(attr->Value[attrDex].Data); 1690 } 1691 } 1692 if(attr->Value) { 1693 mSession.free(attr->Value); 1694 } 1695 } 1696 } 1697 /* no DataAbortQuery needed; we scanned until completion */ 1698 1699 /* 1700 * We have a vector of plugins to be deleted. Remove all records from both 1701 * DBs associated with the plugins, as specified by guid. 1702 */ 1703 size_t numRecords = tbdRecords.size(); 1704 for(size_t i=0; i<numRecords; i++) { 1705 TbdRecord *tbdRecord = tbdRecords[i]; 1706 mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand()); 1707 mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand()); 1708 } 1709 for(size_t i=0; i<numRecords; i++) { 1710 delete tbdRecords[i]; 1711 } 1712 #endif 1713 1714 // don't count plugins again for the lifetime of this program 1715 countPlugins = false; 1716 } 1717 1718 1719 /* 1720 * Update DBs for all bundles in specified directory. 1721 */ 1722 void MDSSession::DbFilesInfo::updateForBundleDir( 1723 const char *bundleDirPath) 1724 { 1725 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is 1726 * much too heavyweight */ 1727 MSDebug("...updating DBs for dir %s", bundleDirPath); 1728 DIR *dir = opendir(bundleDirPath); 1729 if (dir == NULL) { 1730 MSDebug("updateForBundleDir: error %d opening %s", errno, bundleDirPath); 1731 return; 1732 } 1733 struct dirent *dp; 1734 char fullPath[MAXPATHLEN]; 1735 while ((dp = readdir(dir)) != NULL) { 1736 if(isBundle(dp)) { 1737 sprintf(fullPath, "%s/%s", bundleDirPath, dp->d_name); 1738 updateForBundle(fullPath); 1739 } 1740 } 1741 closedir(dir); 1742 } 1743 1744 /* 1745 * lookup by path - just returns true if there is a record assoociated with the path 1746 * in mObjDbHand. 1747 */ 1748 bool MDSSession::DbFilesInfo::lookupForPath( 1749 const char *path) 1750 { 1751 CSSM_QUERY query; 1752 CSSM_DB_UNIQUE_RECORD_PTR record = NULL; 1753 CSSM_HANDLE resultHand = 0; 1754 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs; 1755 CSSM_DB_ATTRIBUTE_DATA theAttr; 1756 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo = &theAttr.Info; 1757 CSSM_SELECTION_PREDICATE predicate; 1758 CSSM_DATA predData; 1759 1760 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE; 1761 recordAttrs.SemanticInformation = 0; 1762 recordAttrs.NumberOfAttributes = 1; 1763 recordAttrs.AttributeData = &theAttr; 1764 1765 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 1766 attrInfo->Label.AttributeName = (char*) "Path"; 1767 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; 1768 1769 theAttr.NumberOfValues = 0; 1770 theAttr.Value = NULL; 1771 1772 predicate.DbOperator = CSSM_DB_EQUAL; 1773 predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING; 1774 predicate.Attribute.Info.Label.AttributeName = (char*) "Path"; 1775 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING; 1776 predData.Data = (uint8 *)path; 1777 predData.Length = strlen(path); 1778 predicate.Attribute.Value = &predData; 1779 predicate.Attribute.NumberOfValues = 1; 1780 1781 query.RecordType = MDS_OBJECT_RECORDTYPE; 1782 query.Conjunctive = CSSM_DB_NONE; 1783 query.NumSelectionPredicates = 1; 1784 query.SelectionPredicate = &predicate; 1785 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful? 1786 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful? 1787 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used? 1788 1789 bool ourRtn = true; 1790 try { 1791 CssmQuery perryQuery(query); 1792 resultHand = mSession.DataGetFirst(objDbHand(), 1793 &perryQuery, 1794 &recordAttrs, 1795 NULL, // No data 1796 record); 1797 } 1798 catch (...) { 1799 ourRtn = false; 1800 } 1801 if(record) { 1802 mSession.FreeUniqueRecord(mObjDbHand, *record); 1803 } 1804 else { 1805 ourRtn = false; 1806 } 1807 if(resultHand && ourRtn) { 1808 /* more resulting pending; terminate the search */ 1809 try { 1810 mSession.DataAbortQuery(mObjDbHand, resultHand); 1811 } 1812 catch(...) { 1813 MSDebug("exception on DataAbortQuery in lookupForPath"); 1814 } 1815 } 1816 for(unsigned dex=0; dex<theAttr.NumberOfValues; dex++) { 1817 if(theAttr.Value[dex].Data) { 1818 mSession.free(theAttr.Value[dex].Data); 1819 } 1820 } 1821 mSession.free(theAttr.Value); 1822 return ourRtn; 1823 } 1824 1825 /* update entry for one bundle, which is known to exist */ 1826 void MDSSession::DbFilesInfo::updateForBundle( 1827 const char *bundlePath) 1828 { 1829 MSDebug("...updating DBs for bundle %s", bundlePath); 1830 1831 /* Quick lookup - do we have ANY entry for a bundle with this path? */ 1832 if(lookupForPath(bundlePath)) { 1833 /* Yep, we're done */ 1834 return; 1835 } 1836 MDSAttrParser parser(bundlePath, 1837 mSession, 1838 objDbHand(), 1839 directDbHand()); 1840 try { 1841 parser.parseAttrs(); 1842 } 1843 catch (const CssmError &err) { 1844 // a corrupt MDS info file invalidates the entire plugin 1845 const char *guid = parser.guid(); 1846 if (guid) { 1847 mSession.removeRecordsForGuid(guid, objDbHand()); 1848 mSession.removeRecordsForGuid(guid, directDbHand()); 1849 } 1850 } 1851 } 1852 1853 1854 // 1855 // Private API: add MDS records from contents of file 1856 // These files are typically written by securityd and handed to us in this call. 1857 // 1858 void MDSSession::installFile(const MDS_InstallDefaults *defaults, 1859 const char *inBundlePath, const char *subdir, const char *file) 1860 { 1861 string bundlePath = inBundlePath ? inBundlePath : cfString(CFBundleGetMainBundle()); 1862 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); 1863 MDSAttrParser parser(bundlePath.c_str(), 1864 *this, 1865 dbFiles.objDbHand(), 1866 dbFiles.directDbHand()); 1867 parser.setDefaults(defaults); 1868 1869 try { 1870 if (file == NULL) // parse a directory 1871 if (subdir) // a particular directory 1872 parser.parseAttrs(CFTempString(subdir)); 1873 else // all resources in bundle 1874 parser.parseAttrs(NULL); 1875 else // parse just one file 1876 parser.parseFile(CFRef<CFURLRef>(makeCFURL(file)), CFTempString(subdir)); 1877 } 1878 catch (const CssmError &err) { 1879 const char *guid = parser.guid(); 1880 if (guid) { 1881 removeRecordsForGuid(guid, dbFiles.objDbHand()); 1882 removeRecordsForGuid(guid, dbFiles.directDbHand()); 1883 } 1884 } 1885 } 1886 1887 1888 // 1889 // Private API: Remove all records for a guid/subservice 1890 // 1891 // Note: Multicursors searching for SSID fail because not all records in the 1892 // database have this attribute. So we have to explicitly run through all tables 1893 // that do. 1894 // 1895 void MDSSession::removeSubservice(const char *guid, uint32 ssid) 1896 { 1897 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); 1898 1899 CssmClient::Query query = 1900 Attribute("ModuleID") == guid && 1901 Attribute("SSID") == ssid; 1902 1903 // only CSP and DL tables are cleared here 1904 // (this function is private to securityd, which only handles those types) 1905 clearRecords(dbFiles.directDbHand(), 1906 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE)); 1907 clearRecords(dbFiles.directDbHand(), 1908 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE)); 1909 clearRecords(dbFiles.directDbHand(), 1910 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE)); 1911 clearRecords(dbFiles.directDbHand(), 1912 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE)); 1913 clearRecords(dbFiles.directDbHand(), 1914 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE)); 1915 clearRecords(dbFiles.directDbHand(), 1916 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE)); 1917 } 1918 1919 1920 } // end namespace Security