2  * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved. 
   4  * The contents of this file constitute Original Code as defined in and are 
   5  * subject to the Apple Public Source License Version 1.2 (the 'License'). 
   6  * You may not use this file except in compliance with the License. Please obtain 
   7  * a copy of the License at http://www.apple.com/publicsource and read it before 
  10  * This Original Code and all software distributed under the License are 
  11  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS 
  12  * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT 
  13  * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR 
  14  * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the 
  15  * specific language governing rights and limitations under the License. 
  19 #include "MDSSession.h" 
  21 #include <security_cdsa_plugin/DbContext.h> 
  22 #include "MDSModule.h" 
  23 #include "MDSAttrParser.h" 
  24 #include "MDSAttrUtils.h" 
  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> 
  36 #include <sys/types.h> 
  37 #include <sys/param.h> 
  46 using namespace CssmClient
; 
  49  * The layout of the various MDS DB files on disk is as follows: 
  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 
  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.  
  72  * The sticky bit in /var/db/mds ensures that users cannot modify other users' private  
  79  * Nominal location of Security.framework. 
  81 #define MDS_SYSTEM_PATH         "/System/Library/Frameworks" 
  82 #define MDS_SYSTEM_FRAME        "Security.framework" 
  85  * Nominal location of standard plugins. 
  87 #define MDS_BUNDLE_PATH         "/System/Library/Security" 
  88 #define MDS_BUNDLE_EXTEN        ".bundle" 
  92  * Location of MDS database and lock files. 
  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" 
  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" 
 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 
 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 
 117  * Location of per-user bundles, relative to home directory. 
 118  * Per-user DB files are in MDS_BASE_DB_DIR/<uid>/.  
 120 #define MDS_USER_BUNDLE         "Library/Security" 
 122 /* time to wait in ms trying to acquire lock */ 
 123 #define DB_LOCK_TIMEOUT         (2 * 1000) 
 125 /* Minimum interval, in seconds, between rescans for plugin changes */ 
 126 #define MDS_SCAN_INTERVAL       5 
 129 #define MSIoDbg(args...)                secinfo("MDS_IO", ## args) 
 131 /* Trace cleanDir() */ 
 132 #define MSCleanDirDbg(args...)  secinfo("MDS_CleanDir", ## args) 
 134 static std::string 
GetMDSBaseDBDir(bool isRoot
) 
 136         // what we return depends on whether or not we are root 
 140                 retValue 
= MDS_SYSTEM_DB_DIR
; 
 144                 char strBuffer
[PATH_MAX 
+ 1]; 
 145                 size_t result 
= confstr(_CS_DARWIN_USER_CACHE_DIR
, strBuffer
, sizeof(strBuffer
)); 
 148                         // we have an error, log it 
 149                         syslog(LOG_CRIT
, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error: %d", errno
); 
 150                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 153                 retValue 
= strBuffer
; 
 161 static std::string 
GetMDSDBDir() 
 164         bool isRoot 
= geteuid() == 0; 
 168                 retValue 
= MDS_SYSTEM_DB_DIR
; 
 172                 retValue 
= GetMDSBaseDBDir(isRoot
) + "/" + MDS_USER_DB_COMP
; 
 180 static std::string 
GetMDSObjectDBPath() 
 182         return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME
; 
 187 static std::string 
GetMDSDirectDBPath() 
 189         return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH
; 
 194 static std::string 
GetMDSDBLockPath() 
 196         return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME
; 
 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.  
 207         const char **keepFileNames
,             // array of strings, size numKeepFileNames 
 208         unsigned numKeepFileNames
) 
 212         char fullPath
