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/DbContext.h>
22 #include "MDSModule.h"
23 #include "MDSAttrParser.h"
24 #include "MDSAttrUtils.h"
27 #include <Security/cssmerr.h>
28 #include <Security/utilities.h>
29 #include <Security/logging.h>
30 #include <Security/debugging.h>
31 #include <Security/mds_schema.h>
33 #include <sys/types.h>
34 #include <sys/param.h>
41 * The layout of the various MDS DB files on disk is as follows:
43 * /var/tmp/mds -- owner = root, mode = 01777, world writable, sticky
44 * mdsObject.db -- owner = root, mode = 0644, object DB
45 * mdsDirectory.db -- owner = root, mode = 0644, MDS directory DB
46 * mds.lock -- temporary, owner = root, protects creation of
48 * <uid>/ -- owner = <uid>, mode = 0644
49 * mdsObject.db -- owner = <uid>, mode = 0644, object DB
50 * mdsDirectory.db -- owner = <uid>, mode = 0644, MDS directory DB
51 * mds.lock -- temporary, owner = <uid>, protects creation of
54 * The /var/tmp/mds directory and the two db files in it are created by root
55 * via SS or an AEWP call. Each user except for root has their own private
56 * directory with two DB files and a lock. The first time a user accesses MDS,
57 * the per-user directory is created and the per-user DB files are created as
58 * copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system
59 * DB files when they are the source of these copies; this is the same mechanism
60 * used by the underlying AtomincFile.
62 * The sticky bit in /var/tmp/mds ensures that users cannot delete, rename, and/or
63 * replace the root-owned DB files in that directory, and that users can not
64 * modify other user's private MDS directories.
70 * Nominal location of Security.framework.
72 #define MDS_SYSTEM_PATH "/System/Library/Frameworks"
73 #define MDS_SYSTEM_FRAME "Security.framework"
76 * Nominal location of standard plugins.
78 #define MDS_BUNDLE_PATH "/System/Library/Security"
79 #define MDS_BUNDLE_EXTEN ".bundle"
83 * Location of system MDS database and lock files.
85 #define MDS_SYSTEM_DB_DIR "/private/var/tmp/mds"
86 #define MDS_LOCK_FILE_NAME "mds.lock"
87 #define MDS_OBJECT_DB_NAME "mdsObject.db"
88 #define MDS_DIRECT_DB_NAME "mdsDirectory.db"
89 #define MDS_LOCK_FILE_PATH MDS_SYSTEM_DB_DIR "/" MDS_LOCK_FILE_NAME
90 #define MDS_OBJECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_OBJECT_DB_NAME
91 #define MDS_DIRECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_DIRECT_DB_NAME
94 * Location of per-user bundles, relative to home directory.
95 * PEr-user DB files are in MDS_SYSTEM_DB_DIR/<uid>/.
97 #define MDS_USER_DB_DIR "Library/Security"
98 #define MDS_USER_BUNDLE "Library/Security"
100 /* time to wait in ms trying to acquire lock */
101 #define DB_LOCK_TIMEOUT (2 * 1000)
103 /* Minimum interval, in seconds, between rescans for plugin changes */
104 #define MDS_SCAN_INTERVAL 10
106 /* initial debug - start from scratch each time */
107 #define START_FROM_SCRATCH 0
109 /* debug - skip file-level locking */
110 #define SKIP_FILE_LOCKING 0
112 /* Only allow root to create and update system DB files - in the final config this
114 #define SYSTEM_MDS_ROOT_ONLY 0
117 * Early development; no Security Server/root involvement with system DB creation.
118 * If this is true, SYSTEM_MDS_ROOT_ONLY must be false (though both can be
119 * false for intermediate testing).
121 #define SYSTEM_DBS_VIA_USER 1
123 /* when true, turn autocommit off when building system DB */
124 #define AUTO_COMMIT_OPT 1
128 * Determine if both of the specified DB files exist as
129 * accessible regular files. Returns true if they do. If the purge argument
130 * is true, we'll ensure that either both or neither of the files exist on
133 static bool doFilesExist(
134 const char *objDbFile
,
135 const char *directDbFile
,
136 bool purge
) // false means "passive" check
139 bool objectExist
= false;
140 bool directExist
= false;
142 if (stat(objDbFile
, &sb
) == 0) {
143 /* Object DB exists */
144 if(!(sb
.st_mode
& S_IFREG
)) {
145 MSDebug("deleting non-regular file %s", objDbFile
);
146 if(purge
&& unlink(objDbFile
)) {
147 MSDebug("unlink(%s) returned %d", objDbFile
, errno
);
148 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
155 if (stat(directDbFile
, &sb
) == 0) {
156 /* directory DB exists */
157 if(!(sb
.st_mode
& S_IFREG
)) {
158 MSDebug("deleting non-regular file %s", directDbFile
);
159 if(purge
& unlink(directDbFile
)) {
160 MSDebug("unlink(%s) returned %d", directDbFile
, errno
);
161 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
166 if(objectExist
&& directExist
) {
167 /* both databases exist as regular files */
174 /* at least one does not exist - ensure neither of them do */
176 if(unlink(objDbFile
)) {
177 MSDebug("unlink(%s) returned %d", objDbFile
, errno
);
178 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
182 if(unlink(directDbFile
)) {
183 MSDebug("unlink(%s) returned %d", directDbFile
, errno
);
184 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
191 * Determine if specified directory exists.
193 static bool doesDirectExist(
198 if (stat(dirPath
, &sb
)) {
201 if(!(sb
.st_mode
& S_IFDIR
)) {
208 * Create specified directory if it doesn't already exist.
209 * Zero for mode means "use the default provided by 0755 modified by umask".
211 static int createDir(
215 if(doesDirectExist(dirPath
)) {
218 int rtn
= mkdir(dirPath
, 0755);
220 if(errno
== EEXIST
) {
226 MSDebug("mkdir(%s) returned %d", dirPath
, errno
);
229 if((rtn
== 0) && (dirMode
!= 0)) {
230 rtn
= chmod(dirPath
, dirMode
);
232 MSDebug("chmod(%s) returned %d", dirPath
, errno
);
239 * Create an MDS session.
241 MDSSession::MDSSession (const Guid
*inCallerGuid
,
242 const CSSM_MEMORY_FUNCS
&inMemoryFunctions
) :
243 DatabaseSession(MDSModule::get().databaseManager()),
244 mCssmMemoryFunctions (inMemoryFunctions
),
245 mModule(MDSModule::get()),
248 MSDebug("MDSSession::MDSSession");
250 #if START_FROM_SCRATCH
251 unlink(MDS_LOCK_FILE_PATH
);
252 unlink(MDS_OBJECT_DB_PATH
);
253 unlink(MDS_DIRECT_DB_PATH
);
256 mCallerGuidPresent
= inCallerGuid
!= nil
;
257 if (mCallerGuidPresent
)
258 mCallerGuid
= *inCallerGuid
;
261 * Create DB files if necessary; make sure they are up-to-date
263 // no! done in either install or open! updateDataBases();
266 MDSSession::~MDSSession ()
268 MSDebug("MDSSession::~MDSSession");
269 releaseLock(mLockFd
);
273 MDSSession::terminate ()
275 MSDebug("MDSSession::terminate");
276 releaseLock(mLockFd
);
281 * Called by security server or AEWP-executed privileged tool.
284 MDSSession::install ()
286 if((getuid() != (uid_t
)0) && SYSTEM_MDS_ROOT_ONLY
) {
287 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
292 /* before we obtain the lock, ensure the the system MDS DB directory exists */
293 if(createDir(MDS_SYSTEM_DB_DIR
, 01777)) {
294 MSDebug("Error creating system MDS dir; aborting.");
295 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
298 if(!obtainLock(MDS_LOCK_FILE_PATH
, sysFdLock
, DB_LOCK_TIMEOUT
)) {
299 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
301 if(!systemDatabasesPresent(true)) {
302 bool created
= createSystemDatabases();
305 * Skip possible race condition in which this is called twice,
306 * both via SS by user procs who say "no system DBs present"
307 * in their updateDataBases() method.
309 * Do initial population of system DBs.
311 DbFilesInfo
dbFiles(*this, MDS_SYSTEM_DB_DIR
);
313 dbFiles
.autoCommit(CSSM_FALSE
);
315 dbFiles
.updateSystemDbInfo(MDS_SYSTEM_PATH
, MDS_BUNDLE_PATH
);
320 if(sysFdLock
!= -1) {
321 releaseLock(sysFdLock
);
325 releaseLock(sysFdLock
);
329 // In this implementation, the uninstall() call is not supported since
330 // we do not allow programmatic deletion of the MDS databases.
334 MDSSession::uninstall ()
336 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
340 * Common private open routine given a full specified path.
342 * FIXME: both of these dbOpen routines leak like crazy even though
343 * we know we close properly.
344 * Typical stack trace (from MallocDebug) of a leak is
346 * DatabaseSession::DbOpen(char const *, cssm_net_address const...)
347 * DatabaseManager::dbOpen(Security::DatabaseSession &, ...)
348 * Database::_dbOpen(Security::DatabaseSession &, unsigned long, ...)
349 * AppleDatabase::dbOpen(Security::DbContext &)
350 * DbModifier::openDatabase(void)
351 * DbModifier::getDbVersion(void)
352 * DbVersion::DbVersion(Security::AtomicFile &, ...)
353 * DbVersion::open(void)
354 * MetaRecord::unpackRecord(Security::ReadSection const &, ...)
355 * MetaRecord::unpackAttribute(Security::ReadSection const &, ...)
356 * MetaAttribute::unpackAttribute(Security::ReadSection const &, ..)
357 * TypedMetaAttribute<Security::StringValue>::unpackValue(...)
358 * TrackingAllocator::malloc(unsigned long)
360 CSSM_DB_HANDLE
MDSSession::dbOpen(
363 MSDebug("Opening %s", dbName
);
364 CSSM_DB_HANDLE dbHand
;
365 DatabaseSession::DbOpen(dbName
,
368 NULL
, // AccessCred - hopefully optional
369 NULL
, // OpenParameters
375 /* DatabaseSession routines we need to override */
376 void MDSSession::DbOpen(const char *DbName
,
377 const CSSM_NET_ADDRESS
*DbLocation
,
378 CSSM_DB_ACCESS_TYPE AccessRequest
,
379 const AccessCredentials
*AccessCred
,
380 const void *OpenParameters
,
381 CSSM_DB_HANDLE
&DbHandle
)
383 /* make sure DBs are up-to-date */
387 * Only task here is map incoming DbName - specified in the CDSA
388 * spec - to a filename we actually use (which is a path to either
389 * a system MDS DB file or a per-user MDS DB file).
392 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
395 if(!strcmp(DbName
, MDS_OBJECT_DIRECTORY_NAME
)) {
396 dbName
= MDS_OBJECT_DB_NAME
;
398 else if(!strcmp(DbName
, MDS_CDSA_DIRECTORY_NAME
)) {
399 dbName
= MDS_DIRECT_DB_NAME
;
402 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
404 char fullPath
[MAXPATHLEN
];
405 dbFullPath(dbName
, fullPath
);
406 DatabaseSession::DbOpen(fullPath
, DbLocation
, AccessRequest
, AccessCred
,
407 OpenParameters
, DbHandle
);
411 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR
&outNameList
)
413 outNameList
= new CSSM_NAME_LIST
[1];
414 outNameList
->NumStrings
= 2;
415 outNameList
->String
= new (char *)[2];
416 outNameList
->String
[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME
);
417 outNameList
->String
[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME
);
421 MDSSession::FreeNameList(CSSM_NAME_LIST
&inNameList
)
423 delete [] inNameList
.String
[0];
424 delete [] inNameList
.String
[1];
425 delete [] inNameList
.String
;
428 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle
,
431 printf("GetDbNameFromHandle: code on demand\n");
432 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
436 // Attempt to obtain an exclusive lock over the the MDS databases. The
437 // parameter is the maximum amount of time, in milliseconds, to spend
438 // trying to obtain the lock. A value of zero means to return failure
439 // right away if the lock cannot be obtained.
442 MDSSession::obtainLock(
443 const char *lockFile
, // e.g. MDS_LOCK_FILE_PATH
445 int timeout
) // default 0
447 #if SKIP_FILE_LOCKING
451 static const int kRetryDelay
= 250; // ms
453 fd
= open(MDS_LOCK_FILE_PATH
, O_CREAT
| O_EXCL
, 0544);
454 while (fd
== -1 && timeout
>= kRetryDelay
) {
455 timeout
-= kRetryDelay
;
456 usleep(1000 * kRetryDelay
);
457 mLockFd
= open(MDS_LOCK_FILE_PATH
, O_CREAT
| O_EXCL
, 0544);
461 #endif /* SKIP_FILE_LOCKING */
465 // Release the exclusive lock over the MDS databases. If this session
466 // does not hold the lock, this method does nothing.
470 MDSSession::releaseLock(int &fd
)
472 #if !SKIP_FILE_LOCKING
475 unlink(MDS_LOCK_FILE_PATH
);
481 /* given DB file name, fill in fully specified path */
482 void MDSSession::dbFullPath(
484 char fullPath
[MAXPATHLEN
+1])
486 mModule
.getDbPath(fullPath
);
487 assert(fullPath
[0] != '\0');
488 strcat(fullPath
, "/");
489 strcat(fullPath
, dbName
);
493 * See if any per-user bundles exist in specified directory. Returns true if so.
494 * First the check for one entry....
496 static bool isBundle(
497 const struct dirent
*dp
)
502 /* NFS directories show up as DT_UNKNOWN */
510 int suffixLen
= strlen(MDS_BUNDLE_EXTEN
);
511 int len
= strlen(dp
->d_name
);
513 return (len
>= suffixLen
) &&
514 !strcmp(dp
->d_name
+ len
- suffixLen
, MDS_BUNDLE_EXTEN
);
517 /* now the full directory search */
518 static bool checkUserBundles(
519 const char *bundlePath
)
521 MSDebug("searching for user bundles in %s", bundlePath
);
522 DIR *dir
= opendir(bundlePath
);
528 while ((dp
= readdir(dir
)) != NULL
) {
530 /* any other checking to do? */
536 MSDebug("...%s bundle(s) found", rtn
? "" : "No");
540 #define COPY_BUF_SIZE 1024
542 /* Single file copy with locking */
543 static void safeCopyFile(
544 const char *fromPath
,
547 /* open source for reading */
548 int srcFd
= open(fromPath
, O_RDONLY
, 0);
550 /* FIXME - what error would we see if the file is locked for writing
551 * by someone else? We definitely have to handle that. */
553 MSDebug("Error %d opening system DB file %s\n", error
, fromPath
);
554 UnixError::throwMe(error
);
557 /* acquire the same kind of lock AtomicFile uses */
562 fl
.l_type
= F_RDLCK
; // AtomicFile gets F_WRLCK
563 fl
.l_whence
= SEEK_SET
;
565 // Keep trying to obtain the lock if we get interupted.
567 if (::fcntl(srcFd
, F_SETLKW
, reinterpret_cast<int>(&fl
)) == -1) {
569 if (error
== EINTR
) {
572 MSDebug("Error %d locking system DB file %s\n", error
, fromPath
);
573 UnixError::throwMe(error
);
580 /* create destination */
581 int destFd
= open(toPath
, O_WRONLY
| O_APPEND
| O_CREAT
| O_TRUNC
| O_EXCL
, 0644);
584 MSDebug("Error %d opening user DB file %s\n", error
, toPath
);
585 UnixError::throwMe(error
);
589 char buf
[COPY_BUF_SIZE
];
591 int bytesRead
= read(srcFd
, buf
, COPY_BUF_SIZE
);
597 MSDebug("Error %d reading system DB file %s\n", error
, fromPath
);
598 UnixError::throwMe(error
);
600 int bytesWritten
= write(destFd
, buf
, bytesRead
);
601 if(bytesWritten
< 0) {
603 MSDebug("Error %d writing user DB file %s\n", error
, toPath
);
604 UnixError::throwMe(error
);
608 /* unlock source and close both */
610 if (::fcntl(srcFd
, F_SETLK
, reinterpret_cast<int>(&fl
)) == -1) {
611 MSDebug("Error %d unlocking system DB file %s\n", errno
, fromPath
);
617 /* Copy system DB files to specified user dir. */
618 static void copySystemDbs(
619 const char *userDbFileDir
)
621 char toPath
[MAXPATHLEN
+1];
623 sprintf(toPath
, "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
);
624 safeCopyFile(MDS_OBJECT_DB_PATH
, toPath
);
625 sprintf(toPath
, "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
);
626 safeCopyFile(MDS_DIRECT_DB_PATH
, toPath
);
630 * Ensure current DB files exist and are up-to-date.
631 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
632 * other public functions which access a DB from scratch.
634 void MDSSession::updateDataBases()
636 bool isRoot
= (getuid() == (uid_t
)0);
637 bool createdSystemDb
= false;
640 * The first thing we do is to ensure that system DBs are present.
641 * This call right here is the reason for the purge argument in
642 * systemDatabasesPresent(); if we're a user proc, we can't grab the system
645 if(!systemDatabasesPresent(false)) {
646 if(isRoot
|| SYSTEM_DBS_VIA_USER
) {
647 /* Either doing actual MDS op as root, or development case:
648 * install as current user */
652 /* This path TBD; it involves either a SecurityServer RPC or
653 * a privileged tool exec'd via AEWP. */
656 /* remember this - we have to delete possible existing user DBs */
657 createdSystemDb
= true;
660 /* if we scanned recently, we're done */
661 double delta
= mModule
.timeSinceLastScan();
662 if(delta
< (double)MDS_SCAN_INTERVAL
) {
667 * Obtain various per-user paths. Root is a special case but follows most
668 * of the same logic from here on.
670 char userDbFileDir
[MAXPATHLEN
+1];
671 char userObjDbFilePath
[MAXPATHLEN
+1];
672 char userDirectDbFilePath
[MAXPATHLEN
+1];
673 char userBundlePath
[MAXPATHLEN
+1];
674 char userDbLockPath
[MAXPATHLEN
+1];
677 strcat(userDbFileDir
, MDS_SYSTEM_DB_DIR
);
678 /* no userBundlePath */
681 char *userHome
= getenv("HOME");
682 if(userHome
== NULL
) {
683 /* FIXME - what now, batman? */
684 MSDebug("updateDataBases: no HOME");
687 sprintf(userBundlePath
, "%s/%s", userHome
, MDS_USER_BUNDLE
);
689 /* DBs go in a per-UID directory in the system MDS DB directory */
690 sprintf(userDbFileDir
, "%s/%d", MDS_SYSTEM_DB_DIR
, (int)(getuid()));
692 sprintf(userObjDbFilePath
, "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
);
693 sprintf(userDirectDbFilePath
, "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
);
694 sprintf(userDbLockPath
, "%s/%s", userDbFileDir
, MDS_LOCK_FILE_NAME
);
697 * Create the per-user directory first...that's where the lock we'll be using
698 * lives. Our createDir() is tolerant of EEXIST errors.
701 if(createDir(userDbFileDir
)) {
702 /* We'll just have to limp along using the read-only system DBs */
703 Syslog::alert("Error creating %s", userDbFileDir
);
704 MSDebug("Error creating user DBs; using system DBs");
705 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
);
710 /* always release mLockFd no matter what happens */
711 if(!obtainLock(userDbLockPath
, mLockFd
, DB_LOCK_TIMEOUT
)) {
712 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
716 if(createdSystemDb
) {
717 /* initial creation of system DBs by user - start from scratch */
718 unlink(userObjDbFilePath
);
719 unlink(userDirectDbFilePath
);
723 * System DBs exist and are as up-to-date as we are allowed to make them.
724 * Create per-user DBs if they don't exist.
726 if(createdSystemDb
|| //Êoptimization - if this is true, the
727 // per-user DBs do not exist since we just
729 !doFilesExist(userObjDbFilePath
, userDirectDbFilePath
,
732 /* copy system DBs to user DBs */
733 MSDebug("copying system DBs to user at %s", userDbFileDir
);
734 copySystemDbs(userDbFileDir
);
737 MSDebug("Using existing user DBs at %s", userDbFileDir
);
741 MSDebug("Using system DBs only");
745 * Update per-user DBs from all three sources (System.framework,
746 * System bundles, user bundles) as appropriate. Note that if we
747 * just created the system DBs, we don't have to update with
748 * respect to system framework or system bundles.
750 DbFilesInfo
dbFiles(*this, userDbFileDir
);
751 if(!createdSystemDb
) {
752 dbFiles
.removeOutdatedPlugins();
753 dbFiles
.updateSystemDbInfo(MDS_SYSTEM_PATH
, MDS_BUNDLE_PATH
);
756 /* root doesn't have user bundles */
757 if(checkUserBundles(userBundlePath
)) {
758 dbFiles
.updateForBundleDir(userBundlePath
);
761 mModule
.setDbPath(userDbFileDir
);
762 } /* main block protected by mLockFd */
764 releaseLock(mLockFd
);
767 mModule
.lastScanIsNow();
768 releaseLock(mLockFd
);
772 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
774 void MDSSession::removeRecordsForGuid(
776 CSSM_DB_HANDLE dbHand
)
779 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
780 CSSM_HANDLE resultHand
;
781 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
782 CSSM_SELECTION_PREDICATE predicate
;
785 /* don't want any attributes back, just a record ptr */
786 recordAttrs
.DataRecordType
= CSSM_DL_DB_RECORD_ANY
;
787 recordAttrs
.SemanticInformation
= 0;
788 recordAttrs
.NumberOfAttributes
= 0;
789 recordAttrs
.AttributeData
= NULL
;
791 /* one predicate, == guid */
792 predicate
.DbOperator
= CSSM_DB_EQUAL
;
793 predicate
.Attribute
.Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
794 predicate
.Attribute
.Info
.Label
.AttributeName
= "ModuleID";
795 predicate
.Attribute
.Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
796 predData
.Data
= (uint8
*)guid
;
797 predData
.Length
= strlen(guid
) + 1;
798 predicate
.Attribute
.Value
= &predData
;
799 predicate
.Attribute
.NumberOfValues
= 1;
801 query
.RecordType
= CSSM_DL_DB_RECORD_ANY
;
802 query
.Conjunctive
= CSSM_DB_NONE
;
803 query
.NumSelectionPredicates
= 1;
804 query
.SelectionPredicate
= &predicate
;
805 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
806 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
807 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
810 * Each search starts from scratch - not sure if we can delete a record
811 * associated with an active query and continue on with that query.
815 DLQuery
perryQuery(query
);
816 resultHand
= DataGetFirst(dbHand
,
823 MSDebug("...deleting a record for guid %s", guid
);
824 DataDelete(dbHand
, *record
);
825 DataAbortQuery(dbHand
, resultHand
);
828 MSDebug("exception (1) while deleting record for guid %s", guid
);
833 FreeUniqueRecord(dbHand
, *record
);
839 MSDebug("exception (2) while deleting record for guid %s", guid
);
844 * Determine if system databases are present.
845 * If the purge argument is true, we'll ensure that either both or neither
846 * DB files exist on exit; in that case caller need to hold MDS_LOCK_FILE_PATH.
848 bool MDSSession::systemDatabasesPresent(bool purge
)
853 /* this can throw on a failed attempt to delete sole existing file */
854 if(doFilesExist(MDS_OBJECT_DB_PATH
, MDS_DIRECT_DB_PATH
, purge
)) {
865 * Given a DB name (which is used as an absolute path) and an array of
866 * RelationInfos, create a DB.
869 MDSSession::createSystemDatabase(
871 const RelationInfo
*relationInfo
,
872 unsigned numRelations
,
873 CSSM_DB_HANDLE
&dbHand
) // RETURNED
876 CSSM_DBINFO_PTR dbInfoP
= &dbInfo
;
878 memset(dbInfoP
, 0, sizeof(CSSM_DBINFO
));
879 dbInfoP
->NumberOfRecordTypes
= numRelations
;
880 dbInfoP
->IsLocal
= CSSM_TRUE
; // TBD - what does this mean?
881 dbInfoP
->AccessPath
= NULL
; // TBD
883 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
885 unsigned size
= sizeof(CSSM_DB_PARSING_MODULE_INFO
) * numRelations
;
886 dbInfoP
->DefaultParsingModules
= (CSSM_DB_PARSING_MODULE_INFO_PTR
)malloc(size
);
887 memset(dbInfoP
->DefaultParsingModules
, 0, size
);
888 size
= sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO
) * numRelations
;
889 dbInfoP
->RecordAttributeNames
= (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR
)malloc(size
);
890 memset(dbInfoP
->RecordAttributeNames
, 0, size
);
891 size
= sizeof(CSSM_DB_RECORD_INDEX_INFO
) * numRelations
;
892 dbInfoP
->RecordIndexes
= (CSSM_DB_RECORD_INDEX_INFO_PTR
)malloc(size
);
893 memset(dbInfoP
->RecordIndexes
, 0, size
);
895 /* cook up attribute and index info for each relation */
897 for(relation
=0; relation
<numRelations
; relation
++) {
898 const struct RelationInfo
*relp
= &relationInfo
[relation
]; // source
899 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo
=
900 &dbInfoP
->RecordAttributeNames
[relation
]; // dest 1
901 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo
=
902 &dbInfoP
->RecordIndexes
[relation
]; // dest 2
904 attrInfo
->DataRecordType
= relp
->DataRecordType
;
905 attrInfo
->NumberOfAttributes
= relp
->NumberOfAttributes
;
906 attrInfo
->AttributeInfo
= (CSSM_DB_ATTRIBUTE_INFO_PTR
)relp
->AttributeInfo
;
908 indexInfo
->DataRecordType
= relp
->DataRecordType
;
909 indexInfo
->NumberOfIndexes
= relp
->NumberOfIndexes
;
910 indexInfo
->IndexInfo
= (CSSM_DB_INDEX_INFO_PTR
)relp
->IndexInfo
;
917 CSSM_DB_ACCESS_READ
| CSSM_DB_ACCESS_WRITE
,
918 NULL
, // CredAndAclEntry
919 NULL
, // OpenParameters
923 MSDebug("Error on DbCreate");
924 free(dbInfoP
->DefaultParsingModules
);
925 free(dbInfoP
->RecordAttributeNames
);
926 free(dbInfoP
->RecordIndexes
);
929 free(dbInfoP
->DefaultParsingModules
);
930 free(dbInfoP
->RecordAttributeNames
);
931 free(dbInfoP
->RecordIndexes
);
936 * Create system databases from scratch if they do not already exist.
937 * MDS_LOCK_FILE_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
938 * exist (that's our caller's job, before acquiring MDS_LOCK_FILE_PATH).
939 * Returns true if we actually built the files, false if they already
942 bool MDSSession::createSystemDatabases()
944 CSSM_DB_HANDLE objectDbHand
= 0;
945 CSSM_DB_HANDLE directoryDbHand
= 0;
947 assert((getuid() == (uid_t
)0) || !SYSTEM_MDS_ROOT_ONLY
);
948 if(systemDatabasesPresent(true)) {
949 /* both databases exist as regular files - we're done */
950 MSDebug("system DBs already exist");
954 /* create two DBs - any exception here results in deleting both of them */
955 MSDebug("Creating MDS DBs");
957 createSystemDatabase(MDS_OBJECT_DB_PATH
, &kObjectRelation
, 1, objectDbHand
);
958 DbClose(objectDbHand
);
960 createSystemDatabase(MDS_DIRECT_DB_PATH
, kMDSRelationInfo
, kNumMdsRelations
,
962 DbClose(directoryDbHand
);
966 MSDebug("Error creating MDS DBs - deleting both DB files");
967 unlink(MDS_OBJECT_DB_PATH
);
968 unlink(MDS_DIRECT_DB_PATH
);
975 * DbFilesInfo helper class
978 /* Note both DB files MUST exist at construction time */
979 MDSSession::DbFilesInfo::DbFilesInfo(
981 const char *dbPath
) :
987 assert(strlen(dbPath
) < MAXPATHLEN
);
988 strcpy(mDbPath
, dbPath
);
990 /* stat the two DB files, snag the later timestamp */
991 char path
[MAXPATHLEN
];
992 sprintf(path
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
994 int rtn
= ::stat(path
, &sb
);
997 MSDebug("Error %d statting DB file %s", error
, path
);
998 UnixError::throwMe(error
);
1000 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1001 sprintf(path
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1002 rtn
= ::stat(path
, &sb
);
1005 MSDebug("Error %d statting DB file %s", error
, path
);
1006 UnixError::throwMe(error
);
1008 if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1009 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1013 #define AUTO_COMMIT_OFF_ON_CLOSE 1
1015 MDSSession::DbFilesInfo::~DbFilesInfo()
1017 if(mObjDbHand
!= 0) {
1018 #if AUTO_COMMIT_OPT && AUTO_COMMIT_OFF_ON_CLOSE
1019 mSession
.PassThrough(mObjDbHand
,
1020 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1021 reinterpret_cast<void *>(CSSM_TRUE
),
1024 mSession
.DbClose(mObjDbHand
);
1027 if(mDirectDbHand
!= 0) {
1028 #if AUTO_COMMIT_OPT && AUTO_COMMIT_OFF_ON_CLOSE
1029 mSession
.PassThrough(mDirectDbHand
,
1030 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1031 reinterpret_cast<void *>(CSSM_TRUE
),
1034 mSession
.DbClose(mDirectDbHand
);
1039 /* lazy evaluation of both DB handlesÊ*/
1040 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::objDbHand()
1042 if(mObjDbHand
!= 0) {
1045 char fullPath
[MAXPATHLEN
+ 1];
1046 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
1047 mObjDbHand
= mSession
.dbOpen(fullPath
);
1051 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::directDbHand()
1053 if(mDirectDbHand
!= 0) {
1054 return mDirectDbHand
;
1056 char fullPath
[MAXPATHLEN
+ 1];
1057 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1058 mDirectDbHand
= mSession
.dbOpen(fullPath
);
1059 return mDirectDbHand
;
1063 * Update the info for System.framework and the system bundles.
1065 void MDSSession::DbFilesInfo::updateSystemDbInfo(
1066 const char *systemPath
, // e.g., /System/Library/Frameworks
1067 const char *bundlePath
) // e.g., /System/Library/Security
1069 /* System.framework - CSSM and built-in modules */
1070 char fullPath
[MAXPATHLEN
];
1071 sprintf(fullPath
, "%s/%s", systemPath
, MDS_SYSTEM_FRAME
);
1072 updateForBundle(fullPath
);
1074 /* Standard loadable bundles */
1075 updateForBundleDir(bundlePath
);
1079 MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1080 const CSSM_DATA
&guid
)
1082 assert(guid
.Length
<= MAX_GUID_LEN
);
1083 assert(guid
.Length
!= 0);
1084 memmove(mGuid
, guid
.Data
, guid
.Length
);
1085 if(mGuid
[guid
.Length
- 1] != '\0') {
1086 mGuid
[guid
.Length
] = '\0';
1091 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1092 * If so, add to tbdVector.
1094 void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1095 const CSSM_DATA
&pathValue
,
1096 const CSSM_DATA
&guidValue
,
1097 TbdVector
&tbdVector
)
1099 /* stat the specified plugin */
1101 bool obsolete
= false;
1102 int rtn
= ::stat((char *)pathValue
.Data
, &sb
);
1104 /* not there or inaccessible; delete */
1107 else if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1108 /* timestamp of plugin's main directory later than that of DBs */
1112 TbdRecord
*tbdRecord
= new TbdRecord(guidValue
);
1113 tbdVector
.push_back(tbdRecord
);
1114 MSDebug("checkOutdatedPlugin: flagging %s obsolete", pathValue
.Data
);
1119 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1120 * i.e., with any path) which are either not present on disk, or which
1121 * have changed since dbFiles.laterTimestamp().
1123 void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1126 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1127 CSSM_HANDLE resultHand
;
1128 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1129 CSSM_DB_ATTRIBUTE_DATA theAttrs
[2];
1130 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
;
1131 TbdVector tbdRecords
;
1134 * First, scan object directory. All we need are the path and GUID attributes.
1136 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1137 recordAttrs
.SemanticInformation
= 0;
1138 recordAttrs
.NumberOfAttributes
= 2;
1139 recordAttrs
.AttributeData
= theAttrs
;
1141 attrInfo
= &theAttrs
[0].Info
;
1142 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1143 attrInfo
->Label
.AttributeName
= "ModuleID";
1144 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1145 theAttrs
[0].NumberOfValues
= 0;
1146 theAttrs
[0].Value
= NULL
;
1147 attrInfo
= &theAttrs
[1].Info
;
1148 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1149 attrInfo
->Label
.AttributeName
= "Path";
1150 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1151 theAttrs
[1].NumberOfValues
= 0;
1152 theAttrs
[1].Value
= NULL
;
1154 /* just search by recordType, no predicates */
1155 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1156 query
.Conjunctive
= CSSM_DB_NONE
;
1157 query
.NumSelectionPredicates
= 0;
1158 query
.SelectionPredicate
= NULL
;
1159 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1160 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1161 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1163 DLQuery
perryQuery(query
);
1165 resultHand
= mSession
.DataGetFirst(objDbHand(),
1172 MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1176 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1179 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1180 checkOutdatedPlugin(*theAttrs
[1].Value
, *theAttrs
[0].Value
,
1184 MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1186 for(unsigned dex
=0; dex
<2; dex
++) {
1187 if(theAttrs
[dex
].Value
) {
1188 if(theAttrs
[dex
].Value
->Data
) {
1189 mSession
.free(theAttrs
[dex
].Value
->Data
);
1191 mSession
.free(theAttrs
[dex
].Value
);
1196 /* empty Object DB - we're done */
1197 MSDebug("removeOutdatedPlugins: empty object DB");
1201 /* now the rest of the object DB records */
1203 bool brtn
= mSession
.DataGetNext(objDbHand(),
1213 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1215 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1216 checkOutdatedPlugin(*theAttrs
[1].Value
,
1221 MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1223 for(unsigned dex
=0; dex
<2; dex
++) {
1224 if(theAttrs
[dex
].Value
) {
1225 if(theAttrs
[dex
].Value
->Data
) {
1226 mSession
.free(theAttrs
[dex
].Value
->Data
);
1228 mSession
.free(theAttrs
[dex
].Value
);
1232 /* no DataAbortQuery needed; we scanned until completion */
1235 * We have a vector of plugins to be deleted. Remove all records from both
1236 * DBs associated with the plugins, as specified by guid.
1238 unsigned numRecords
= tbdRecords
.size();
1239 for(unsigned i
=0; i
<numRecords
; i
++) {
1240 TbdRecord
*tbdRecord
= tbdRecords
[i
];
1241 mSession
.removeRecordsForGuid(tbdRecord
->guid(), objDbHand());
1242 mSession
.removeRecordsForGuid(tbdRecord
->guid(), directDbHand());
1244 for(unsigned i
=0; i
<numRecords
; i
++) {
1245 delete tbdRecords
[i
];
1251 * Update DBs for all bundles in specified directory.
1253 void MDSSession::DbFilesInfo::updateForBundleDir(
1254 const char *bundleDirPath
)
1256 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1257 * much too heavyweight */
1258 MSDebug("...updating DBs for dir %s", bundleDirPath
);
1259 DIR *dir
= opendir(bundleDirPath
);
1261 MSDebug("updateForBundleDir: error %d opening %s", errno
, bundleDirPath
);
1265 char fullPath
[MAXPATHLEN
];
1266 while ((dp
= readdir(dir
)) != NULL
) {
1268 sprintf(fullPath
, "%s/%s", bundleDirPath
, dp
->d_name
);
1269 updateForBundle(fullPath
);
1276 * lookup by path - just returns true if there is a record assoociated with the path
1279 bool MDSSession::DbFilesInfo::lookupForPath(
1283 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1284 CSSM_HANDLE resultHand
= 0;
1285 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1286 CSSM_DB_ATTRIBUTE_DATA theAttr
;
1287 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
= &theAttr
.Info
;
1288 CSSM_SELECTION_PREDICATE predicate
;
1291 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1292 recordAttrs
.SemanticInformation
= 0;
1293 recordAttrs
.NumberOfAttributes
= 1;
1294 recordAttrs
.AttributeData
= &theAttr
;
1296 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1297 attrInfo
->Label
.AttributeName
= "Path";
1298 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1300 theAttr
.NumberOfValues
= 0;
1301 theAttr
.Value
= NULL
;
1303 predicate
.DbOperator
= CSSM_DB_EQUAL
;
1304 predicate
.Attribute
.Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1305 predicate
.Attribute
.Info
.Label
.AttributeName
= "Path";
1306 predicate
.Attribute
.Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1307 predData
.Data
= (uint8
*)path
;
1308 predData
.Length
= strlen(path
) + 1;
1309 predicate
.Attribute
.Value
= &predData
;
1310 predicate
.Attribute
.NumberOfValues
= 1;
1312 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1313 query
.Conjunctive
= CSSM_DB_NONE
;
1314 query
.NumSelectionPredicates
= 1;
1315 query
.SelectionPredicate
= &predicate
;
1316 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1317 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1318 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1322 DLQuery
perryQuery(query
);
1323 resultHand
= mSession
.DataGetFirst(objDbHand(),
1333 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1338 if(resultHand
&& ourRtn
) {
1339 /* more resulting pending; terminate the search */
1341 mSession
.DataAbortQuery(mObjDbHand
, resultHand
);
1344 MSDebug("exception on DataAbortQuery in lookupForPath");
1348 if(theAttr
.Value
->Data
) {
1349 mSession
.free(theAttr
.Value
->Data
);
1351 mSession
.free(theAttr
.Value
);
1356 /* update entry for one bundle, which is known to exist */
1357 void MDSSession::DbFilesInfo::updateForBundle(
1358 const char *bundlePath
)
1360 MSDebug("...updating DBs for bundle %s", bundlePath
);
1362 /* Quick lookup - do we have ANY entry for a bundle with this path? */
1363 if(lookupForPath(bundlePath
)) {
1364 /* Yep, we're done */
1367 MDSAttrParser
parser(bundlePath
,
1371 parser
.parseAttrs();
1374 /* DB autocommit on/off */
1375 void MDSSession::DbFilesInfo::autoCommit(CSSM_BOOL val
)
1378 mSession
.PassThrough(objDbHand(),
1379 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1380 reinterpret_cast<void *>(val
),
1382 mSession
.PassThrough(directDbHand(),
1383 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1384 reinterpret_cast<void *>(val
),
1388 MSDebug("DbFilesInfo::autoCommit error!");
1394 } // end namespace Security