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