[MAXPATHLEN
]; 
 215         MSCleanDirDbg("cleanDir(%s) top", dirPath
); 
 216     if ((dirp 
= opendir(dirPath
)) == NULL
) { 
 218                 MSCleanDirDbg("opendir(%s) returned  %d", dirPath
, rtn
); 
 224                 const char *d_name 
= NULL
; 
 226                 /* this block is for breaking on unqualified entries */ 
 231                                 /* end of directory or error */ 
 234                                         MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath
, rtn
); 
 240                         /* skip "." and ".." */ 
 241                         if( (d_name
[0] == '.') && 
 242                             ( (d_name
[1] == '\0') || 
 243                                   ( (d_name
[1] == '.') && (d_name
[2] == '\0') ) ) ) { 
 248                         /* skip entries in keepFileNames */ 
 249                         for(unsigned dex
=0; dex
<numKeepFileNames
; dex
++) { 
 250                                 if(!strcmp(keepFileNames
[dex
], d_name
)) { 
 256                 if(rtn 
|| (dp 
== NULL
)) { 
 257                         /* one way or another, we're done */ 
 265                 /* We have an entry to delete. Delete it, or recurse. */ 
 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); 
 275                         MSCleanDirDbg("cleanDir deleting dir %s", fullPath
); 
 276                         if(rmdir(fullPath
)) { 
 278                                 MSCleanDirDbg("unlink(%s) returned %d", fullPath
, rtn
); 
 283                         MSCleanDirDbg("cleanDir deleting file %s", fullPath
); 
 284                         if(unlink(fullPath
)) { 
 286                                 MSCleanDirDbg("unlink(%s) returned %d", fullPath
, rtn
); 
 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. 
 297                 if ((dirp 
= opendir(dirPath
)) == NULL
) { 
 299                         MSCleanDirDbg("opendir(%s) returned  %d", dirPath
, rtn
); 
 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.  
 315 static bool doesFileExist( 
 316         const char *filePath
, 
 319         struct stat 
&sb
)                // RETURNED 
 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
) { 
 328                         /* If we can't stat it we sure can't delete it. */ 
 329                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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
)) { 
 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::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 349                 if(rmdir(filePath
)) { 
 350                         MSDebug("rmdir(%s) returned %d", filePath
, errno
); 
 351                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 355                 if(unlink(filePath
)) { 
 356                         MSDebug("unlink(%s) returned %d", filePath
, errno
); 
 357                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 361         /* caller should be somewhat happy */ 
 366  * Determine if both of the specified DB files exist as accessible regular files with specified  
 367  * owner. Returns true if they do.  
 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.  
 374 static bool doFilesExist( 
 375         const char *objDbFile
, 
 376         const char *directDbFile
, 
 378         bool purge
,                                     // false means "passive" check  
 379         struct stat 
&objDbSb
,           // RETURNED 
 380         struct stat 
&directDbSb
)        // RETURNED 
 383         bool objectExist 
= doesFileExist(objDbFile
, forUid
, purge
, objDbSb
); 
 384         bool directExist 
= doesFileExist(directDbFile
, forUid
, purge
, directDbSb
); 
 385         if(objectExist 
&& directExist
) { 
 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.  
 398                 if(unlink(objDbFile
)) { 
 399                         MSDebug("unlink(%s) returned %d", objDbFile
, errno
); 
 400                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 404                 if(unlink(directDbFile
)) { 
 405                         MSDebug("unlink(%s) returned %d", directDbFile
, errno
); 
 406                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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.  
 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 */ 
 424 static bool doesDirectExist( 
 428         MdsDirectStatus 
&directStatus
)          /* RETURNED */ 
 432         MSIoDbg("stat %s in doesDirectExist", dirPath
); 
 433         if (lstat(dirPath
, &sb
)) { 
 437                                 directStatus 
= MDS_Access
; 
 440                                 directStatus 
= MDS_NotPresent
; 
 442                         /* Any others? Is this a good SWAG to handle the default? */ 
 444                                 directStatus 
= MDS_NotDirectory
; 
 449         mode_t fileType 
= sb
.st_mode 
& S_IFMT
; 
 450         if(fileType 
!= S_IFDIR
) { 
 451                 directStatus 
= MDS_NotDirectory
; 
 454         if(sb
.st_uid 
!= forUid
) { 
 455                 directStatus 
= MDS_BadOwnerMode
; 
 458         if((sb
.st_mode 
& 07777) != mode
) { 
 459                 directStatus 
= MDS_BadOwnerMode
; 
 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 
 471  * Returns an errno on any unrecoverable error.  
 473 static int createDir( 
 475         uid_t forUid
,                   // for checking - we don't try to set this 
 478         MdsDirectStatus directStatus
; 
 480         if(doesDirectExist(dirPath
, forUid
, dirMode
, directStatus
)) { 
 486          * Attempt recovery if there is *something* there. 
 487          * Anything other than "not present" should be considered to be a possible 
 491         switch(directStatus
) { 
 493                         /* normal trivial case: proceed. */ 
 496                 case MDS_NotDirectory
: 
 497                         /* there's a file or symlink in the way */ 
 498                         if(unlink(dirPath
)) { 
 500                                 MSDebug("createDir(%s): unlink() returned %d", dirPath
, rtn
); 
 505                 case MDS_BadOwnerMode
: 
 507                          * It's a directory; try to clean it out (which may well fail if we're 
 510                         rtn 
= cleanDir(dirPath
, NULL
, 0); 
 516                                 MSDebug("createDir(%s): rmdir() returned %d", dirPath
, rtn
); 
 522                 case MDS_Access
:                /* hopeless */ 
 523                         MSDebug("createDir(%s): access failure, bailing", dirPath
); 
 526         rtn 
= mkdir(dirPath
, dirMode
); 
 529                 MSDebug("createDir(%s): mkdir() returned %d", dirPath
, errno
); 
 532                 /* make sure umask does't trick us */ 
 533                 rtn 
= chmod(dirPath
, dirMode
); 
 535                         MSDebug("chmod(%s) returned  %d", dirPath
, errno
); 
 542  * Create an MDS session. 
 544 MDSSession::MDSSession (const Guid 
*inCallerGuid
, 
 545                         const CSSM_MEMORY_FUNCS 
&inMemoryFunctions
) : 
 546         DatabaseSession(MDSModule::get().databaseManager()), 
 547         mCssmMemoryFunctions (inMemoryFunctions
), 
 548         mModule(MDSModule::get()) 
 550         MSDebug("MDSSession::MDSSession"); 
 552     mCallerGuidPresent 
=  inCallerGuid 
!= nil
; 
 553     if (mCallerGuidPresent
) { 
 554         mCallerGuid 
= *inCallerGuid
; 
 558 MDSSession::~MDSSession () 
 560         MSDebug("MDSSession::~MDSSession"); 
 564 MDSSession::terminate () 
 566         MSDebug("MDSSession::terminate"); 
 570 const char* kExceptionDeletePath 
= "messages"; 
 574  * Called by security server via MDS_Install(). 
 577 MDSSession::install () 
 580         // Installation requires root 
 582         if(geteuid() != (uid_t
)0) {  
 583                 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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. 
 590         mModule
.setServerMode(); 
 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::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 607                 if(!lh
.obtainLock(MDS_INSTALL_LOCK_PATH
, DB_LOCK_TIMEOUT
)) { 
 608                         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 612                  * We own the whole MDS system. Clean everything out except for our lock 
 613                  * (and the directory it's in :-) 
 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::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 629                  * Do initial population of system DBs. 
 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
); 
 641 // In this implementation, the uninstall() call is not supported since 
 642 // we do not allow programmatic deletion of the MDS databases. 
 646 MDSSession::uninstall () 
 648         CssmError::throwMeNoLogging(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
); 
 652  * Common private open routine given a full specified path. 
 654 CSSM_DB_HANDLE 
MDSSession::dbOpen(const char *dbName
, bool batched
) 
 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 
 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
, 
 669                 NULL
,                           // AccessCred - hopefully optional  
 670                 batched 
? &batchOpenParams 
: NULL
, 
 675 /* DatabaseSession routines we need to override */ 
 676 void 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
) 
 683         if (!mModule
.serverMode()) { 
 685                  * Make sure securityd has finished initializing (system) MDS data. 
 686                  * Note that activate() only does IPC once and retains global state after that. 
 688                 SecurityServer::ClientSession 
client(Allocator::standard(), Allocator::standard()); 
 689                 client
.activate();              /* contact securityd - won't return until MDS is ready */ 
 692         /* make sure DBs are up-to-date */ 
 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).   
 701                 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME
); 
 704         if(!strcmp(DbName
, MDS_OBJECT_DIRECTORY_NAME
)) { 
 705                 dbName 
= MDS_OBJECT_DB_NAME
; 
 707         else if(!strcmp(DbName
, MDS_CDSA_DIRECTORY_NAME
)) { 
 708                 dbName 
= MDS_DIRECT_DB_NAME
; 
 711                 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME
); 
 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
); 
 720 CSSM_HANDLE 
MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle
, 
 721                              const CssmQuery 
*Query
, 
 722                              CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes
, 
 724                              CSSM_DB_UNIQUE_RECORD_PTR 
&UniqueId
) 
 727         return DatabaseSession::DataGetFirst(DBHandle
, Query
, Attributes
, Data
, UniqueId
); 
 732 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR 
&outNameList
) 
 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
); 
 742 MDSSession::FreeNameList(CSSM_NAME_LIST 
&inNameList
) 
 744         delete [] inNameList
.String
[0]; 
 745         delete [] inNameList
.String
[1]; 
 746         delete [] inNameList
.String
; 
 749 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle
, 
 752         printf("GetDbNameFromHandle: code on demand\n"); 
 753         CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 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. 
 763 MDSSession::LockHelper::obtainLock( 
 764         const char *lockFile
,   // e.g. MDS_INSTALL_LOCK_PATH 
 765         int timeout
)                    // default 0 
 769                 secinfo("mdslock", "obtainLock: calling open(%s)", lockFile
); 
 770                 mFD 
= open(lockFile
, O_EXLOCK 
| O_CREAT 
| O_RDWR
, 0644); 
 773                         secinfo("mdslock", "obtainLock: open error %d", errno
); 
 775                                 /* got a signal, go again */ 
 779                                 /* theoretically should never happen */ 
 784                         secinfo("mdslock", "obtainLock: success"); 
 791 // Release the exclusive lock over the MDS databases. If this session 
 792 // does not hold the lock, this method does nothing. 
 795 MDSSession::LockHelper::~LockHelper() 
 797         secinfo("mdslock", "releaseLock"); 
 808 /* given DB file name, fill in fully specified path */ 
 809 void MDSSession::dbFullPath( 
 811         char fullPath
[MAXPATHLEN
+1]) 
 813         mModule
.getDbPath(fullPath
); 
 814         assert(fullPath
[0] != '\0'); 
 815         strcat(fullPath
, "/"); 
 816         strcat(fullPath
, dbName
); 
 820  * See if any per-user bundles exist in specified directory. Returns true if so. 
 821  * First the check for one entry.... 
 823 static bool isBundle( 
 824         const struct dirent 
*dp
) 
 829         /* NFS directories show up as DT_UNKNOWN */ 
 837         int suffixLen 
= strlen(MDS_BUNDLE_EXTEN
); 
 838         size_t len 
= strlen(dp
->d_name
); 
 840         return (len 
>= suffixLen
) &&  
 841                !strcmp(dp
->d_name 
+ len 
- suffixLen
, MDS_BUNDLE_EXTEN
); 
 844 /* now the full directory search */ 
 845 static bool checkUserBundles( 
 846         const char *bundlePath
) 
 848         MSDebug("searching for user bundles in %s", bundlePath
); 
 849         DIR *dir 
= opendir(bundlePath
); 
 855         while ((dp 
= readdir(dir
)) != NULL
) { 
 857                         /* any other checking to do? */ 
 863         MSDebug("...%s bundle(s) found", rtn 
? "" : "No"); 
 867 #define COPY_BUF_SIZE   65536 
 870  * Single file copy with locking.  
 871  * Ensures that the source is a regular file with specified owner.  
 872  * Caller specifies mode of destination file.  
 873  * Throws a CssmError if the source file doesn't meet spec; throws a 
 874  *    UnixError on any other error (which is generally recoverable by  
 875  *    having the user MDS session use the system DB files). 
 877 static void safeCopyFile( 
 878         const char *fromPath
, 
 884         bool haveLock 
= false; 
 888         char tmpToPath
[MAXPATHLEN
+1]; 
 890         MSIoDbg("open %s, %s in safeCopyFile", fromPath
, toPath
); 
 892         if(!doesFileExist(fromPath
, fromUid
, false, sb
)) { 
 893                 MSDebug("safeCopyFile: bad system DB file %s", fromPath
); 
 894                 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
 897         /* create temp destination */ 
 898         snprintf(tmpToPath
, sizeof(tmpToPath
), "%s_", toPath
); 
 899         destFd 
= open(tmpToPath
, O_WRONLY 
| O_APPEND 
| O_CREAT 
| O_TRUNC 
| O_EXCL
, toMode
); 
 902                 MSDebug("Error %d opening user DB file %s\n", error
, tmpToPath
); 
 903                 UnixError::throwMeNoLogging(error
); 
 908                 /* don't get tripped up by umask */ 
 909                 if(fchmod(destFd
, toMode
)) { 
 911                         MSDebug("Error %d chmoding user DB file %s\n", error
, tmpToPath
); 
 912                         UnixError::throwMeNoLogging(error
); 
 915                 /* open source for reading */ 
 916                 srcFd 
= open(fromPath
, O_RDONLY
, 0); 
 919                         MSDebug("Error %d opening system DB file %s\n", error
, fromPath
); 
 920                         UnixError::throwMeNoLogging(error
); 
 923                 /* acquire the same kind of lock AtomicFile uses */ 
 927                 fl
.l_type 
= F_RDLCK
;            // AtomicFile gets F_WRLCK 
 928                 fl
.l_whence 
= SEEK_SET
; 
 930                 // Keep trying to obtain the lock if we get interupted. 
 932                         if (::fcntl(srcFd
, F_SETLKW
, &fl
) == -1) { 
 934                                 if (error 
== EINTR
) { 
 938                                 MSDebug("Error %d locking system DB file %s\n", error
, fromPath
); 
 939                                 UnixError::throwMeNoLogging(error
); 
 948                 char *buf 
= new char[COPY_BUF_SIZE
]; 
 953                                 bytesRead 
= read(srcFd
, buf
, COPY_BUF_SIZE
); 
 954                         } while (bytesRead 
< 0 && errno 
== EINTR
); 
 962                                 MSDebug("Error %d reading system DB file %s\n", error
, fromPath
); 
 963                                 UnixError::throwMeNoLogging(error
); 
 966                         ssize_t bytesWritten
; 
 969                                 bytesWritten 
= write(destFd
, buf
, bytesRead
); 
 970                         } while (bytesWritten 
< 0 && errno 
== EINTR
); 
 972                         if(bytesWritten 
< 0) { 
 975                                 MSDebug("Error %d writing user DB file %s\n", error
, tmpToPath
); 
 976                                 UnixError::throwMeNoLogging(error
); 
 982                 /* error is nonzero, we'll re-throw below...still have some cleanup */ 
 985         /* unlock source and close both */ 
 988                 if (::fcntl(srcFd
, F_SETLK
, &fl
) == -1) { 
 989                         MSDebug("Error %d unlocking system DB file %s\n", errno
, fromPath
); 
 992         MSIoDbg("close %s, %s in safeCopyFile", fromPath
, tmpToPath
); 
1000                 /* commit temp file */ 
1001                 if(::rename(tmpToPath
, toPath
)) { 
1003                         MSDebug("Error %d committing %s\n", error
, toPath
); 
1007                 UnixError::throwMeNoLogging(error
); 
1012  * Copy system DB files to specified user dir. Caller holds user DB lock.  
1013  * Throws a UnixError on error. 
1015 static void copySystemDbs( 
1016         const char *userDbFileDir
) 
1018         char toPath
[MAXPATHLEN
+1]; 
1020         snprintf(toPath
, sizeof(toPath
), "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
); 
1021         safeCopyFile(MDS_OBJECT_DB_PATH
, MDS_SYSTEM_UID
, toPath
, MDS_USER_DB_MODE
); 
1022         snprintf(toPath
, sizeof(toPath
), "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
); 
1023         safeCopyFile(MDS_DIRECT_DB_PATH
, MDS_SYSTEM_UID
, toPath
, MDS_USER_DB_MODE
); 
1027  * Ensure current DB files exist and are up-to-date. 
1028  * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any 
1029  * other public functions which access a DB from scratch. 
1031 void MDSSession::updateDataBases() 
1033         RecursionBlock::Once 
once(mUpdating
); 
1035                 return; // already updating; don't recurse 
1037         uid_t ourUid 
= geteuid(); 
1038         bool isRoot 
= (ourUid 
== 0); 
1040         /* if we scanned recently, we're done */ 
1041         double delta 
= mModule
.timeSinceLastScan(); 
1042         if(delta 
< (double)MDS_SCAN_INTERVAL
) { 
1047          * If we're root, the first thing we do is to ensure that system DBs are present. 
1048          * Note that this is a necessary artifact of the problem behind Radar 3800811. 
1049          * When that is fixed, install() should ONLY be called from the public MDS_Install() 
1051          * Anyway, if we *do* have to install here, we're done. 
1053         if(isRoot 
&& !systemDatabasesPresent(false)) { 
1055                 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
); 
1056                 mModule
.lastScanIsNow(); 
1061          * Obtain various per-user paths. Root is a special case but follows most 
1062          * of the same logic from here on. 
1064         std::string userDBFileDir 
= GetMDSDBDir(); 
1065         std::string userObjDBFilePath 
= GetMDSObjectDBPath(); 
1066         std::string userDirectDBFilePath 
= GetMDSDirectDBPath(); 
1067         char userBundlePath
[MAXPATHLEN
+1]; 
1068         std::string userDbLockPath 
= GetMDSDBLockPath(); 
1070         /* this means "no user bundles" */ 
1071         userBundlePath
[0] = '\0'; 
1073                 char *userHome 
= getenv("HOME"); 
1074                 if((userHome 
== NULL
) || 
1075                    (strlen(userHome
) + strlen(MDS_USER_BUNDLE
) + 2) > sizeof(userBundlePath
)) { 
1076                         /* Can't check for user bundles */ 
1077                         MSDebug("missing or invalid HOME; skipping user bundle check"); 
1079                 /* TBD: any other checking of userHome? */ 
1081                         snprintf(userBundlePath
, sizeof(userBundlePath
),  
1082                                 "%s/%s", userHome
, MDS_USER_BUNDLE
); 
1087          * Create the per-user directory...that's where the lock we'll be using lives. 
1090                 if(createDir(userDBFileDir
.c_str(), ourUid
, MDS_USER_DB_DIR_MODE
)) { 
1092                          * We'll just have to limp along using the read-only system DBs. 
1093                          * Note that this protects (somewhat) against the DoS attack in  
1094                          * Radar 3801292. The only problem is that this user won't be able  
1095                          * to use per-user bundles.  
1097                         MSDebug("Error creating user DBs; using system DBs"); 
1098                         mModule
.setDbPath(MDS_SYSTEM_DB_DIR
); 
1103         /* always release userLockFd no matter what happens */ 
1106         if(!lh
.obtainLock(userDbLockPath
.c_str(), DB_LOCK_TIMEOUT
)) { 
1107                 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
1113                                  * We copy the system DBs to the per-user DBs in two cases: 
1114                                  * -- user DBs don't exist, or 
1115                                  * -- system DBs have changed since the the last update to the user DBs.  
1116                                  *    This happens on smart card insertion and removal.  
1118                                 bool doCopySystem 
= false; 
1119                                 struct stat userObjStat
, userDirectStat
; 
1120                                 if(!doFilesExist(userObjDBFilePath
.c_str(), userDirectDBFilePath
.c_str(), ourUid
, true, 
1121                                                 userObjStat
, userDirectStat
)) { 
1122                                         doCopySystem 
= true; 
1125                                         /* compare the two mdsDirectory.db files */ 
1126                                         MSIoDbg("stat %s, %s in updateDataBases", 
1127                                                 MDS_DIRECT_DB_PATH
, userDirectDBFilePath
.c_str()); 
1128                                         struct stat sysStat
; 
1129                                         if (!stat(MDS_DIRECT_DB_PATH
, &sysStat
)) { 
1130                                                 doCopySystem 
= (sysStat
.st_mtimespec
.tv_sec 
> userDirectStat
.st_mtimespec
.tv_sec
) || 
1131                                                         ((sysStat
.st_mtimespec
.tv_sec 
== userDirectStat
.st_mtimespec
.tv_sec
) && 
1132                                                                 (sysStat
.st_mtimespec
.tv_nsec 
> userDirectStat
.st_mtimespec
.tv_nsec
)); 
1134                                                         MSDebug("user DB files obsolete at %s", userDBFileDir
.c_str()); 
1139                                         /* copy system DBs to user DBs */ 
1140                                         MSDebug("copying system DBs to user at %s", userDBFileDir
.c_str()); 
1141                                         copySystemDbs(userDBFileDir
.c_str()); 
1144                                         MSDebug("Using existing user DBs at %s", userDBFileDir
.c_str()); 
1147                         catch(const CssmError 
&cerror
) { 
1149                                  * Bad system DB file detected. Fatal. 
1155                                  * Error on delete or create user DBs; fall back on system DBs.  
1157                                 MSDebug("doFilesExist(purge) error; using system DBs"); 
1158                                 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
); 
1163                         MSDebug("Using system DBs only"); 
1167                  * Update per-user DBs from both bundle sources (System bundles, user bundles) 
1170                 DbFilesInfo 
dbFiles(*this, userDBFileDir
.c_str()); 
1171                 dbFiles
.removeOutdatedPlugins(); 
1172                 dbFiles
.updateSystemDbInfo(NULL
, MDS_BUNDLE_PATH
); 
1173                 if(userBundlePath
[0]) { 
1174                         /* skip for invalid or missing $HOME... */ 
1175                         if(checkUserBundles(userBundlePath
)) { 
1176                                 dbFiles
.updateForBundleDir(userBundlePath
); 
1179                 mModule
.setDbPath(userDBFileDir
.c_str()); 
1180         }       /* main block protected by mLockFd */ 
1184         mModule
.lastScanIsNow(); 
1188  * Remove all records with specified guid (a.k.a. ModuleID) from specified DB. 
1190 void MDSSession::removeRecordsForGuid( 
1192         CSSM_DB_HANDLE dbHand
) 
1194         // tell the DB to flush its intermediate data to disk 
1195         PassThrough(dbHand
, CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
); 
1196         CssmClient::Query query 
= Attribute("ModuleID") == guid
; 
1197         clearRecords(dbHand
, query
.cssmQuery()); 
1201 void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand
, const CssmQuery 
&query
) 
1203         CSSM_DB_UNIQUE_RECORD_PTR record 
= NULL
; 
1204         CSSM_HANDLE resultHand 
= DataGetFirst(dbHand
, 
1209         if (resultHand 
== CSSM_INVALID_HANDLE
) 
1210                 return; // no matches 
1213                         DataDelete(dbHand
, *record
); 
1214                         FreeUniqueRecord(dbHand
, *record
); 
1216                 } while (DataGetNext(dbHand
, 
1223                         FreeUniqueRecord(dbHand
, *record
); 
1224                 DataAbortQuery(dbHand
, resultHand
); 
1230  * Determine if system databases are present.  
1231  * If the purge argument is true, we'll ensure that either both or neither  
1232  * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH. 
1234 bool MDSSession::systemDatabasesPresent(bool purge
) 
1240                  * This can throw on a failed attempt to delete sole existing file.... 
1241                  * But if that happens while we're root, our goose is fully cooked.  
1243                 struct stat objDbSb
, directDbSb
; 
1244                 if(doFilesExist(MDS_OBJECT_DB_PATH
, MDS_DIRECT_DB_PATH
,  
1245                                 MDS_SYSTEM_UID
, purge
, objDbSb
, directDbSb
)) { 
1256  * Given a DB name (which is used as an absolute path) and an array of  
1257  * RelationInfos, create a DB. 
1260 MDSSession::createSystemDatabase( 
1262         const RelationInfo 
*relationInfo
, 
1263         unsigned numRelations
, 
1264         CSSM_BOOL autoCommit
, 
1266         CSSM_DB_HANDLE 
&dbHand
)                 // RETURNED 
1269         CSSM_DBINFO_PTR dbInfoP 
= &dbInfo
; 
1271         memset(dbInfoP
, 0, sizeof(CSSM_DBINFO
)); 
1272         dbInfoP
->NumberOfRecordTypes 
= numRelations
; 
1273         dbInfoP
->IsLocal 
= CSSM_TRUE
;           // TBD - what does this mean? 
1274         dbInfoP
->AccessPath 
= NULL
;             // TBD 
1276         /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex 
1278         unsigned size 
= sizeof(CSSM_DB_PARSING_MODULE_INFO
) * numRelations
; 
1279         dbInfoP
->DefaultParsingModules 
= (CSSM_DB_PARSING_MODULE_INFO_PTR
)malloc(size
); 
1280         memset(dbInfoP
->DefaultParsingModules
, 0, size
); 
1281         size 
= sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO
) * numRelations
; 
1282         dbInfoP
->RecordAttributeNames 
= (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR
)malloc(size
); 
1283         memset(dbInfoP
->RecordAttributeNames
, 0, size
); 
1284         size 
= sizeof(CSSM_DB_RECORD_INDEX_INFO
) * numRelations
; 
1285         dbInfoP
->RecordIndexes 
= (CSSM_DB_RECORD_INDEX_INFO_PTR
)malloc(size
); 
1286         memset(dbInfoP
->RecordIndexes
, 0, size
); 
1288         /* cook up attribute and index info for each relation */ 
1290         for(relation
=0; relation
<numRelations
; relation
++) { 
1291                 const struct RelationInfo 
*relp 
= &relationInfo
[relation
];      // source 
1292                 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo 
=  
1293                         &dbInfoP
->RecordAttributeNames
[relation
];                                       // dest 1 
1294                 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo 
=  
1295                         &dbInfoP
->RecordIndexes
[relation
];                                              // dest 2 
1297                 attrInfo
->DataRecordType 
= relp
->DataRecordType
; 
1298                 attrInfo
->NumberOfAttributes 
= relp
->NumberOfAttributes
; 
1299                 attrInfo
->AttributeInfo 
= (CSSM_DB_ATTRIBUTE_INFO_PTR
)relp
->AttributeInfo
; 
1301                 indexInfo
->DataRecordType 
= relp
->DataRecordType
; 
1302                 indexInfo
->NumberOfIndexes 
= relp
->NumberOfIndexes
; 
1303                 indexInfo
->IndexInfo 
= (CSSM_DB_INDEX_INFO_PTR
)relp
->IndexInfo
; 
1306         /* set autocommit and mode */ 
1307         CSSM_APPLEDL_OPEN_PARAMETERS openParams
; 
1308         memset(&openParams
, 0, sizeof(openParams
)); 
1309         openParams
.length 
= sizeof(openParams
); 
1310         openParams
.version 
= CSSM_APPLEDL_OPEN_PARAMETERS_VERSION
; 
1311         openParams
.autoCommit 
= autoCommit
; 
1312         openParams
.mask 
= kCSSM_APPLEDL_MASK_MODE
; 
1313         openParams
.mode 
= mode
; 
1319                         CSSM_DB_ACCESS_READ 
| CSSM_DB_ACCESS_WRITE
, 
1320                         NULL
,                   // CredAndAclEntry 
1325                 MSDebug("Error on DbCreate"); 
1326                 free(dbInfoP
->DefaultParsingModules
); 
1327                 free(dbInfoP
->RecordAttributeNames
); 
1328                 free(dbInfoP
->RecordIndexes
); 
1329                 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR
); 
1331         free(dbInfoP
->DefaultParsingModules
); 
1332         free(dbInfoP
->RecordAttributeNames
); 
1333         free(dbInfoP
->RecordIndexes
); 
1338  * Create system databases from scratch if they do not already exist.  
1339  * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to 
1340  * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH).  
1341  * Returns true if we actually built the files, false if they already  
1344 bool MDSSession::createSystemDatabases( 
1345         CSSM_BOOL autoCommit
, 
1348         CSSM_DB_HANDLE objectDbHand 
= 0; 
1349         CSSM_DB_HANDLE directoryDbHand 
= 0; 
1351         assert(geteuid() == (uid_t
)0); 
1352         if(systemDatabasesPresent(true)) { 
1353                 /* both databases exist as regular files with correct owner - we're done */ 
1354                 MSDebug("system DBs already exist"); 
1358         /* create two DBs - any exception here results in deleting both of them */ 
1359         MSDebug("Creating MDS DBs"); 
1361                 createSystemDatabase(MDS_OBJECT_DB_PATH
, &kObjectRelation
, 1,  
1362                         autoCommit
, mode
, objectDbHand
); 
1363                 MSIoDbg("close objectDbHand in createSystemDatabases"); 
1364                 DbClose(objectDbHand
); 
1366                 createSystemDatabase(MDS_DIRECT_DB_PATH
, kMDSRelationInfo
, kNumMdsRelations
, 
1367                         autoCommit
, mode
, directoryDbHand
); 
1368                 MSIoDbg("close directoryDbHand in createSystemDatabases"); 
1369                 DbClose(directoryDbHand
); 
1370                 directoryDbHand 
= 0; 
1373                 MSDebug("Error creating MDS DBs - deleting both DB files"); 
1374                 unlink(MDS_OBJECT_DB_PATH
); 
1375                 unlink(MDS_DIRECT_DB_PATH
); 
1382  * DbFilesInfo helper class 
1385 /* Note both DB files MUST exist at construction time */ 
1386 MDSSession::DbFilesInfo::DbFilesInfo( 
1387         MDSSession 
&session
,  
1388         const char *dbPath
) : 
1394         assert(strlen(dbPath
) < MAXPATHLEN
); 
1395         strcpy(mDbPath
, dbPath
); 
1397         /* stat the two DB files, snag the later timestamp */ 
1398         char path
[MAXPATHLEN
]; 
1399         sprintf(path
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
); 
1401         MSIoDbg("stat %s in DbFilesInfo()", path
); 
1402         int rtn 
= ::stat(path
, &sb
); 
1405                 MSDebug("Error %d statting DB file %s", error
, path
); 
1406                 UnixError::throwMeNoLogging(error
); 
1408         mLaterTimestamp 
= sb
.st_mtimespec
.tv_sec
; 
1409         sprintf(path
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
); 
1410         MSIoDbg("stat %s in DbFilesInfo()", path
); 
1411         rtn 
= ::stat(path
, &sb
); 
1414                 MSDebug("Error %d statting DB file %s", error
, path
); 
1415                 UnixError::throwMeNoLogging(error
); 
1417         if(sb
.st_mtimespec
.tv_sec 
> mLaterTimestamp
) { 
1418                 mLaterTimestamp 
= sb
.st_mtimespec
.tv_sec
; 
1422 MDSSession::DbFilesInfo::~DbFilesInfo() 
1424         if(mObjDbHand 
!= 0) { 
1425                 /* autocommit on, henceforth */ 
1426                 mSession
.PassThrough(mObjDbHand
, 
1427                         CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
); 
1428                 MSIoDbg("close objectDbHand in ~DbFilesInfo()"); 
1429                 mSession
.DbClose(mObjDbHand
); 
1432         if(mDirectDbHand 
!= 0) { 
1433                 mSession
.PassThrough(mDirectDbHand
, 
1434                         CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
); 
1435                 MSIoDbg("close mDirectDbHand in ~DbFilesInfo()"); 
1436                 mSession
.DbClose(mDirectDbHand
); 
1441 /* lazy evaluation of both DB handlesÊ*/ 
1442 CSSM_DB_HANDLE 
MDSSession::DbFilesInfo::objDbHand() 
1444         if(mObjDbHand 
!= 0) { 
1447         char fullPath
[MAXPATHLEN 
+ 1]; 
1448         sprintf(fullPath
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
); 
1449         MSIoDbg("open %s in objDbHand()", fullPath
); 
1450         mObjDbHand 
= mSession
.dbOpen(fullPath
, true);   // batch mode 
1454 CSSM_DB_HANDLE 
MDSSession::DbFilesInfo::directDbHand() 
1456         if(mDirectDbHand 
!= 0) { 
1457                 return mDirectDbHand
; 
1459         char fullPath
[MAXPATHLEN 
+ 1]; 
1460         sprintf(fullPath
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
); 
1461         MSIoDbg("open %s in directDbHand()", fullPath
); 
1462         mDirectDbHand 
= mSession
.dbOpen(fullPath
, true);        // batch mode 
1463         return mDirectDbHand
; 
1467  * Update the info for Security.framework and the system bundles. 
1469 void MDSSession::DbFilesInfo::updateSystemDbInfo( 
1470         const char *systemPath
,         // e.g., /System/Library/Frameworks 
1471         const char *bundlePath
)         // e.g., /System/Library/Security 
1474          * Security.framework - CSSM and built-in modules - only for initial population of 
1479                 if (CFRef
<CFBundleRef
> me 
= CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security"))) 
1480                         if (CFRef
<CFURLRef
> url 
= CFBundleCopyBundleURL(me
)) 
1481                                 if (CFRef
<CFStringRef
> cfpath 
= CFURLCopyFileSystemPath(url
, kCFURLPOSIXPathStyle
)) 
1482                                         path 
= cfString(cfpath
);        // path to my bundle 
1484                 if (path
.empty())   // use system default 
1485                         path 
= string(systemPath
) + "/" MDS_SYSTEM_FRAME
; 
1486                 updateForBundle(path
.c_str()); 
1489         /* Standard loadable bundles */ 
1490         updateForBundleDir(bundlePath
); 
1494 MDSSession::DbFilesInfo::TbdRecord::TbdRecord( 
1495         const CSSM_DATA 
&guid
) 
1497     if (guid
.Length 
!= 0 && guid
.Length 
< MAX_GUID_LEN
) { 
1498         memmove(mGuid
, guid
.Data
, guid
.Length
); 
1499         // mGuid is treated as a string elsewhere; terminate 
1500         mGuid
[guid
.Length
] = '\0'; 
1505  * Test if plugin specified by pluginPath needs to be deleted from DBs.  
1506  * If so, add to tbdVector. 
1508 void MDSSession::DbFilesInfo::checkOutdatedPlugin( 
1509         const CSSM_DATA 
&pathValue
,  
1510         const CSSM_DATA 
&guidValue
,  
1511         TbdVector 
&tbdVector
) 
1513         /* stat the specified plugin */ 
1515         bool obsolete 
= false; 
1516         string path 
= CssmData::overlay(pathValue
).toString(); 
1517         if (!path
.empty() && path
[0] == '*') { 
1518                 /* builtin pseudo-path; never obsolete this */ 
1521         MSIoDbg("stat %s in checkOutdatedPlugin()", path
.c_str()); 
1522         int rtn 
= ::stat(path
.c_str(), &sb
); 
1524                 /* not there or inaccessible; delete */ 
1527         else if(sb
.st_mtimespec
.tv_sec 
> mLaterTimestamp
) { 
1528                 /* timestamp of plugin's main directory later than that of DBs */ 
1532         if (guidValue
.Length 
!= 0 && guidValue
.Length 
< MAX_GUID_LEN
) { 
1533             TbdRecord 
*tbdRecord 
= new TbdRecord(guidValue
); 
1534             tbdVector
.push_back(tbdRecord
); 
1535             MSDebug("checkOutdatedPlugin: flagging %s obsolete", path
.c_str()); 
1537             MSDebug("checkOutdatedPlugin: flagging %s obsolete, but guid length is invalid (%zu)", path
.c_str(), guidValue
.Length
); 
1543  * Examine dbFiles.objDbHand; remove all fields associated with any bundle 
1544  * i.e., with any path) which are either not present on disk, or which  
1545  * have changed since dbFiles.laterTimestamp(). 
1547 void MDSSession::DbFilesInfo::removeOutdatedPlugins() 
1550         CSSM_DB_UNIQUE_RECORD_PTR               record 
= NULL
; 
1551         CSSM_HANDLE                                             resultHand
; 
1552         CSSM_DB_RECORD_ATTRIBUTE_DATA   recordAttrs
; 
1553         CSSM_DB_ATTRIBUTE_DATA                  theAttrs
[2]; 
1554         CSSM_DB_ATTRIBUTE_INFO_PTR              attrInfo
; 
1555         TbdVector                                               tbdRecords
; 
1558          * First, scan object directory. All we need are the path and GUID attributes.  
1560         recordAttrs
.DataRecordType 
= MDS_OBJECT_RECORDTYPE
; 
1561         recordAttrs
.SemanticInformation 
= 0; 
1562         recordAttrs
.NumberOfAttributes 
= 2; 
1563         recordAttrs
.AttributeData 
= theAttrs
; 
1565         attrInfo 
= &theAttrs
[0].Info
; 
1566         attrInfo
->AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
1567         attrInfo
->Label
.AttributeName 
= (char*) "ModuleID"; 
1568         attrInfo
->AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
; 
1569         theAttrs
[0].NumberOfValues 
= 0; 
1570         theAttrs
[0].Value 
= NULL
; 
1571         attrInfo 
= &theAttrs
[1].Info
; 
1572         attrInfo
->AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
1573         attrInfo
->Label
.AttributeName 
= (char*) "Path"; 
1574         attrInfo
->AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
; 
1575         theAttrs
[1].NumberOfValues 
= 0; 
1576         theAttrs
[1].Value 
= NULL
; 
1578         /* just search by recordType, no predicates */ 
1579         query
.RecordType 
= MDS_OBJECT_RECORDTYPE
; 
1580         query
.Conjunctive 
= CSSM_DB_NONE
; 
1581         query
.NumSelectionPredicates 
= 0; 
1582         query
.SelectionPredicate 
= NULL
; 
1583         query
.QueryLimits
.TimeLimit 
= 0;                        // FIXME - meaningful? 
1584         query
.QueryLimits
.SizeLimit 
= 1;                        // FIXME - meaningful? 
1585         query
.QueryFlags 
= 0;           // CSSM_QUERY_RETURN_DATA...FIXME - used? 
1587         CssmQuery 
perryQuery(query
); 
1589                 resultHand 
= mSession
.DataGetFirst(objDbHand(), 
1596                 MSDebug("removeOutdatedPlugins: DataGetFirst threw"); 
1600                 mSession
.FreeUniqueRecord(mObjDbHand
, *record
); 
1603                 if(theAttrs
[0].NumberOfValues 
&& theAttrs
[1].NumberOfValues
) { 
1604                         checkOutdatedPlugin(*theAttrs
[1].Value
, *theAttrs
[0].Value
,  
1608                         MSDebug("removeOutdatedPlugins: incomplete record found (1)!"); 
1610                 for(unsigned dex
=0; dex
<2; dex
++) { 
1611                         CSSM_DB_ATTRIBUTE_DATA 
*attr 
= &theAttrs
[dex
]; 
1612                         for (unsigned attrDex
=0; attrDex
<attr
->NumberOfValues
; attrDex
++) { 
1613                                 if(attr
->Value
[attrDex
].Data
) { 
1614                                         mSession
.free(attr
->Value
[attrDex
].Data
); 
1618                                 mSession
.free(attr
->Value
); 
1623                 /* empty Object DB - we're done */ 
1624                 MSDebug("removeOutdatedPlugins: empty object DB"); 
1628         /* now the rest of the object DB records */ 
1630                 bool brtn 
= mSession
.DataGetNext(objDbHand(), 
1640                         mSession
.FreeUniqueRecord(mObjDbHand
, *record
); 
1642                 if(theAttrs
[0].NumberOfValues 
&& theAttrs
[1].NumberOfValues
) { 
1643                         checkOutdatedPlugin(*theAttrs
[1].Value
,  
1648                         MSDebug("removeOutdatedPlugins: incomplete record found (2)!"); 
1650                 for(unsigned dex
=0; dex
<2; dex
++) { 
1651                         CSSM_DB_ATTRIBUTE_DATA 
*attr 
= &theAttrs
[dex
]; 
1652                         for (unsigned attrDex
=0; attrDex
<attr
->NumberOfValues
; attrDex
++) { 
1653                                 if(attr
->Value
[attrDex
].Data
) { 
1654                                         mSession
.free(attr
->Value
[attrDex
].Data
); 
1658                                 mSession
.free(attr
->Value
); 
1662         /* no DataAbortQuery needed; we scanned until completion */ 
1665          * We have a vector of plugins to be deleted. Remove all records from both 
1666          * DBs associated with the plugins, as specified by guid. 
1668         size_t numRecords 
= tbdRecords
.size(); 
1669         for(size_t i
=0; i
<numRecords
; i
++) { 
1670                 TbdRecord 
*tbdRecord 
= tbdRecords
[i
]; 
1671                 mSession
.removeRecordsForGuid(tbdRecord
->guid(), objDbHand()); 
1672                 mSession
.removeRecordsForGuid(tbdRecord
->guid(), directDbHand()); 
1674         for(size_t i
=0; i
<numRecords
; i
++) { 
1675                 delete tbdRecords
[i
]; 
1681  * Update DBs for all bundles in specified directory. 
1683 void MDSSession::DbFilesInfo::updateForBundleDir( 
1684         const char *bundleDirPath
) 
1686         /* do this with readdir(); CFBundleCreateBundlesFromDirectory is 
1687          * much too heavyweight */ 
1688         MSDebug("...updating DBs for dir %s", bundleDirPath
); 
1689         DIR *dir 
= opendir(bundleDirPath
); 
1691                 MSDebug("updateForBundleDir: error %d opening %s", errno
, bundleDirPath
); 
1695         char fullPath
[MAXPATHLEN
]; 
1696         while ((dp 
= readdir(dir
)) != NULL
) { 
1698                         sprintf(fullPath
, "%s/%s", bundleDirPath
, dp
->d_name
); 
1699                         updateForBundle(fullPath
); 
1706  * lookup by path - just returns true if there is a record assoociated with the path 
1709 bool MDSSession::DbFilesInfo::lookupForPath( 
1713         CSSM_DB_UNIQUE_RECORD_PTR               record 
= NULL
; 
1714         CSSM_HANDLE                                             resultHand 
= 0; 
1715         CSSM_DB_RECORD_ATTRIBUTE_DATA   recordAttrs
; 
1716         CSSM_DB_ATTRIBUTE_DATA                  theAttr
; 
1717         CSSM_DB_ATTRIBUTE_INFO_PTR              attrInfo 
= &theAttr
.Info
; 
1718         CSSM_SELECTION_PREDICATE                predicate
; 
1721         recordAttrs
.DataRecordType 
= MDS_OBJECT_RECORDTYPE
; 
1722         recordAttrs
.SemanticInformation 
= 0; 
1723         recordAttrs
.NumberOfAttributes 
= 1; 
1724         recordAttrs
.AttributeData 
= &theAttr
; 
1726         attrInfo
->AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
1727         attrInfo
->Label
.AttributeName 
= (char*) "Path"; 
1728         attrInfo
->AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
; 
1730         theAttr
.NumberOfValues 
= 0; 
1731         theAttr
.Value 
= NULL
; 
1733         predicate
.DbOperator 
= CSSM_DB_EQUAL
; 
1734         predicate
.Attribute
.Info
.AttributeNameFormat 
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
; 
1735         predicate
.Attribute
.Info
.Label
.AttributeName 
= (char*) "Path"; 
1736         predicate
.Attribute
.Info
.AttributeFormat 
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
; 
1737         predData
.Data 
= (uint8 
*)path
; 
1738         predData
.Length 
= strlen(path
); 
1739         predicate
.Attribute
.Value 
= &predData
; 
1740         predicate
.Attribute
.NumberOfValues 
= 1; 
1742         query
.RecordType 
= MDS_OBJECT_RECORDTYPE
; 
1743         query
.Conjunctive 
= CSSM_DB_NONE
; 
1744         query
.NumSelectionPredicates 
= 1; 
1745         query
.SelectionPredicate 
= &predicate
; 
1746         query
.QueryLimits
.TimeLimit 
= 0;                        // FIXME - meaningful? 
1747         query
.QueryLimits
.SizeLimit 
= 1;                        // FIXME - meaningful? 
1748         query
.QueryFlags 
= 0;           // CSSM_QUERY_RETURN_DATA...FIXME - used? 
1752                 CssmQuery 
perryQuery(query
); 
1753                 resultHand 
= mSession
.DataGetFirst(objDbHand(), 
1763                 mSession
.FreeUniqueRecord(mObjDbHand
, *record
); 
1768         if(resultHand 
&& ourRtn
) { 
1769                 /* more resulting pending; terminate the search */ 
1771                         mSession
.DataAbortQuery(mObjDbHand
, resultHand
); 
1774                         MSDebug("exception on DataAbortQuery in lookupForPath"); 
1777         for(unsigned dex
=0; dex
<theAttr
.NumberOfValues
; dex
++) { 
1778                 if(theAttr
.Value
[dex
].Data
) { 
1779                         mSession
.free(theAttr
.Value
[dex
].Data
); 
1782         mSession
.free(theAttr
.Value
); 
1786 /* update entry for one bundle, which is known to exist */ 
1787 void MDSSession::DbFilesInfo::updateForBundle( 
1788         const char *bundlePath
) 
1790         MSDebug("...updating DBs for bundle %s", bundlePath
); 
1792         /* Quick lookup - do we have ANY entry for a bundle with this path? */ 
1793         if(lookupForPath(bundlePath
)) { 
1794                 /* Yep, we're done */ 
1797         MDSAttrParser 
parser(bundlePath
, 
1802                 parser
.parseAttrs(); 
1804         catch (const CssmError 
&err
) { 
1805                 // a corrupt MDS info file invalidates the entire plugin 
1806                 const char *guid 
= parser
.guid(); 
1808                         mSession
.removeRecordsForGuid(guid
, objDbHand()); 
1809                         mSession
.removeRecordsForGuid(guid
, directDbHand()); 
1816 // Private API: add MDS records from contents of file 
1817 // These files are typically written by securityd and handed to us in this call. 
1819 void MDSSession::installFile(const MDS_InstallDefaults 
*defaults
, 
1820         const char *inBundlePath
, const char *subdir
, const char *file
) 
1822         string bundlePath 
= inBundlePath 
? inBundlePath 
: cfString(CFBundleGetMainBundle()); 
1823         DbFilesInfo 
dbFiles(*this, MDS_SYSTEM_DB_DIR
); 
1824         MDSAttrParser 
parser(bundlePath
.c_str(), 
1826                 dbFiles
.objDbHand(), 
1827                 dbFiles
.directDbHand()); 
1828         parser
.setDefaults(defaults
); 
1831                 if (file 
== NULL
)       // parse a directory 
1832                         if (subdir
)             // a particular directory 
1833                                 parser
.parseAttrs(CFTempString(subdir
)); 
1834                         else                    // all resources in bundle 
1835                                 parser
.parseAttrs(NULL
); 
1836                 else                            // parse just one file 
1837                         parser
.parseFile(CFRef
<CFURLRef
>(makeCFURL(file
)), CFTempString(subdir
)); 
1839         catch (const CssmError 
&err
) { 
1840                 const char *guid 
= parser
.guid(); 
1842                         removeRecordsForGuid(guid
, dbFiles
.objDbHand()); 
1843                         removeRecordsForGuid(guid
, dbFiles
.directDbHand()); 
1850 // Private API: Remove all records for a guid/subservice 
1852 // Note: Multicursors searching for SSID fail because not all records in the 
1853 // database have this attribute. So we have to explicitly run through all tables 
1856 void MDSSession::removeSubservice(const char *guid
, uint32 ssid
) 
1858         DbFilesInfo 
dbFiles(*this, MDS_SYSTEM_DB_DIR
); 
1860         CssmClient::Query query 
= 
1861                 Attribute("ModuleID") == guid 
&& 
1862                 Attribute("SSID") == ssid
; 
1864         // only CSP and DL tables are cleared here 
1865         // (this function is private to securityd, which only handles those types) 
1866         clearRecords(dbFiles
.directDbHand(), 
1867                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE
)); 
1868         clearRecords(dbFiles
.directDbHand(), 
1869                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE
)); 
1870         clearRecords(dbFiles
.directDbHand(), 
1871                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE
)); 
1872         clearRecords(dbFiles
.directDbHand(), 
1873                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE
)); 
1874         clearRecords(dbFiles
.directDbHand(), 
1875                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE
)); 
1876         clearRecords(dbFiles
.directDbHand(), 
1877                 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE
)); 
1881 } // end namespace Security