2 * Copyright (c) 2000-2001 Apple Computer, 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>
45 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 userss 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...) secdebug("MDS_IO", ## args)
131 /* Trace cleanDir() */
132 #define MSCleanDirDbg(args...) secdebug("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 confstr(_CS_DARWIN_USER_CACHE_DIR
, strBuffer
, sizeof(strBuffer
));
146 retValue
= strBuffer
;
154 static std::string
GetMDSDBDir()
157 bool isRoot
= geteuid() == 0;
161 retValue
= MDS_SYSTEM_DB_DIR
;
165 retValue
= GetMDSBaseDBDir(isRoot
) + "/" + MDS_USER_DB_COMP
;
173 static std::string
GetMDSObjectDBPath()
175 return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME
;
180 static std::string
GetMDSDirectDBPath()
182 return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH
;
187 static std::string
GetMDSDBLockPath()
189 return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME
;
195 * Given a path to a directory, remove everything in the directory except for the optional
196 * keepFileNames. Returns 0 on success, else an errno.
200 const char **keepFileNames
, // array of strings, size numKeepFileNames
201 unsigned numKeepFileNames
)
205 char fullPath
[MAXPATHLEN
];
208 MSCleanDirDbg("cleanDir(%s) top", dirPath
);
209 if ((dirp
= opendir(dirPath
)) == NULL
) {
211 MSCleanDirDbg("opendir(%s) returned %d", dirPath
, rtn
);
217 const char *d_name
= NULL
;
219 /* this block is for breaking on unqualified entries */
224 /* end of directory or error */
227 MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath
, rtn
);
233 /* skip "." and ".." */
234 if( (d_name
[0] == '.') &&
235 ( (d_name
[1] == '\0') ||
236 ( (d_name
[1] == '.') && (d_name
[2] == '\0') ) ) ) {
241 /* skip entries in keepFileNames */
242 for(unsigned dex
=0; dex
<numKeepFileNames
; dex
++) {
243 if(!strcmp(keepFileNames
[dex
], d_name
)) {
249 if(rtn
|| (dp
== NULL
)) {
250 /* one way or another, we're done */
258 /* We have an entry to delete. Delete it, or recurse. */
260 snprintf(fullPath
, sizeof(fullPath
), "%s/%s", dirPath
, d_name
);
261 if(dp
->d_type
== DT_DIR
) {
262 /* directory. Clean it, then delete. */
263 MSCleanDirDbg("cleanDir recursing for dir %s", fullPath
);
264 rtn
= cleanDir(fullPath
, NULL
, 0);
268 MSCleanDirDbg("cleanDir deleting dir %s", fullPath
);
269 if(rmdir(fullPath
)) {
271 MSCleanDirDbg("unlink(%s) returned %d", fullPath
, rtn
);
276 MSCleanDirDbg("cleanDir deleting file %s", fullPath
);
277 if(unlink(fullPath
)) {
279 MSCleanDirDbg("unlink(%s) returned %d", fullPath
, rtn
);
285 * Back to beginning of directory for clean scan.
286 * Normally we'd just do a rewinddir() here but the RAMDisk filesystem,
287 * used when booting from DVD, does not implement that properly.
290 if ((dirp
= opendir(dirPath
)) == NULL
) {
292 MSCleanDirDbg("opendir(%s) returned %d", dirPath
, rtn
);
302 * Determine if a file exists as regular file with specified owner. Returns true if so.
303 * If the purge argument is true, and there is something at the specified path that
304 * doesn't meet spec, we do everything we can to delete it. If that fails we throw
305 * CssmError(CSSM_ERRCODE_MDS_ERROR). If the delete succeeds we return false.
306 * Returns the stat info on success for further processing by caller.
308 static bool doesFileExist(
309 const char *filePath
,
312 struct stat
&sb
) // RETURNED
314 MSIoDbg("stat %s in doesFileExist", filePath
);
315 if(lstat(filePath
, &sb
)) {
316 /* doesn't exist or we can't even get to it. */
317 if(errno
== ENOENT
) {
321 /* If we can't stat it we sure can't delete it. */
322 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
327 /* it's there...how does it look? */
328 mode_t fileType
= sb
.st_mode
& S_IFMT
;
329 if((fileType
== S_IFREG
) && (sb
.st_uid
== forUid
)) {
336 /* not what we want: get rid of it. */
337 if(fileType
== S_IFDIR
) {
338 /* directory: clean then remove */
339 if(cleanDir(filePath
, NULL
, 0)) {
340 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
342 if(rmdir(filePath
)) {
343 MSDebug("rmdir(%s) returned %d", filePath
, errno
);
344 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
348 if(unlink(filePath
)) {
349 MSDebug("unlink(%s) returned %d", filePath
, errno
);
350 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
354 /* caller should be somewhat happy */
359 * Determine if both of the specified DB files exist as accessible regular files with specified
360 * owner. Returns true if they do.
362 * If the purge argument is true, we'll ensure that either both files exist with
363 * the right owner, or neither of the files exist on exit. An error on that operation
364 * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed).
365 * Returns the stat info for both files on success for further processing by caller.
367 static bool doFilesExist(
368 const char *objDbFile
,
369 const char *directDbFile
,
371 bool purge
, // false means "passive" check
372 struct stat
&objDbSb
, // RETURNED
373 struct stat
&directDbSb
) // RETURNED
376 bool objectExist
= doesFileExist(objDbFile
, forUid
, purge
, objDbSb
);
377 bool directExist
= doesFileExist(directDbFile
, forUid
, purge
, directDbSb
);
378 if(objectExist
&& directExist
) {
386 * At least one does not exist - ensure neither of them do.
387 * Note that if we got this far, we know the one that exists is a regular file
388 * so it's safe to just unlink it.
391 if(unlink(objDbFile
)) {
392 MSDebug("unlink(%s) returned %d", objDbFile
, errno
);
393 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
397 if(unlink(directDbFile
)) {
398 MSDebug("unlink(%s) returned %d", directDbFile
, errno
);
399 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
406 * Determine if specified directory exists with specified owner and mode.
407 * Returns true if copacetic, else returns false and also indicates
408 * via the directStatus out param what went wrong.
411 MDS_NotPresent
, /* nothing there */
412 MDS_NotDirectory
, /* not a directory */
413 MDS_BadOwnerMode
, /* wrong owner or mode */
414 MDS_Access
/* couldn't search the directories */
417 static bool doesDirectExist(
421 MdsDirectStatus
&directStatus
) /* RETURNED */
425 MSIoDbg("stat %s in doesDirectExist", dirPath
);
426 if (lstat(dirPath
, &sb
)) {
430 directStatus
= MDS_Access
;
433 directStatus
= MDS_NotPresent
;
435 /* Any others? Is this a good SWAG to handle the default? */
437 directStatus
= MDS_NotDirectory
;
442 mode_t fileType
= sb
.st_mode
& S_IFMT
;
443 if(fileType
!= S_IFDIR
) {
444 directStatus
= MDS_NotDirectory
;
447 if(sb
.st_uid
!= forUid
) {
448 directStatus
= MDS_BadOwnerMode
;
451 if((sb
.st_mode
& 07777) != mode
) {
452 directStatus
= MDS_BadOwnerMode
;
459 * Create specified directory if it doesn't already exist. If there is something
460 * already there that doesn't meet spec (not a directory, wrong mode, wrong owner)
461 * we'll do everything we can do delete what is there and then try to create it
464 * Returns an errno on any unrecoverable error.
466 static int createDir(
468 uid_t forUid
, // for checking - we don't try to set this
471 MdsDirectStatus directStatus
;
473 if(doesDirectExist(dirPath
, forUid
, dirMode
, directStatus
)) {
479 * Attempt recovery if there is *something* there.
480 * Anything other than "not present" should be considered to be a possible
484 switch(directStatus
) {
486 /* normal trivial case: proceed. */
489 case MDS_NotDirectory
:
490 /* there's a file or symlink in the way */
491 if(unlink(dirPath
)) {
493 MSDebug("createDir(%s): unlink() returned %d", dirPath
, rtn
);
498 case MDS_BadOwnerMode
:
500 * It's a directory; try to clean it out (which may well fail if we're
503 rtn
= cleanDir(dirPath
, NULL
, 0);
509 MSDebug("createDir(%s): rmdir() returned %d", dirPath
, rtn
);
515 case MDS_Access
: /* hopeless */
516 MSDebug("createDir(%s): access failure, bailing", dirPath
);
519 rtn
= mkdir(dirPath
, dirMode
);
522 MSDebug("createDir(%s): mkdir() returned %d", dirPath
, errno
);
525 /* make sure umask does't trick us */
526 rtn
= chmod(dirPath
, dirMode
);
528 MSDebug("chmod(%s) returned %d", dirPath
, errno
);
535 * Create an MDS session.
537 MDSSession::MDSSession (const Guid
*inCallerGuid
,
538 const CSSM_MEMORY_FUNCS
&inMemoryFunctions
) :
539 DatabaseSession(MDSModule::get().databaseManager()),
540 mCssmMemoryFunctions (inMemoryFunctions
),
541 mModule(MDSModule::get())
543 MSDebug("MDSSession::MDSSession");
545 mCallerGuidPresent
= inCallerGuid
!= nil
;
546 if (mCallerGuidPresent
) {
547 mCallerGuid
= *inCallerGuid
;
551 MDSSession::~MDSSession ()
553 MSDebug("MDSSession::~MDSSession");
557 MDSSession::terminate ()
559 MSDebug("MDSSession::terminate");
563 const char* kExceptionDeletePath
= "messages";
567 * Called by security server via MDS_Install().
570 MDSSession::install ()
573 // Installation requires root
575 if(geteuid() != (uid_t
)0) {
576 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
580 // install() is only (legitimately) called from securityd.
581 // Mark "server mode" so we don't end up waiting for ourselves when the databases open.
583 mModule
.setServerMode();
587 /* ensure MDS base directory exists with correct permissions */
588 if(createDir(MDS_BASE_DB_DIR
, MDS_SYSTEM_UID
, MDS_BASE_DB_DIR_MODE
)) {
589 MSDebug("Error creating base MDS dir; aborting.");
590 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
593 /* ensure the the system MDS DB directory exists with correct permissions */
594 if(createDir(MDS_SYSTEM_DB_DIR
, MDS_SYSTEM_UID
, MDS_SYSTEM_DB_DIR_MODE
)) {
595 MSDebug("Error creating system MDS dir; aborting.");
596 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
599 if(!obtainLock(MDS_INSTALL_LOCK_PATH
, sysFdLock
, DB_LOCK_TIMEOUT
)) {
600 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
604 * We own the whole MDS system. Clean everything out except for our lock
605 * (and the directory it's in :-)
608 const char *savedFile
= MDS_INSTALL_LOCK_NAME
;
609 if(cleanDir(MDS_SYSTEM_DB_DIR
, &savedFile
, 1)) {
610 /* this should never happen - we're root */
611 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
614 const char *savedFiles
[] = {MDS_SYSTEM_DB_COMP
, kExceptionDeletePath
};
615 if(cleanDir(MDS_BASE_DB_DIR
, savedFiles
, 2)) {
616 /* this should never happen - we're root */
617 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
621 * Do initial population of system DBs.
623 createSystemDatabases(CSSM_FALSE
, MDS_SYSTEM_DB_MODE
);
624 DbFilesInfo
dbFiles(*this, MDS_SYSTEM_DB_DIR
);
625 dbFiles
.updateSystemDbInfo(MDS_SYSTEM_PATH
, MDS_BUNDLE_PATH
);
628 if(sysFdLock
!= -1) {
629 releaseLock(sysFdLock
);
633 releaseLock(sysFdLock
);
637 // In this implementation, the uninstall() call is not supported since
638 // we do not allow programmatic deletion of the MDS databases.
642 MDSSession::uninstall ()
644 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
648 * Common private open routine given a full specified path.
650 CSSM_DB_HANDLE
MDSSession::dbOpen(const char *dbName
, bool batched
)
652 static CSSM_APPLEDL_OPEN_PARAMETERS batchOpenParams
= {
653 sizeof(CSSM_APPLEDL_OPEN_PARAMETERS
),
654 CSSM_APPLEDL_OPEN_PARAMETERS_VERSION
,
655 CSSM_FALSE
, // do not auto-commit
656 0 // mask - do not use following fields
659 MSDebug("Opening %s%s", dbName
, batched
? " in batch mode" : "");
660 MSIoDbg("open %s in dbOpen(name, batched)", dbName
);
661 CSSM_DB_HANDLE dbHand
;
662 DatabaseSession::DbOpen(dbName
,
665 NULL
, // AccessCred - hopefully optional
666 batched
? &batchOpenParams
: NULL
,
671 /* DatabaseSession routines we need to override */
672 void MDSSession::DbOpen(const char *DbName
,
673 const CSSM_NET_ADDRESS
*DbLocation
,
674 CSSM_DB_ACCESS_TYPE AccessRequest
,
675 const AccessCredentials
*AccessCred
,
676 const void *OpenParameters
,
677 CSSM_DB_HANDLE
&DbHandle
)
679 if (!mModule
.serverMode()) {
681 * Make sure securityd has finished initializing (system) MDS data.
682 * Note that activate() only does IPC once and retains global state after that.
684 SecurityServer::ClientSession
client(Allocator::standard(), Allocator::standard());
685 client
.activate(); /* contact securityd - won't return until MDS is ready */
688 /* make sure DBs are up-to-date */
692 * Only task here is map incoming DbName - specified in the CDSA
693 * spec - to a filename we actually use (which is a path to either
694 * a system MDS DB file or a per-user MDS DB file).
697 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
700 if(!strcmp(DbName
, MDS_OBJECT_DIRECTORY_NAME
)) {
701 dbName
= MDS_OBJECT_DB_NAME
;
703 else if(!strcmp(DbName
, MDS_CDSA_DIRECTORY_NAME
)) {
704 dbName
= MDS_DIRECT_DB_NAME
;
707 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
709 char fullPath
[MAXPATHLEN
];
710 dbFullPath(dbName
, fullPath
);
711 MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName
);
712 DatabaseSession::DbOpen(fullPath
, DbLocation
, AccessRequest
, AccessCred
,
713 OpenParameters
, DbHandle
);
716 CSSM_HANDLE
MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle
,
717 const CssmQuery
*Query
,
718 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes
,
720 CSSM_DB_UNIQUE_RECORD_PTR
&UniqueId
)
723 return DatabaseSession::DataGetFirst(DBHandle
, Query
, Attributes
, Data
, UniqueId
);
728 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR
&outNameList
)
730 outNameList
= new CSSM_NAME_LIST
[1];
731 outNameList
->NumStrings
= 2;
732 outNameList
->String
= new char*[2];
733 outNameList
->String
[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME
);
734 outNameList
->String
[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME
);
738 MDSSession::FreeNameList(CSSM_NAME_LIST
&inNameList
)
740 delete [] inNameList
.String
[0];
741 delete [] inNameList
.String
[1];
742 delete [] inNameList
.String
;
745 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle
,
748 printf("GetDbNameFromHandle: code on demand\n");
749 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
753 // Attempt to obtain an exclusive lock over the the MDS databases. The
754 // parameter is the maximum amount of time, in milliseconds, to spend
755 // trying to obtain the lock. A value of zero means to return failure
756 // right away if the lock cannot be obtained.
759 MDSSession::obtainLock(
760 const char *lockFile
, // e.g. MDS_INSTALL_LOCK_PATH
762 int timeout
) // default 0
766 secdebug("mdslock", "obtainLock: calling open(%s)", lockFile
);
767 fd
= open(lockFile
, O_EXLOCK
| O_CREAT
| O_RDWR
, 0644);
770 secdebug("mdslock", "obtainLock: open error %d", errno
);
772 /* got a signal, go again */
776 /* theoretically should never happen */
781 secdebug("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.
796 MDSSession::releaseLock(int &fd
)
798 secdebug("mdslock", "releaseLock");
805 /* given DB file name, fill in fully specified path */
806 void MDSSession::dbFullPath(
808 char fullPath
[MAXPATHLEN
+1])
810 mModule
.getDbPath(fullPath
);
811 assert(fullPath
[0] != '\0');
812 strcat(fullPath
, "/");
813 strcat(fullPath
, dbName
);
817 * See if any per-user bundles exist in specified directory. Returns true if so.
818 * First the check for one entry....
820 static bool isBundle(
821 const struct dirent
*dp
)
826 /* NFS directories show up as DT_UNKNOWN */
834 int suffixLen
= strlen(MDS_BUNDLE_EXTEN
);
835 int len
= strlen(dp
->d_name
);
837 return (len
>= suffixLen
) &&
838 !strcmp(dp
->d_name
+ len
- suffixLen
, MDS_BUNDLE_EXTEN
);
841 /* now the full directory search */
842 static bool checkUserBundles(
843 const char *bundlePath
)
845 MSDebug("searching for user bundles in %s", bundlePath
);
846 DIR *dir
= opendir(bundlePath
);
852 while ((dp
= readdir(dir
)) != NULL
) {
854 /* any other checking to do? */
860 MSDebug("...%s bundle(s) found", rtn
? "" : "No");
864 #define COPY_BUF_SIZE 1024
867 * Single file copy with locking.
868 * Ensures that the source is a regular file with specified owner.
869 * Caller specifies mode of destination file.
870 * Throws a CssmError if the source file doesn't meet spec; throws a
871 * UnixError on any other error (which is generally recoverable by
872 * having the user MDS session use the system DB files).
874 static void safeCopyFile(
875 const char *fromPath
,
881 bool haveLock
= false;
885 char tmpToPath
[MAXPATHLEN
+1];
887 MSIoDbg("open %s, %s in safeCopyFile", fromPath
, toPath
);
889 if(!doesFileExist(fromPath
, fromUid
, false, sb
)) {
890 MSDebug("safeCopyFile: bad system DB file %s", fromPath
);
891 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
894 /* create temp destination */
895 snprintf(tmpToPath
, sizeof(tmpToPath
), "%s_", toPath
);
896 destFd
= open(tmpToPath
, O_WRONLY
| O_APPEND
| O_CREAT
| O_TRUNC
| O_EXCL
, toMode
);
899 MSDebug("Error %d opening user DB file %s\n", error
, tmpToPath
);
900 UnixError::throwMe(error
);
905 /* don't get tripped up by umask */
906 if(fchmod(destFd
, toMode
)) {
908 MSDebug("Error %d chmoding user DB file %s\n", error
, tmpToPath
);
909 UnixError::throwMe(error
);
912 /* open source for reading */
913 srcFd
= open(fromPath
, O_RDONLY
, 0);
916 MSDebug("Error %d opening system DB file %s\n", error
, fromPath
);
917 UnixError::throwMe(error
);
920 /* acquire the same kind of lock AtomicFile uses */
924 fl
.l_type
= F_RDLCK
; // AtomicFile gets F_WRLCK
925 fl
.l_whence
= SEEK_SET
;
927 // Keep trying to obtain the lock if we get interupted.
929 if (::fcntl(srcFd
, F_SETLKW
, &fl
) == -1) {
931 if (error
== EINTR
) {
935 MSDebug("Error %d locking system DB file %s\n", error
, fromPath
);
936 UnixError::throwMe(error
);
945 char buf
[COPY_BUF_SIZE
];
947 int bytesRead
= read(srcFd
, buf
, COPY_BUF_SIZE
);
953 MSDebug("Error %d reading system DB file %s\n", error
, fromPath
);
954 UnixError::throwMe(error
);
956 int bytesWritten
= write(destFd
, buf
, bytesRead
);
957 if(bytesWritten
< 0) {
959 MSDebug("Error %d writing user DB file %s\n", error
, tmpToPath
);
960 UnixError::throwMe(error
);
965 /* error is nonzero, we'll re-throw below...still have some cleanup */
968 /* unlock source and close both */
971 if (::fcntl(srcFd
, F_SETLK
, &fl
) == -1) {
972 MSDebug("Error %d unlocking system DB file %s\n", errno
, fromPath
);
975 MSIoDbg("close %s, %s in safeCopyFile", fromPath
, tmpToPath
);
983 /* commit temp file */
984 if(::rename(tmpToPath
, toPath
)) {
986 MSDebug("Error %d committing %s\n", error
, toPath
);
990 UnixError::throwMe(error
);
995 * Copy system DB files to specified user dir. Caller holds user DB lock.
996 * Throws a UnixError on error.
998 static void copySystemDbs(
999 const char *userDbFileDir
)
1001 char toPath
[MAXPATHLEN
+1];
1003 snprintf(toPath
, sizeof(toPath
), "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
);
1004 safeCopyFile(MDS_OBJECT_DB_PATH
, MDS_SYSTEM_UID
, toPath
, MDS_USER_DB_MODE
);
1005 snprintf(toPath
, sizeof(toPath
), "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
);
1006 safeCopyFile(MDS_DIRECT_DB_PATH
, MDS_SYSTEM_UID
, toPath
, MDS_USER_DB_MODE
);
1010 * Ensure current DB files exist and are up-to-date.
1011 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
1012 * other public functions which access a DB from scratch.
1014 void MDSSession::updateDataBases()
1016 RecursionBlock::Once
once(mUpdating
);
1018 return; // already updating; don't recurse
1020 uid_t ourUid
= geteuid();
1021 bool isRoot
= (ourUid
== 0);
1023 /* if we scanned recently, we're done */
1024 double delta
= mModule
.timeSinceLastScan();
1025 if(delta
< (double)MDS_SCAN_INTERVAL
) {
1030 * If we're root, the first thing we do is to ensure that system DBs are present.
1031 * Note that this is a necessary artifact of the problem behind Radar 3800811.
1032 * When that is fixed, install() should ONLY be called from the public MDS_Install()
1034 * Anyway, if we *do* have to install here, we're done.
1036 if(isRoot
&& !systemDatabasesPresent(false)) {
1038 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
);
1039 mModule
.lastScanIsNow();
1044 * Obtain various per-user paths. Root is a special case but follows most
1045 * of the same logic from here on.
1047 std::string userDBFileDir
= GetMDSDBDir();
1048 std::string userObjDBFilePath
= GetMDSObjectDBPath();
1049 std::string userDirectDBFilePath
= GetMDSDirectDBPath();
1050 char userBundlePath
[MAXPATHLEN
+1];
1051 std::string userDbLockPath
= GetMDSDBLockPath();
1053 /* this means "no user bundles" */
1054 userBundlePath
[0] = '\0';
1056 char *userHome
= getenv("HOME");
1057 if((userHome
== NULL
) ||
1058 (strlen(userHome
) + strlen(MDS_USER_BUNDLE
) + 2) > sizeof(userBundlePath
)) {
1059 /* Can't check for user bundles */
1060 MSDebug("missing or invalid HOME; skipping user bundle check");
1062 /* TBD: any other checking of userHome? */
1064 snprintf(userBundlePath
, sizeof(userBundlePath
),
1065 "%s/%s", userHome
, MDS_USER_BUNDLE
);
1070 * Create the per-user directory...that's where the lock we'll be using lives.
1073 if(createDir(userDBFileDir
.c_str(), ourUid
, MDS_USER_DB_DIR_MODE
)) {
1075 * We'll just have to limp along using the read-only system DBs.
1076 * Note that this protects (somewhat) against the DoS attack in
1077 * Radar 3801292. The only problem is that this user won't be able
1078 * to use per-user bundles.
1080 MSDebug("Error creating user DBs; using system DBs");
1081 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
);
1086 /* always release userLockFd no matter what happens */
1087 int userLockFd
= -1;
1088 if(!obtainLock(userDbLockPath
.c_str(), userLockFd
, DB_LOCK_TIMEOUT
)) {
1089 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
1095 * We copy the system DBs to the per-user DBs in two cases:
1096 * -- user DBs don't exist, or
1097 * -- system DBs have changed since the the last update to the user DBs.
1098 * This happens on smart card insertion and removal.
1100 bool doCopySystem
= false;
1101 struct stat userObjStat
, userDirectStat
;
1102 if(!doFilesExist(userObjDBFilePath
.c_str(), userDirectDBFilePath
.c_str(), ourUid
, true,
1103 userObjStat
, userDirectStat
)) {
1104 doCopySystem
= true;
1107 /* compare the two mdsDirectory.db files */
1108 MSIoDbg("stat %s, %s in updateDataBases",
1109 MDS_DIRECT_DB_PATH
, userDirectDBFilePath
.c_str());
1110 struct stat sysStat
;
1111 if (!stat(MDS_DIRECT_DB_PATH
, &sysStat
)) {
1112 doCopySystem
= (sysStat
.st_mtimespec
.tv_sec
> userDirectStat
.st_mtimespec
.tv_sec
) ||
1113 ((sysStat
.st_mtimespec
.tv_sec
== userDirectStat
.st_mtimespec
.tv_sec
) &&
1114 (sysStat
.st_mtimespec
.tv_nsec
> userDirectStat
.st_mtimespec
.tv_nsec
));
1116 MSDebug("user DB files obsolete at %s", userDBFileDir
.c_str());
1121 /* copy system DBs to user DBs */
1122 MSDebug("copying system DBs to user at %s", userDBFileDir
.c_str());
1123 copySystemDbs(userDBFileDir
.c_str());
1126 MSDebug("Using existing user DBs at %s", userDBFileDir
.c_str());
1129 catch(const CssmError
&cerror
) {
1131 * Bad system DB file detected. Fatal.
1137 * Error on delete or create user DBs; fall back on system DBs.
1139 MSDebug("doFilesExist(purge) error; using system DBs");
1140 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
);
1141 releaseLock(userLockFd
);
1146 MSDebug("Using system DBs only");
1150 * Update per-user DBs from both bundle sources (System bundles, user bundles)
1153 DbFilesInfo
dbFiles(*this, userDBFileDir
.c_str());
1154 dbFiles
.removeOutdatedPlugins();
1155 dbFiles
.updateSystemDbInfo(NULL
, MDS_BUNDLE_PATH
);
1156 if(userBundlePath
[0]) {
1157 /* skip for invalid or missing $HOME... */
1158 if(checkUserBundles(userBundlePath
)) {
1159 dbFiles
.updateForBundleDir(userBundlePath
);
1162 mModule
.setDbPath(userDBFileDir
.c_str());
1163 } /* main block protected by mLockFd */
1165 releaseLock(userLockFd
);
1168 mModule
.lastScanIsNow();
1169 releaseLock(userLockFd
);
1173 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
1175 void MDSSession::removeRecordsForGuid(
1177 CSSM_DB_HANDLE dbHand
)
1179 // tell the DB to flush its intermediate data to disk
1180 PassThrough(dbHand
, CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
);
1181 CssmClient::Query query
= Attribute("ModuleID") == guid
;
1182 clearRecords(dbHand
, query
.cssmQuery());
1186 void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand
, const CssmQuery
&query
)
1188 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1189 CSSM_HANDLE resultHand
= DataGetFirst(dbHand
,
1194 if (resultHand
== CSSM_INVALID_HANDLE
)
1195 return; // no matches
1198 DataDelete(dbHand
, *record
);
1199 FreeUniqueRecord(dbHand
, *record
);
1201 } while (DataGetNext(dbHand
,
1208 FreeUniqueRecord(dbHand
, *record
);
1209 DataAbortQuery(dbHand
, resultHand
);
1215 * Determine if system databases are present.
1216 * If the purge argument is true, we'll ensure that either both or neither
1217 * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH.
1219 bool MDSSession::systemDatabasesPresent(bool purge
)
1225 * This can throw on a failed attempt to delete sole existing file....
1226 * But if that happens while we're root, our goose is fully cooked.
1228 struct stat objDbSb
, directDbSb
;
1229 if(doFilesExist(MDS_OBJECT_DB_PATH
, MDS_DIRECT_DB_PATH
,
1230 MDS_SYSTEM_UID
, purge
, objDbSb
, directDbSb
)) {
1241 * Given a DB name (which is used as an absolute path) and an array of
1242 * RelationInfos, create a DB.
1245 MDSSession::createSystemDatabase(
1247 const RelationInfo
*relationInfo
,
1248 unsigned numRelations
,
1249 CSSM_BOOL autoCommit
,
1251 CSSM_DB_HANDLE
&dbHand
) // RETURNED
1254 CSSM_DBINFO_PTR dbInfoP
= &dbInfo
;
1256 memset(dbInfoP
, 0, sizeof(CSSM_DBINFO
));
1257 dbInfoP
->NumberOfRecordTypes
= numRelations
;
1258 dbInfoP
->IsLocal
= CSSM_TRUE
; // TBD - what does this mean?
1259 dbInfoP
->AccessPath
= NULL
; // TBD
1261 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
1263 unsigned size
= sizeof(CSSM_DB_PARSING_MODULE_INFO
) * numRelations
;
1264 dbInfoP
->DefaultParsingModules
= (CSSM_DB_PARSING_MODULE_INFO_PTR
)malloc(size
);
1265 memset(dbInfoP
->DefaultParsingModules
, 0, size
);
1266 size
= sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO
) * numRelations
;
1267 dbInfoP
->RecordAttributeNames
= (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR
)malloc(size
);
1268 memset(dbInfoP
->RecordAttributeNames
, 0, size
);
1269 size
= sizeof(CSSM_DB_RECORD_INDEX_INFO
) * numRelations
;
1270 dbInfoP
->RecordIndexes
= (CSSM_DB_RECORD_INDEX_INFO_PTR
)malloc(size
);
1271 memset(dbInfoP
->RecordIndexes
, 0, size
);
1273 /* cook up attribute and index info for each relation */
1275 for(relation
=0; relation
<numRelations
; relation
++) {
1276 const struct RelationInfo
*relp
= &relationInfo
[relation
]; // source
1277 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo
=
1278 &dbInfoP
->RecordAttributeNames
[relation
]; // dest 1
1279 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo
=
1280 &dbInfoP
->RecordIndexes
[relation
]; // dest 2
1282 attrInfo
->DataRecordType
= relp
->DataRecordType
;
1283 attrInfo
->NumberOfAttributes
= relp
->NumberOfAttributes
;
1284 attrInfo
->AttributeInfo
= (CSSM_DB_ATTRIBUTE_INFO_PTR
)relp
->AttributeInfo
;
1286 indexInfo
->DataRecordType
= relp
->DataRecordType
;
1287 indexInfo
->NumberOfIndexes
= relp
->NumberOfIndexes
;
1288 indexInfo
->IndexInfo
= (CSSM_DB_INDEX_INFO_PTR
)relp
->IndexInfo
;
1291 /* set autocommit and mode */
1292 CSSM_APPLEDL_OPEN_PARAMETERS openParams
;
1293 memset(&openParams
, 0, sizeof(openParams
));
1294 openParams
.length
= sizeof(openParams
);
1295 openParams
.version
= CSSM_APPLEDL_OPEN_PARAMETERS_VERSION
;
1296 openParams
.autoCommit
= autoCommit
;
1297 openParams
.mask
= kCSSM_APPLEDL_MASK_MODE
;
1298 openParams
.mode
= mode
;
1304 CSSM_DB_ACCESS_READ
| CSSM_DB_ACCESS_WRITE
,
1305 NULL
, // CredAndAclEntry
1310 MSDebug("Error on DbCreate");
1311 free(dbInfoP
->DefaultParsingModules
);
1312 free(dbInfoP
->RecordAttributeNames
);
1313 free(dbInfoP
->RecordIndexes
);
1316 free(dbInfoP
->DefaultParsingModules
);
1317 free(dbInfoP
->RecordAttributeNames
);
1318 free(dbInfoP
->RecordIndexes
);
1323 * Create system databases from scratch if they do not already exist.
1324 * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
1325 * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH).
1326 * Returns true if we actually built the files, false if they already
1329 bool MDSSession::createSystemDatabases(
1330 CSSM_BOOL autoCommit
,
1333 CSSM_DB_HANDLE objectDbHand
= 0;
1334 CSSM_DB_HANDLE directoryDbHand
= 0;
1336 assert(geteuid() == (uid_t
)0);
1337 if(systemDatabasesPresent(true)) {
1338 /* both databases exist as regular files with correct owner - we're done */
1339 MSDebug("system DBs already exist");
1343 /* create two DBs - any exception here results in deleting both of them */
1344 MSDebug("Creating MDS DBs");
1346 createSystemDatabase(MDS_OBJECT_DB_PATH
, &kObjectRelation
, 1,
1347 autoCommit
, mode
, objectDbHand
);
1348 MSIoDbg("close objectDbHand in createSystemDatabases");
1349 DbClose(objectDbHand
);
1351 createSystemDatabase(MDS_DIRECT_DB_PATH
, kMDSRelationInfo
, kNumMdsRelations
,
1352 autoCommit
, mode
, directoryDbHand
);
1353 MSIoDbg("close directoryDbHand in createSystemDatabases");
1354 DbClose(directoryDbHand
);
1355 directoryDbHand
= 0;
1358 MSDebug("Error creating MDS DBs - deleting both DB files");
1359 unlink(MDS_OBJECT_DB_PATH
);
1360 unlink(MDS_DIRECT_DB_PATH
);
1367 * DbFilesInfo helper class
1370 /* Note both DB files MUST exist at construction time */
1371 MDSSession::DbFilesInfo::DbFilesInfo(
1372 MDSSession
&session
,
1373 const char *dbPath
) :
1379 assert(strlen(dbPath
) < MAXPATHLEN
);
1380 strcpy(mDbPath
, dbPath
);
1382 /* stat the two DB files, snag the later timestamp */
1383 char path
[MAXPATHLEN
];
1384 sprintf(path
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
1386 MSIoDbg("stat %s in DbFilesInfo()", path
);
1387 int rtn
= ::stat(path
, &sb
);
1390 MSDebug("Error %d statting DB file %s", error
, path
);
1391 UnixError::throwMe(error
);
1393 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1394 sprintf(path
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1395 MSIoDbg("stat %s in DbFilesInfo()", path
);
1396 rtn
= ::stat(path
, &sb
);
1399 MSDebug("Error %d statting DB file %s", error
, path
);
1400 UnixError::throwMe(error
);
1402 if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1403 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1407 MDSSession::DbFilesInfo::~DbFilesInfo()
1409 if(mObjDbHand
!= 0) {
1410 /* autocommit on, henceforth */
1411 mSession
.PassThrough(mObjDbHand
,
1412 CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
);
1413 MSIoDbg("close objectDbHand in ~DbFilesInfo()");
1414 mSession
.DbClose(mObjDbHand
);
1417 if(mDirectDbHand
!= 0) {
1418 mSession
.PassThrough(mDirectDbHand
,
1419 CSSM_APPLEFILEDL_COMMIT
, NULL
, NULL
);
1420 MSIoDbg("close mDirectDbHand in ~DbFilesInfo()");
1421 mSession
.DbClose(mDirectDbHand
);
1426 /* lazy evaluation of both DB handlesÊ*/
1427 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::objDbHand()
1429 if(mObjDbHand
!= 0) {
1432 char fullPath
[MAXPATHLEN
+ 1];
1433 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
1434 MSIoDbg("open %s in objDbHand()", fullPath
);
1435 mObjDbHand
= mSession
.dbOpen(fullPath
, true); // batch mode
1439 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::directDbHand()
1441 if(mDirectDbHand
!= 0) {
1442 return mDirectDbHand
;
1444 char fullPath
[MAXPATHLEN
+ 1];
1445 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1446 MSIoDbg("open %s in directDbHand()", fullPath
);
1447 mDirectDbHand
= mSession
.dbOpen(fullPath
, true); // batch mode
1448 return mDirectDbHand
;
1452 * Update the info for Security.framework and the system bundles.
1454 void MDSSession::DbFilesInfo::updateSystemDbInfo(
1455 const char *systemPath
, // e.g., /System/Library/Frameworks
1456 const char *bundlePath
) // e.g., /System/Library/Security
1459 * Security.framework - CSSM and built-in modules - only for initial population of
1464 if (CFRef
<CFBundleRef
> me
= CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")))
1465 if (CFRef
<CFURLRef
> url
= CFBundleCopyBundleURL(me
))
1466 if (CFRef
<CFStringRef
> cfpath
= CFURLCopyFileSystemPath(url
, kCFURLPOSIXPathStyle
))
1467 path
= cfString(cfpath
); // path to my bundle
1469 if (path
.empty()) // use system default
1470 path
= string(systemPath
) + "/" MDS_SYSTEM_FRAME
;
1471 updateForBundle(path
.c_str());
1474 /* Standard loadable bundles */
1475 updateForBundleDir(bundlePath
);
1479 MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1480 const CSSM_DATA
&guid
)
1482 assert(guid
.Length
<= MAX_GUID_LEN
);
1483 assert(guid
.Length
!= 0);
1484 memmove(mGuid
, guid
.Data
, guid
.Length
);
1485 if(mGuid
[guid
.Length
- 1] != '\0') {
1486 mGuid
[guid
.Length
] = '\0';
1491 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1492 * If so, add to tbdVector.
1494 void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1495 const CSSM_DATA
&pathValue
,
1496 const CSSM_DATA
&guidValue
,
1497 TbdVector
&tbdVector
)
1499 /* stat the specified plugin */
1501 bool obsolete
= false;
1502 string path
= CssmData::overlay(pathValue
).toString();
1503 if (!path
.empty() && path
[0] == '*') {
1504 /* builtin pseudo-path; never obsolete this */
1507 MSIoDbg("stat %s in checkOutdatedPlugin()", path
.c_str());
1508 int rtn
= ::stat(path
.c_str(), &sb
);
1510 /* not there or inaccessible; delete */
1513 else if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1514 /* timestamp of plugin's main directory later than that of DBs */
1518 TbdRecord
*tbdRecord
= new TbdRecord(guidValue
);
1519 tbdVector
.push_back(tbdRecord
);
1520 MSDebug("checkOutdatedPlugin: flagging %s obsolete", path
.c_str());
1525 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1526 * i.e., with any path) which are either not present on disk, or which
1527 * have changed since dbFiles.laterTimestamp().
1529 void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1532 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1533 CSSM_HANDLE resultHand
;
1534 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1535 CSSM_DB_ATTRIBUTE_DATA theAttrs
[2];
1536 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
;
1537 TbdVector tbdRecords
;
1540 * First, scan object directory. All we need are the path and GUID attributes.
1542 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1543 recordAttrs
.SemanticInformation
= 0;
1544 recordAttrs
.NumberOfAttributes
= 2;
1545 recordAttrs
.AttributeData
= theAttrs
;
1547 attrInfo
= &theAttrs
[0].Info
;
1548 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1549 attrInfo
->Label
.AttributeName
= (char*) "ModuleID";
1550 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1551 theAttrs
[0].NumberOfValues
= 0;
1552 theAttrs
[0].Value
= NULL
;
1553 attrInfo
= &theAttrs
[1].Info
;
1554 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1555 attrInfo
->Label
.AttributeName
= (char*) "Path";
1556 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1557 theAttrs
[1].NumberOfValues
= 0;
1558 theAttrs
[1].Value
= NULL
;
1560 /* just search by recordType, no predicates */
1561 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1562 query
.Conjunctive
= CSSM_DB_NONE
;
1563 query
.NumSelectionPredicates
= 0;
1564 query
.SelectionPredicate
= NULL
;
1565 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1566 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1567 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1569 CssmQuery
perryQuery(query
);
1571 resultHand
= mSession
.DataGetFirst(objDbHand(),
1578 MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1582 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1585 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1586 checkOutdatedPlugin(*theAttrs
[1].Value
, *theAttrs
[0].Value
,
1590 MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1592 for(unsigned dex
=0; dex
<2; dex
++) {
1593 CSSM_DB_ATTRIBUTE_DATA
*attr
= &theAttrs
[dex
];
1594 for (unsigned attrDex
=0; attrDex
<attr
->NumberOfValues
; attrDex
++) {
1595 if(attr
->Value
[attrDex
].Data
) {
1596 mSession
.free(attr
->Value
[attrDex
].Data
);
1600 mSession
.free(attr
->Value
);
1605 /* empty Object DB - we're done */
1606 MSDebug("removeOutdatedPlugins: empty object DB");
1610 /* now the rest of the object DB records */
1612 bool brtn
= mSession
.DataGetNext(objDbHand(),
1622 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1624 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1625 checkOutdatedPlugin(*theAttrs
[1].Value
,
1630 MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1632 for(unsigned dex
=0; dex
<2; dex
++) {
1633 CSSM_DB_ATTRIBUTE_DATA
*attr
= &theAttrs
[dex
];
1634 for (unsigned attrDex
=0; attrDex
<attr
->NumberOfValues
; attrDex
++) {
1635 if(attr
->Value
[attrDex
].Data
) {
1636 mSession
.free(attr
->Value
[attrDex
].Data
);
1640 mSession
.free(attr
->Value
);
1644 /* no DataAbortQuery needed; we scanned until completion */
1647 * We have a vector of plugins to be deleted. Remove all records from both
1648 * DBs associated with the plugins, as specified by guid.
1650 unsigned numRecords
= tbdRecords
.size();
1651 for(unsigned i
=0; i
<numRecords
; i
++) {
1652 TbdRecord
*tbdRecord
= tbdRecords
[i
];
1653 mSession
.removeRecordsForGuid(tbdRecord
->guid(), objDbHand());
1654 mSession
.removeRecordsForGuid(tbdRecord
->guid(), directDbHand());
1656 for(unsigned i
=0; i
<numRecords
; i
++) {
1657 delete tbdRecords
[i
];
1663 * Update DBs for all bundles in specified directory.
1665 void MDSSession::DbFilesInfo::updateForBundleDir(
1666 const char *bundleDirPath
)
1668 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1669 * much too heavyweight */
1670 MSDebug("...updating DBs for dir %s", bundleDirPath
);
1671 DIR *dir
= opendir(bundleDirPath
);
1673 MSDebug("updateForBundleDir: error %d opening %s", errno
, bundleDirPath
);
1677 char fullPath
[MAXPATHLEN
];
1678 while ((dp
= readdir(dir
)) != NULL
) {
1680 sprintf(fullPath
, "%s/%s", bundleDirPath
, dp
->d_name
);
1681 updateForBundle(fullPath
);
1688 * lookup by path - just returns true if there is a record assoociated with the path
1691 bool MDSSession::DbFilesInfo::lookupForPath(
1695 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1696 CSSM_HANDLE resultHand
= 0;
1697 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1698 CSSM_DB_ATTRIBUTE_DATA theAttr
;
1699 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
= &theAttr
.Info
;
1700 CSSM_SELECTION_PREDICATE predicate
;
1703 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1704 recordAttrs
.SemanticInformation
= 0;
1705 recordAttrs
.NumberOfAttributes
= 1;
1706 recordAttrs
.AttributeData
= &theAttr
;
1708 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1709 attrInfo
->Label
.AttributeName
= (char*) "Path";
1710 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1712 theAttr
.NumberOfValues
= 0;
1713 theAttr
.Value
= NULL
;
1715 predicate
.DbOperator
= CSSM_DB_EQUAL
;
1716 predicate
.Attribute
.Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1717 predicate
.Attribute
.Info
.Label
.AttributeName
= (char*) "Path";
1718 predicate
.Attribute
.Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1719 predData
.Data
= (uint8
*)path
;
1720 predData
.Length
= strlen(path
);
1721 predicate
.Attribute
.Value
= &predData
;
1722 predicate
.Attribute
.NumberOfValues
= 1;
1724 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1725 query
.Conjunctive
= CSSM_DB_NONE
;
1726 query
.NumSelectionPredicates
= 1;
1727 query
.SelectionPredicate
= &predicate
;
1728 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1729 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1730 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1734 CssmQuery
perryQuery(query
);
1735 resultHand
= mSession
.DataGetFirst(objDbHand(),
1745 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1750 if(resultHand
&& ourRtn
) {
1751 /* more resulting pending; terminate the search */
1753 mSession
.DataAbortQuery(mObjDbHand
, resultHand
);
1756 MSDebug("exception on DataAbortQuery in lookupForPath");
1759 for(unsigned dex
=0; dex
<theAttr
.NumberOfValues
; dex
++) {
1760 if(theAttr
.Value
[dex
].Data
) {
1761 mSession
.free(theAttr
.Value
[dex
].Data
);
1764 mSession
.free(theAttr
.Value
);
1768 /* update entry for one bundle, which is known to exist */
1769 void MDSSession::DbFilesInfo::updateForBundle(
1770 const char *bundlePath
)
1772 MSDebug("...updating DBs for bundle %s", bundlePath
);
1774 /* Quick lookup - do we have ANY entry for a bundle with this path? */
1775 if(lookupForPath(bundlePath
)) {
1776 /* Yep, we're done */
1779 MDSAttrParser
parser(bundlePath
,
1784 parser
.parseAttrs();
1786 catch (const CssmError
&err
) {
1787 // a corrupt MDS info file invalidates the entire plugin
1788 const char *guid
= parser
.guid();
1790 mSession
.removeRecordsForGuid(guid
, objDbHand());
1791 mSession
.removeRecordsForGuid(guid
, directDbHand());
1798 // Private API: add MDS records from contents of file
1799 // These files are typically written by securityd and handed to us in this call.
1801 void MDSSession::installFile(const MDS_InstallDefaults
*defaults
,
1802 const char *inBundlePath
, const char *subdir
, const char *file
)
1804 string bundlePath
= inBundlePath
? inBundlePath
: cfString(CFBundleGetMainBundle());
1805 DbFilesInfo
dbFiles(*this, MDS_SYSTEM_DB_DIR
);
1806 MDSAttrParser
parser(bundlePath
.c_str(),
1808 dbFiles
.objDbHand(),
1809 dbFiles
.directDbHand());
1810 parser
.setDefaults(defaults
);
1813 if (file
== NULL
) // parse a directory
1814 if (subdir
) // a particular directory
1815 parser
.parseAttrs(CFTempString(subdir
));
1816 else // all resources in bundle
1817 parser
.parseAttrs(NULL
);
1818 else // parse just one file
1819 parser
.parseFile(CFRef
<CFURLRef
>(makeCFURL(file
)), CFTempString(subdir
));
1821 catch (const CssmError
&err
) {
1822 const char *guid
= parser
.guid();
1824 removeRecordsForGuid(guid
, dbFiles
.objDbHand());
1825 removeRecordsForGuid(guid
, dbFiles
.directDbHand());
1832 // Private API: Remove all records for a guid/subservice
1834 // Note: Multicursors searching for SSID fail because not all records in the
1835 // database have this attribute. So we have to explicitly run through all tables
1838 void MDSSession::removeSubservice(const char *guid
, uint32 ssid
)
1840 DbFilesInfo
dbFiles(*this, MDS_SYSTEM_DB_DIR
);
1842 CssmClient::Query query
=
1843 Attribute("ModuleID") == guid
&&
1844 Attribute("SSID") == ssid
;
1846 // only CSP and DL tables are cleared here
1847 // (this function is private to securityd, which only handles those types)
1848 clearRecords(dbFiles
.directDbHand(),
1849 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE
));
1850 clearRecords(dbFiles
.directDbHand(),
1851 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE
));
1852 clearRecords(dbFiles
.directDbHand(),
1853 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE
));
1854 clearRecords(dbFiles
.directDbHand(),
1855 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE
));
1856 clearRecords(dbFiles
.directDbHand(),
1857 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE
));
1858 clearRecords(dbFiles
.directDbHand(),
1859 CssmQuery(query
.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE
));
1863 } // end namespace Security