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
114 /* Only allow root to create and update system DB files - in the final config this
116 #define SYSTEM_MDS_ROOT_ONLY 0
119 * Early development; no Security Server/root involvement with system DB creation.
120 * If this is true, SYSTEM_MDS_ROOT_ONLY must be false (though both can be
121 * false for intermediate testing).
123 #define SYSTEM_DBS_VIA_USER 1
126 /* normal deployment case */
127 #define SYSTEM_MDS_ROOT_ONLY 1
128 #define SYSTEM_DBS_VIA_USER 0
132 * Determine if both of the specified DB files exist as
133 * accessible regular files. Returns true if they do. If the purge argument
134 * is true, we'll ensure that either both or neither of the files exist on
137 static bool doFilesExist(
138 const char *objDbFile
,
139 const char *directDbFile
,
140 bool purge
) // false means "passive" check
143 bool objectExist
= false;
144 bool directExist
= false;
146 if (stat(objDbFile
, &sb
) == 0) {
147 /* Object DB exists */
148 if(!(sb
.st_mode
& S_IFREG
)) {
149 MSDebug("deleting non-regular file %s", objDbFile
);
150 if(purge
&& unlink(objDbFile
)) {
151 MSDebug("unlink(%s) returned %d", objDbFile
, errno
);
152 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
159 if (stat(directDbFile
, &sb
) == 0) {
160 /* directory DB exists */
161 if(!(sb
.st_mode
& S_IFREG
)) {
162 MSDebug("deleting non-regular file %s", directDbFile
);
163 if(purge
& unlink(directDbFile
)) {
164 MSDebug("unlink(%s) returned %d", directDbFile
, errno
);
165 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
170 if(objectExist
&& directExist
) {
171 /* both databases exist as regular files */
178 /* at least one does not exist - ensure neither of them do */
180 if(unlink(objDbFile
)) {
181 MSDebug("unlink(%s) returned %d", objDbFile
, errno
);
182 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
186 if(unlink(directDbFile
)) {
187 MSDebug("unlink(%s) returned %d", directDbFile
, errno
);
188 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
195 * Determine if specified directory exists.
197 static bool doesDirectExist(
202 if (stat(dirPath
, &sb
)) {
205 if(!(sb
.st_mode
& S_IFDIR
)) {
212 * Create specified directory if it doesn't already exist.
213 * Zero for mode means "use the default provided by 0755 modified by umask".
215 static int createDir(
219 if(doesDirectExist(dirPath
)) {
222 int rtn
= mkdir(dirPath
, 0755);
224 if(errno
== EEXIST
) {
230 MSDebug("mkdir(%s) returned %d", dirPath
, errno
);
233 if((rtn
== 0) && (dirMode
!= 0)) {
234 rtn
= chmod(dirPath
, dirMode
);
236 MSDebug("chmod(%s) returned %d", dirPath
, errno
);
243 * Create an MDS session.
245 MDSSession::MDSSession (const Guid
*inCallerGuid
,
246 const CSSM_MEMORY_FUNCS
&inMemoryFunctions
) :
247 DatabaseSession(MDSModule::get().databaseManager()),
248 mCssmMemoryFunctions (inMemoryFunctions
),
249 mModule(MDSModule::get()),
252 MSDebug("MDSSession::MDSSession");
254 #if START_FROM_SCRATCH
255 unlink(MDS_LOCK_FILE_PATH
);
256 unlink(MDS_OBJECT_DB_PATH
);
257 unlink(MDS_DIRECT_DB_PATH
);
260 mCallerGuidPresent
= inCallerGuid
!= nil
;
261 if (mCallerGuidPresent
)
262 mCallerGuid
= *inCallerGuid
;
265 * Create DB files if necessary; make sure they are up-to-date
267 // no! done in either install or open! updateDataBases();
270 MDSSession::~MDSSession ()
272 MSDebug("MDSSession::~MDSSession");
273 releaseLock(mLockFd
);
277 MDSSession::terminate ()
279 MSDebug("MDSSession::terminate");
280 releaseLock(mLockFd
);
285 * Called by security server or AEWP-executed privileged tool.
288 MDSSession::install ()
290 if((getuid() != (uid_t
)0) && SYSTEM_MDS_ROOT_ONLY
) {
291 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
296 /* before we obtain the lock, ensure the the system MDS DB directory exists */
297 if(createDir(MDS_SYSTEM_DB_DIR
, 01777)) {
298 MSDebug("Error creating system MDS dir; aborting.");
299 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED
);
302 if(!obtainLock(MDS_LOCK_FILE_PATH
, sysFdLock
, DB_LOCK_TIMEOUT
)) {
303 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
305 if(!systemDatabasesPresent(true)) {
307 * Root umask is 0 when this runs, so specify readable (only)
308 * by world. Also, turn off autocommit during initial
309 * system DB population.
311 bool created
= createSystemDatabases(CSSM_FALSE
, 0644);
314 * Skip possible race condition in which this is called twice,
315 * both via SS by user procs who say "no system DBs present"
316 * in their updateDataBases() method.
318 * Do initial population of system DBs.
320 DbFilesInfo
dbFiles(*this, MDS_SYSTEM_DB_DIR
);
321 dbFiles
.autoCommit(CSSM_FALSE
);
322 dbFiles
.updateSystemDbInfo(MDS_SYSTEM_PATH
, MDS_BUNDLE_PATH
);
327 if(sysFdLock
!= -1) {
328 releaseLock(sysFdLock
);
332 releaseLock(sysFdLock
);
336 // In this implementation, the uninstall() call is not supported since
337 // we do not allow programmatic deletion of the MDS databases.
341 MDSSession::uninstall ()
343 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
347 * Common private open routine given a full specified path.
349 * FIXME: both of these dbOpen routines leak like crazy even though
350 * we know we close properly.
351 * Typical stack trace (from MallocDebug) of a leak is
353 * DatabaseSession::DbOpen(char const *, cssm_net_address const...)
354 * DatabaseManager::dbOpen(Security::DatabaseSession &, ...)
355 * Database::_dbOpen(Security::DatabaseSession &, unsigned long, ...)
356 * AppleDatabase::dbOpen(Security::DbContext &)
357 * DbModifier::openDatabase(void)
358 * DbModifier::getDbVersion(void)
359 * DbVersion::DbVersion(Security::AtomicFile &, ...)
360 * DbVersion::open(void)
361 * MetaRecord::unpackRecord(Security::ReadSection const &, ...)
362 * MetaRecord::unpackAttribute(Security::ReadSection const &, ...)
363 * MetaAttribute::unpackAttribute(Security::ReadSection const &, ..)
364 * TypedMetaAttribute<Security::StringValue>::unpackValue(...)
365 * TrackingAllocator::malloc(unsigned long)
367 CSSM_DB_HANDLE
MDSSession::dbOpen(
370 MSDebug("Opening %s", dbName
);
371 CSSM_DB_HANDLE dbHand
;
372 DatabaseSession::DbOpen(dbName
,
375 NULL
, // AccessCred - hopefully optional
376 NULL
, // OpenParameters
382 /* DatabaseSession routines we need to override */
383 void MDSSession::DbOpen(const char *DbName
,
384 const CSSM_NET_ADDRESS
*DbLocation
,
385 CSSM_DB_ACCESS_TYPE AccessRequest
,
386 const AccessCredentials
*AccessCred
,
387 const void *OpenParameters
,
388 CSSM_DB_HANDLE
&DbHandle
)
390 /* make sure DBs are up-to-date */
394 * Only task here is map incoming DbName - specified in the CDSA
395 * spec - to a filename we actually use (which is a path to either
396 * a system MDS DB file or a per-user MDS DB file).
399 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
402 if(!strcmp(DbName
, MDS_OBJECT_DIRECTORY_NAME
)) {
403 dbName
= MDS_OBJECT_DB_NAME
;
405 else if(!strcmp(DbName
, MDS_CDSA_DIRECTORY_NAME
)) {
406 dbName
= MDS_DIRECT_DB_NAME
;
409 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME
);
411 char fullPath
[MAXPATHLEN
];
412 dbFullPath(dbName
, fullPath
);
413 DatabaseSession::DbOpen(fullPath
, DbLocation
, AccessRequest
, AccessCred
,
414 OpenParameters
, DbHandle
);
418 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR
&outNameList
)
420 outNameList
= new CSSM_NAME_LIST
[1];
421 outNameList
->NumStrings
= 2;
422 outNameList
->String
= new (char *)[2];
423 outNameList
->String
[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME
);
424 outNameList
->String
[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME
);
428 MDSSession::FreeNameList(CSSM_NAME_LIST
&inNameList
)
430 delete [] inNameList
.String
[0];
431 delete [] inNameList
.String
[1];
432 delete [] inNameList
.String
;
435 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle
,
438 printf("GetDbNameFromHandle: code on demand\n");
439 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
443 // Attempt to obtain an exclusive lock over the the MDS databases. The
444 // parameter is the maximum amount of time, in milliseconds, to spend
445 // trying to obtain the lock. A value of zero means to return failure
446 // right away if the lock cannot be obtained.
449 MDSSession::obtainLock(
450 const char *lockFile
, // e.g. MDS_LOCK_FILE_PATH
452 int timeout
) // default 0
454 #if SKIP_FILE_LOCKING
458 static const int kRetryDelay
= 250; // ms
460 fd
= open(MDS_LOCK_FILE_PATH
, O_CREAT
| O_EXCL
, 0544);
461 while (fd
== -1 && timeout
>= kRetryDelay
) {
462 timeout
-= kRetryDelay
;
463 usleep(1000 * kRetryDelay
);
464 mLockFd
= open(MDS_LOCK_FILE_PATH
, O_CREAT
| O_EXCL
, 0544);
468 #endif /* SKIP_FILE_LOCKING */
472 // Release the exclusive lock over the MDS databases. If this session
473 // does not hold the lock, this method does nothing.
477 MDSSession::releaseLock(int &fd
)
479 #if !SKIP_FILE_LOCKING
482 unlink(MDS_LOCK_FILE_PATH
);
488 /* given DB file name, fill in fully specified path */
489 void MDSSession::dbFullPath(
491 char fullPath
[MAXPATHLEN
+1])
493 mModule
.getDbPath(fullPath
);
494 assert(fullPath
[0] != '\0');
495 strcat(fullPath
, "/");
496 strcat(fullPath
, dbName
);
500 * See if any per-user bundles exist in specified directory. Returns true if so.
501 * First the check for one entry....
503 static bool isBundle(
504 const struct dirent
*dp
)
509 /* NFS directories show up as DT_UNKNOWN */
517 int suffixLen
= strlen(MDS_BUNDLE_EXTEN
);
518 int len
= strlen(dp
->d_name
);
520 return (len
>= suffixLen
) &&
521 !strcmp(dp
->d_name
+ len
- suffixLen
, MDS_BUNDLE_EXTEN
);
524 /* now the full directory search */
525 static bool checkUserBundles(
526 const char *bundlePath
)
528 MSDebug("searching for user bundles in %s", bundlePath
);
529 DIR *dir
= opendir(bundlePath
);
535 while ((dp
= readdir(dir
)) != NULL
) {
537 /* any other checking to do? */
543 MSDebug("...%s bundle(s) found", rtn
? "" : "No");
547 #define COPY_BUF_SIZE 1024
549 /* Single file copy with locking */
550 static void safeCopyFile(
551 const char *fromPath
,
554 /* open source for reading */
555 int srcFd
= open(fromPath
, O_RDONLY
, 0);
557 /* FIXME - what error would we see if the file is locked for writing
558 * by someone else? We definitely have to handle that. */
560 MSDebug("Error %d opening system DB file %s\n", error
, fromPath
);
561 UnixError::throwMe(error
);
564 /* acquire the same kind of lock AtomicFile uses */
569 fl
.l_type
= F_RDLCK
; // AtomicFile gets F_WRLCK
570 fl
.l_whence
= SEEK_SET
;
572 // Keep trying to obtain the lock if we get interupted.
574 if (::fcntl(srcFd
, F_SETLKW
, reinterpret_cast<int>(&fl
)) == -1) {
576 if (error
== EINTR
) {
579 MSDebug("Error %d locking system DB file %s\n", error
, fromPath
);
580 UnixError::throwMe(error
);
587 /* create destination */
588 int destFd
= open(toPath
, O_WRONLY
| O_APPEND
| O_CREAT
| O_TRUNC
| O_EXCL
, 0644);
591 MSDebug("Error %d opening user DB file %s\n", error
, toPath
);
592 UnixError::throwMe(error
);
596 char buf
[COPY_BUF_SIZE
];
598 int bytesRead
= read(srcFd
, buf
, COPY_BUF_SIZE
);
604 MSDebug("Error %d reading system DB file %s\n", error
, fromPath
);
605 UnixError::throwMe(error
);
607 int bytesWritten
= write(destFd
, buf
, bytesRead
);
608 if(bytesWritten
< 0) {
610 MSDebug("Error %d writing user DB file %s\n", error
, toPath
);
611 UnixError::throwMe(error
);
615 /* unlock source and close both */
617 if (::fcntl(srcFd
, F_SETLK
, reinterpret_cast<int>(&fl
)) == -1) {
618 MSDebug("Error %d unlocking system DB file %s\n", errno
, fromPath
);
624 /* Copy system DB files to specified user dir. */
625 static void copySystemDbs(
626 const char *userDbFileDir
)
628 char toPath
[MAXPATHLEN
+1];
630 sprintf(toPath
, "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
);
631 safeCopyFile(MDS_OBJECT_DB_PATH
, toPath
);
632 sprintf(toPath
, "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
);
633 safeCopyFile(MDS_DIRECT_DB_PATH
, toPath
);
637 * Ensure current DB files exist and are up-to-date.
638 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
639 * other public functions which access a DB from scratch.
641 void MDSSession::updateDataBases()
643 bool isRoot
= (getuid() == (uid_t
)0);
644 bool createdSystemDb
= false;
647 * The first thing we do is to ensure that system DBs are present.
648 * This call right here is the reason for the purge argument in
649 * systemDatabasesPresent(); if we're a user proc, we can't grab the system
652 if(!systemDatabasesPresent(false)) {
653 if(isRoot
|| SYSTEM_DBS_VIA_USER
) {
654 /* Either doing actual MDS op as root, or development case:
655 * install as current user */
659 /* This path TBD; it involves either a SecurityServer RPC or
660 * a privileged tool exec'd via AEWP. */
663 /* remember this - we have to delete possible existing user DBs */
664 createdSystemDb
= true;
667 /* if we scanned recently, we're done */
668 double delta
= mModule
.timeSinceLastScan();
669 if(delta
< (double)MDS_SCAN_INTERVAL
) {
674 * Obtain various per-user paths. Root is a special case but follows most
675 * of the same logic from here on.
677 char userDbFileDir
[MAXPATHLEN
+1];
678 char userObjDbFilePath
[MAXPATHLEN
+1];
679 char userDirectDbFilePath
[MAXPATHLEN
+1];
680 char userBundlePath
[MAXPATHLEN
+1];
681 char userDbLockPath
[MAXPATHLEN
+1];
684 strcat(userDbFileDir
, MDS_SYSTEM_DB_DIR
);
685 /* no userBundlePath */
688 char *userHome
= getenv("HOME");
689 if(userHome
== NULL
) {
690 /* FIXME - what now, batman? */
691 MSDebug("updateDataBases: no HOME");
694 sprintf(userBundlePath
, "%s/%s", userHome
, MDS_USER_BUNDLE
);
696 /* DBs go in a per-UID directory in the system MDS DB directory */
697 sprintf(userDbFileDir
, "%s/%d", MDS_SYSTEM_DB_DIR
, (int)(getuid()));
699 sprintf(userObjDbFilePath
, "%s/%s", userDbFileDir
, MDS_OBJECT_DB_NAME
);
700 sprintf(userDirectDbFilePath
, "%s/%s", userDbFileDir
, MDS_DIRECT_DB_NAME
);
701 sprintf(userDbLockPath
, "%s/%s", userDbFileDir
, MDS_LOCK_FILE_NAME
);
704 * Create the per-user directory first...that's where the lock we'll be using
705 * lives. Our createDir() is tolerant of EEXIST errors.
708 if(createDir(userDbFileDir
)) {
709 /* We'll just have to limp along using the read-only system DBs */
710 Syslog::alert("Error creating %s", userDbFileDir
);
711 MSDebug("Error creating user DBs; using system DBs");
712 mModule
.setDbPath(MDS_SYSTEM_DB_DIR
);
717 /* always release mLockFd no matter what happens */
718 if(!obtainLock(userDbLockPath
, mLockFd
, DB_LOCK_TIMEOUT
)) {
719 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
723 if(createdSystemDb
) {
724 /* initial creation of system DBs by user - start from scratch */
725 unlink(userObjDbFilePath
);
726 unlink(userDirectDbFilePath
);
730 * System DBs exist and are as up-to-date as we are allowed to make them.
731 * Create per-user DBs if they don't exist.
733 if(createdSystemDb
|| //Êoptimization - if this is true, the
734 // per-user DBs do not exist since we just
736 !doFilesExist(userObjDbFilePath
, userDirectDbFilePath
,
739 /* copy system DBs to user DBs */
740 MSDebug("copying system DBs to user at %s", userDbFileDir
);
741 copySystemDbs(userDbFileDir
);
744 MSDebug("Using existing user DBs at %s", userDbFileDir
);
748 MSDebug("Using system DBs only");
752 * Update per-user DBs from all three sources (System.framework,
753 * System bundles, user bundles) as appropriate. Note that if we
754 * just created the system DBs, we don't have to update with
755 * respect to system framework or system bundles.
757 DbFilesInfo
dbFiles(*this, userDbFileDir
);
758 if(!createdSystemDb
) {
759 dbFiles
.removeOutdatedPlugins();
760 dbFiles
.updateSystemDbInfo(MDS_SYSTEM_PATH
, MDS_BUNDLE_PATH
);
763 /* root doesn't have user bundles */
764 if(checkUserBundles(userBundlePath
)) {
765 dbFiles
.updateForBundleDir(userBundlePath
);
768 mModule
.setDbPath(userDbFileDir
);
769 } /* main block protected by mLockFd */
771 releaseLock(mLockFd
);
774 mModule
.lastScanIsNow();
775 releaseLock(mLockFd
);
779 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
781 void MDSSession::removeRecordsForGuid(
783 CSSM_DB_HANDLE dbHand
)
786 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
787 CSSM_HANDLE resultHand
;
788 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
789 CSSM_SELECTION_PREDICATE predicate
;
792 /* don't want any attributes back, just a record ptr */
793 recordAttrs
.DataRecordType
= CSSM_DL_DB_RECORD_ANY
;
794 recordAttrs
.SemanticInformation
= 0;
795 recordAttrs
.NumberOfAttributes
= 0;
796 recordAttrs
.AttributeData
= NULL
;
798 /* one predicate, == guid */
799 predicate
.DbOperator
= CSSM_DB_EQUAL
;
800 predicate
.Attribute
.Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
801 predicate
.Attribute
.Info
.Label
.AttributeName
= "ModuleID";
802 predicate
.Attribute
.Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
803 predData
.Data
= (uint8
*)guid
;
804 predData
.Length
= strlen(guid
) + 1;
805 predicate
.Attribute
.Value
= &predData
;
806 predicate
.Attribute
.NumberOfValues
= 1;
808 query
.RecordType
= CSSM_DL_DB_RECORD_ANY
;
809 query
.Conjunctive
= CSSM_DB_NONE
;
810 query
.NumSelectionPredicates
= 1;
811 query
.SelectionPredicate
= &predicate
;
812 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
813 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
814 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
817 * Each search starts from scratch - not sure if we can delete a record
818 * associated with an active query and continue on with that query.
822 DLQuery
perryQuery(query
);
823 resultHand
= DataGetFirst(dbHand
,
830 MSDebug("...deleting a record for guid %s", guid
);
831 DataDelete(dbHand
, *record
);
832 DataAbortQuery(dbHand
, resultHand
);
835 MSDebug("exception (1) while deleting record for guid %s", guid
);
840 FreeUniqueRecord(dbHand
, *record
);
846 MSDebug("exception (2) while deleting record for guid %s", guid
);
851 * Determine if system databases are present.
852 * If the purge argument is true, we'll ensure that either both or neither
853 * DB files exist on exit; in that case caller need to hold MDS_LOCK_FILE_PATH.
855 bool MDSSession::systemDatabasesPresent(bool purge
)
860 /* this can throw on a failed attempt to delete sole existing file */
861 if(doFilesExist(MDS_OBJECT_DB_PATH
, MDS_DIRECT_DB_PATH
, purge
)) {
872 * Given a DB name (which is used as an absolute path) and an array of
873 * RelationInfos, create a DB.
876 MDSSession::createSystemDatabase(
878 const RelationInfo
*relationInfo
,
879 unsigned numRelations
,
880 CSSM_BOOL autoCommit
,
882 CSSM_DB_HANDLE
&dbHand
) // RETURNED
885 CSSM_DBINFO_PTR dbInfoP
= &dbInfo
;
887 memset(dbInfoP
, 0, sizeof(CSSM_DBINFO
));
888 dbInfoP
->NumberOfRecordTypes
= numRelations
;
889 dbInfoP
->IsLocal
= CSSM_TRUE
; // TBD - what does this mean?
890 dbInfoP
->AccessPath
= NULL
; // TBD
892 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
894 unsigned size
= sizeof(CSSM_DB_PARSING_MODULE_INFO
) * numRelations
;
895 dbInfoP
->DefaultParsingModules
= (CSSM_DB_PARSING_MODULE_INFO_PTR
)malloc(size
);
896 memset(dbInfoP
->DefaultParsingModules
, 0, size
);
897 size
= sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO
) * numRelations
;
898 dbInfoP
->RecordAttributeNames
= (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR
)malloc(size
);
899 memset(dbInfoP
->RecordAttributeNames
, 0, size
);
900 size
= sizeof(CSSM_DB_RECORD_INDEX_INFO
) * numRelations
;
901 dbInfoP
->RecordIndexes
= (CSSM_DB_RECORD_INDEX_INFO_PTR
)malloc(size
);
902 memset(dbInfoP
->RecordIndexes
, 0, size
);
904 /* cook up attribute and index info for each relation */
906 for(relation
=0; relation
<numRelations
; relation
++) {
907 const struct RelationInfo
*relp
= &relationInfo
[relation
]; // source
908 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo
=
909 &dbInfoP
->RecordAttributeNames
[relation
]; // dest 1
910 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo
=
911 &dbInfoP
->RecordIndexes
[relation
]; // dest 2
913 attrInfo
->DataRecordType
= relp
->DataRecordType
;
914 attrInfo
->NumberOfAttributes
= relp
->NumberOfAttributes
;
915 attrInfo
->AttributeInfo
= (CSSM_DB_ATTRIBUTE_INFO_PTR
)relp
->AttributeInfo
;
917 indexInfo
->DataRecordType
= relp
->DataRecordType
;
918 indexInfo
->NumberOfIndexes
= relp
->NumberOfIndexes
;
919 indexInfo
->IndexInfo
= (CSSM_DB_INDEX_INFO_PTR
)relp
->IndexInfo
;
922 /* set autocommit and mode */
923 CSSM_APPLEDL_OPEN_PARAMETERS openParams
;
924 memset(&openParams
, 0, sizeof(openParams
));
925 openParams
.length
= sizeof(openParams
);
926 openParams
.version
= CSSM_APPLEDL_OPEN_PARAMETERS_VERSION
;
927 openParams
.autoCommit
= autoCommit
;
928 openParams
.mask
= kCSSM_APPLEDL_MASK_MODE
;
929 openParams
.mode
= mode
;
935 CSSM_DB_ACCESS_READ
| CSSM_DB_ACCESS_WRITE
,
936 NULL
, // CredAndAclEntry
941 MSDebug("Error on DbCreate");
942 free(dbInfoP
->DefaultParsingModules
);
943 free(dbInfoP
->RecordAttributeNames
);
944 free(dbInfoP
->RecordIndexes
);
947 free(dbInfoP
->DefaultParsingModules
);
948 free(dbInfoP
->RecordAttributeNames
);
949 free(dbInfoP
->RecordIndexes
);
954 * Create system databases from scratch if they do not already exist.
955 * MDS_LOCK_FILE_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
956 * exist (that's our caller's job, before acquiring MDS_LOCK_FILE_PATH).
957 * Returns true if we actually built the files, false if they already
960 bool MDSSession::createSystemDatabases(
961 CSSM_BOOL autoCommit
,
964 CSSM_DB_HANDLE objectDbHand
= 0;
965 CSSM_DB_HANDLE directoryDbHand
= 0;
967 assert((getuid() == (uid_t
)0) || !SYSTEM_MDS_ROOT_ONLY
);
968 if(systemDatabasesPresent(true)) {
969 /* both databases exist as regular files - we're done */
970 MSDebug("system DBs already exist");
974 /* create two DBs - any exception here results in deleting both of them */
975 MSDebug("Creating MDS DBs");
977 createSystemDatabase(MDS_OBJECT_DB_PATH
, &kObjectRelation
, 1,
978 autoCommit
, mode
, objectDbHand
);
979 DbClose(objectDbHand
);
981 createSystemDatabase(MDS_DIRECT_DB_PATH
, kMDSRelationInfo
, kNumMdsRelations
,
982 autoCommit
, mode
, directoryDbHand
);
983 DbClose(directoryDbHand
);
987 MSDebug("Error creating MDS DBs - deleting both DB files");
988 unlink(MDS_OBJECT_DB_PATH
);
989 unlink(MDS_DIRECT_DB_PATH
);
996 * DbFilesInfo helper class
999 /* Note both DB files MUST exist at construction time */
1000 MDSSession::DbFilesInfo::DbFilesInfo(
1001 MDSSession
&session
,
1002 const char *dbPath
) :
1008 assert(strlen(dbPath
) < MAXPATHLEN
);
1009 strcpy(mDbPath
, dbPath
);
1011 /* stat the two DB files, snag the later timestamp */
1012 char path
[MAXPATHLEN
];
1013 sprintf(path
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
1015 int rtn
= ::stat(path
, &sb
);
1018 MSDebug("Error %d statting DB file %s", error
, path
);
1019 UnixError::throwMe(error
);
1021 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1022 sprintf(path
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1023 rtn
= ::stat(path
, &sb
);
1026 MSDebug("Error %d statting DB file %s", error
, path
);
1027 UnixError::throwMe(error
);
1029 if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1030 mLaterTimestamp
= sb
.st_mtimespec
.tv_sec
;
1034 MDSSession::DbFilesInfo::~DbFilesInfo()
1036 if(mObjDbHand
!= 0) {
1037 /* autocommit on, henceforth */
1038 mSession
.PassThrough(mObjDbHand
,
1039 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1040 reinterpret_cast<void *>(CSSM_TRUE
),
1042 mSession
.DbClose(mObjDbHand
);
1045 if(mDirectDbHand
!= 0) {
1046 mSession
.PassThrough(mDirectDbHand
,
1047 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1048 reinterpret_cast<void *>(CSSM_TRUE
),
1050 mSession
.DbClose(mDirectDbHand
);
1055 /* lazy evaluation of both DB handlesÊ*/
1056 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::objDbHand()
1058 if(mObjDbHand
!= 0) {
1061 char fullPath
[MAXPATHLEN
+ 1];
1062 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_OBJECT_DB_NAME
);
1063 mObjDbHand
= mSession
.dbOpen(fullPath
);
1067 CSSM_DB_HANDLE
MDSSession::DbFilesInfo::directDbHand()
1069 if(mDirectDbHand
!= 0) {
1070 return mDirectDbHand
;
1072 char fullPath
[MAXPATHLEN
+ 1];
1073 sprintf(fullPath
, "%s/%s", mDbPath
, MDS_DIRECT_DB_NAME
);
1074 mDirectDbHand
= mSession
.dbOpen(fullPath
);
1075 return mDirectDbHand
;
1079 * Update the info for System.framework and the system bundles.
1081 void MDSSession::DbFilesInfo::updateSystemDbInfo(
1082 const char *systemPath
, // e.g., /System/Library/Frameworks
1083 const char *bundlePath
) // e.g., /System/Library/Security
1085 /* System.framework - CSSM and built-in modules */
1086 char fullPath
[MAXPATHLEN
];
1087 sprintf(fullPath
, "%s/%s", systemPath
, MDS_SYSTEM_FRAME
);
1088 updateForBundle(fullPath
);
1090 /* Standard loadable bundles */
1091 updateForBundleDir(bundlePath
);
1095 MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1096 const CSSM_DATA
&guid
)
1098 assert(guid
.Length
<= MAX_GUID_LEN
);
1099 assert(guid
.Length
!= 0);
1100 memmove(mGuid
, guid
.Data
, guid
.Length
);
1101 if(mGuid
[guid
.Length
- 1] != '\0') {
1102 mGuid
[guid
.Length
] = '\0';
1107 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1108 * If so, add to tbdVector.
1110 void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1111 const CSSM_DATA
&pathValue
,
1112 const CSSM_DATA
&guidValue
,
1113 TbdVector
&tbdVector
)
1115 /* stat the specified plugin */
1117 bool obsolete
= false;
1118 int rtn
= ::stat((char *)pathValue
.Data
, &sb
);
1120 /* not there or inaccessible; delete */
1123 else if(sb
.st_mtimespec
.tv_sec
> mLaterTimestamp
) {
1124 /* timestamp of plugin's main directory later than that of DBs */
1128 TbdRecord
*tbdRecord
= new TbdRecord(guidValue
);
1129 tbdVector
.push_back(tbdRecord
);
1130 MSDebug("checkOutdatedPlugin: flagging %s obsolete", pathValue
.Data
);
1135 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1136 * i.e., with any path) which are either not present on disk, or which
1137 * have changed since dbFiles.laterTimestamp().
1139 void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1142 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1143 CSSM_HANDLE resultHand
;
1144 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1145 CSSM_DB_ATTRIBUTE_DATA theAttrs
[2];
1146 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
;
1147 TbdVector tbdRecords
;
1150 * First, scan object directory. All we need are the path and GUID attributes.
1152 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1153 recordAttrs
.SemanticInformation
= 0;
1154 recordAttrs
.NumberOfAttributes
= 2;
1155 recordAttrs
.AttributeData
= theAttrs
;
1157 attrInfo
= &theAttrs
[0].Info
;
1158 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1159 attrInfo
->Label
.AttributeName
= "ModuleID";
1160 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1161 theAttrs
[0].NumberOfValues
= 0;
1162 theAttrs
[0].Value
= NULL
;
1163 attrInfo
= &theAttrs
[1].Info
;
1164 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1165 attrInfo
->Label
.AttributeName
= "Path";
1166 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1167 theAttrs
[1].NumberOfValues
= 0;
1168 theAttrs
[1].Value
= NULL
;
1170 /* just search by recordType, no predicates */
1171 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1172 query
.Conjunctive
= CSSM_DB_NONE
;
1173 query
.NumSelectionPredicates
= 0;
1174 query
.SelectionPredicate
= NULL
;
1175 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1176 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1177 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1179 DLQuery
perryQuery(query
);
1181 resultHand
= mSession
.DataGetFirst(objDbHand(),
1188 MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1192 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1195 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1196 checkOutdatedPlugin(*theAttrs
[1].Value
, *theAttrs
[0].Value
,
1200 MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1202 for(unsigned dex
=0; dex
<2; dex
++) {
1203 if(theAttrs
[dex
].Value
) {
1204 if(theAttrs
[dex
].Value
->Data
) {
1205 mSession
.free(theAttrs
[dex
].Value
->Data
);
1207 mSession
.free(theAttrs
[dex
].Value
);
1212 /* empty Object DB - we're done */
1213 MSDebug("removeOutdatedPlugins: empty object DB");
1217 /* now the rest of the object DB records */
1219 bool brtn
= mSession
.DataGetNext(objDbHand(),
1229 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1231 if(theAttrs
[0].NumberOfValues
&& theAttrs
[1].NumberOfValues
) {
1232 checkOutdatedPlugin(*theAttrs
[1].Value
,
1237 MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1239 for(unsigned dex
=0; dex
<2; dex
++) {
1240 if(theAttrs
[dex
].Value
) {
1241 if(theAttrs
[dex
].Value
->Data
) {
1242 mSession
.free(theAttrs
[dex
].Value
->Data
);
1244 mSession
.free(theAttrs
[dex
].Value
);
1248 /* no DataAbortQuery needed; we scanned until completion */
1251 * We have a vector of plugins to be deleted. Remove all records from both
1252 * DBs associated with the plugins, as specified by guid.
1254 unsigned numRecords
= tbdRecords
.size();
1255 for(unsigned i
=0; i
<numRecords
; i
++) {
1256 TbdRecord
*tbdRecord
= tbdRecords
[i
];
1257 mSession
.removeRecordsForGuid(tbdRecord
->guid(), objDbHand());
1258 mSession
.removeRecordsForGuid(tbdRecord
->guid(), directDbHand());
1260 for(unsigned i
=0; i
<numRecords
; i
++) {
1261 delete tbdRecords
[i
];
1267 * Update DBs for all bundles in specified directory.
1269 void MDSSession::DbFilesInfo::updateForBundleDir(
1270 const char *bundleDirPath
)
1272 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1273 * much too heavyweight */
1274 MSDebug("...updating DBs for dir %s", bundleDirPath
);
1275 DIR *dir
= opendir(bundleDirPath
);
1277 MSDebug("updateForBundleDir: error %d opening %s", errno
, bundleDirPath
);
1281 char fullPath
[MAXPATHLEN
];
1282 while ((dp
= readdir(dir
)) != NULL
) {
1284 sprintf(fullPath
, "%s/%s", bundleDirPath
, dp
->d_name
);
1285 updateForBundle(fullPath
);
1292 * lookup by path - just returns true if there is a record assoociated with the path
1295 bool MDSSession::DbFilesInfo::lookupForPath(
1299 CSSM_DB_UNIQUE_RECORD_PTR record
= NULL
;
1300 CSSM_HANDLE resultHand
= 0;
1301 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs
;
1302 CSSM_DB_ATTRIBUTE_DATA theAttr
;
1303 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo
= &theAttr
.Info
;
1304 CSSM_SELECTION_PREDICATE predicate
;
1307 recordAttrs
.DataRecordType
= MDS_OBJECT_RECORDTYPE
;
1308 recordAttrs
.SemanticInformation
= 0;
1309 recordAttrs
.NumberOfAttributes
= 1;
1310 recordAttrs
.AttributeData
= &theAttr
;
1312 attrInfo
->AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1313 attrInfo
->Label
.AttributeName
= "Path";
1314 attrInfo
->AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1316 theAttr
.NumberOfValues
= 0;
1317 theAttr
.Value
= NULL
;
1319 predicate
.DbOperator
= CSSM_DB_EQUAL
;
1320 predicate
.Attribute
.Info
.AttributeNameFormat
= CSSM_DB_ATTRIBUTE_NAME_AS_STRING
;
1321 predicate
.Attribute
.Info
.Label
.AttributeName
= "Path";
1322 predicate
.Attribute
.Info
.AttributeFormat
= CSSM_DB_ATTRIBUTE_FORMAT_STRING
;
1323 predData
.Data
= (uint8
*)path
;
1324 predData
.Length
= strlen(path
) + 1;
1325 predicate
.Attribute
.Value
= &predData
;
1326 predicate
.Attribute
.NumberOfValues
= 1;
1328 query
.RecordType
= MDS_OBJECT_RECORDTYPE
;
1329 query
.Conjunctive
= CSSM_DB_NONE
;
1330 query
.NumSelectionPredicates
= 1;
1331 query
.SelectionPredicate
= &predicate
;
1332 query
.QueryLimits
.TimeLimit
= 0; // FIXME - meaningful?
1333 query
.QueryLimits
.SizeLimit
= 1; // FIXME - meaningful?
1334 query
.QueryFlags
= 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1338 DLQuery
perryQuery(query
);
1339 resultHand
= mSession
.DataGetFirst(objDbHand(),
1349 mSession
.FreeUniqueRecord(mObjDbHand
, *record
);
1354 if(resultHand
&& ourRtn
) {
1355 /* more resulting pending; terminate the search */
1357 mSession
.DataAbortQuery(mObjDbHand
, resultHand
);
1360 MSDebug("exception on DataAbortQuery in lookupForPath");
1364 if(theAttr
.Value
->Data
) {
1365 mSession
.free(theAttr
.Value
->Data
);
1367 mSession
.free(theAttr
.Value
);
1372 /* update entry for one bundle, which is known to exist */
1373 void MDSSession::DbFilesInfo::updateForBundle(
1374 const char *bundlePath
)
1376 MSDebug("...updating DBs for bundle %s", bundlePath
);
1378 /* Quick lookup - do we have ANY entry for a bundle with this path? */
1379 if(lookupForPath(bundlePath
)) {
1380 /* Yep, we're done */
1383 MDSAttrParser
parser(bundlePath
,
1387 parser
.parseAttrs();
1390 /* DB autocommit on/off */
1391 void MDSSession::DbFilesInfo::autoCommit(CSSM_BOOL val
)
1394 mSession
.PassThrough(objDbHand(),
1395 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1396 reinterpret_cast<void *>(val
),
1398 mSession
.PassThrough(directDbHand(),
1399 CSSM_APPLEFILEDL_TOGGLE_AUTOCOMMIT
,
1400 reinterpret_cast<void *>(val
),
1404 MSDebug("DbFilesInfo::autoCommit error!");
1409 } // end namespace Security