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"
25 #include <Security/cssmerr.h>
26 #include <Security/utilities.h>
27 #include <Security/logging.h>
29 #include <sys/types.h>
34 // Location of security plugins.
36 #define kPluginPath "/System/Library/Security/"
38 // Location of MDS database and lock files.
40 #define kDatabasePath "/var/tmp/"
41 #define kLockFilename kDatabasePath "mds.lock"
43 // Minimum interval, in seconds, between rescans for plugin changes.
45 #define kScanInterval 10
48 // Get the current time in a format that matches that in which
49 // a file's modification time is expressed.
53 getCurrentTime(struct timespec
&now
)
56 gettimeofday(&tv
, NULL
);
57 TIMEVAL_TO_TIMESPEC(&tv
, &now
);
61 // Create an MDS session.
64 MDSSession::MDSSession (const Guid
*inCallerGuid
,
65 const CSSM_MEMORY_FUNCS
&inMemoryFunctions
) :
66 DatabaseSession(MDSModule::get().databaseManager()),
67 mCssmMemoryFunctions (inMemoryFunctions
),
70 fprintf(stderr
, "MDSSession::MDSSession\n");
72 mCallerGuidPresent
= inCallerGuid
!= nil
;
73 if (mCallerGuidPresent
)
74 mCallerGuid
= *inCallerGuid
;
76 // make sure the MDS databases have been created, and the required
77 // tables have been constructed
78 initializeDatabases();
80 // schedule a scan for plugin changes
81 getCurrentTime(mLastScanTime
);
84 MDSSession::~MDSSession ()
86 fprintf(stderr
, "MDSSession::~MDSSession\n");
91 MDSSession::terminate ()
93 fprintf(stderr
, "MDSSession::terminate\n");
99 // In this implementation, install() does nothing, since the databases
100 // are implicitly created as needed by initialize().
104 MDSSession::install ()
106 // this space intentionally left blank
110 // In this implementation, the uninstall() call is not supported since
111 // we do not allow programmatic deletion of the MDS databases.
115 MDSSession::uninstall ()
117 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED
);
121 // Obtain and free a list of names of current databases.
125 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR
&outNameList
)
127 outNameList
= mDatabaseManager
.getDbNames(*this);
131 MDSSession::FreeNameList(CSSM_NAME_LIST
&inNameList
)
133 mDatabaseManager
.freeNameList(*this, inNameList
);
137 // Scan the plugin directory.
140 static bool intervalHasElapsed(const struct timespec
&then
, const struct timespec
&now
,
143 return (now
.tv_sec
- then
.tv_sec
> intervalSeconds
) ||
144 ((now
.tv_sec
- then
.tv_sec
== intervalSeconds
) && (now
.tv_nsec
>= then
.tv_nsec
));
147 static bool operator <=(const struct timespec
&a
, const struct timespec
&b
)
149 return (a
.tv_sec
< b
.tv_sec
) || ((a
.tv_sec
== b
.tv_sec
) && (a
.tv_nsec
<= b
.tv_nsec
));
155 PluginInfo(const char *pluginName
, const struct timespec
&modTime
) : mModTime(modTime
) {
156 mPluginName
= new char[strlen(pluginName
) + 1];
157 strcpy(mPluginName
, pluginName
);
160 ~PluginInfo() { delete [] mPluginName
; }
162 const char *name() { return mPluginName
; }
163 const struct timespec
&modTime() { return mModTime
; }
167 struct timespec mModTime
;
171 // Attempt to obtain an exclusive lock over the the MDS databases. The
172 // parameter is the maximum amount of time, in milliseconds, to spend
173 // trying to obtain the lock. A value of zero means to return failure
174 // right away if the lock cannot be obtained.
178 MDSSession::obtainLock(int timeout
/* = 0 */)
180 static const int kRetryDelay
= 250; // ms
183 // this session already holds the lock
186 mLockFd
= open(kLockFilename
, O_CREAT
| O_EXCL
, 0544);
187 while (mLockFd
== -1 && timeout
>= kRetryDelay
) {
188 timeout
-= kRetryDelay
;
189 usleep(1000 * kRetryDelay
);
190 mLockFd
= open(kLockFilename
, O_CREAT
| O_EXCL
, 0544);
193 return (mLockFd
!= -1);
197 // Release the exclusive lock over the MDS databases. If this session
198 // does not hold the lock, this method does nothing.
202 MDSSession::releaseLock()
206 unlink(kLockFilename
);
212 // If necessary, create the two MDS databases and construct the required
213 // tables in each database.
217 MDSSession::initializeDatabases()
219 printf("MDSSession::initializeDatabases\n");
221 static int kLockTimeout
= 2000; // ms
223 // obtain an exclusive lock. in this case we really want the lock, so
224 // if it's not immediately available we wait around for a bit
226 if (!obtainLock(kLockTimeout
))
227 // something is wrong; either a stale lock file is lying around or
228 // some other process is stuck updating the databases
229 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR
);
232 // check for the existence of the MDS database file; if it exists,
233 // assume that the databases have already been properly created
235 // look for added/removed/changed plugins
237 scanPluginDirectory();
244 // release the exclusive lock
250 // Update the databases due to added/removed/changed plugins. This obtains
251 // an exclusive lock over the databases, if possible, and then scans the
252 // module path. If the lock cannot be obtained, it does nothing. The intent
253 // is that this will be called periodically, so a failure at any given time
254 // is not a big deal and may simply imply that another process is already
255 // updating the MDS databases.
259 MDSSession::updateDatabases()
261 // get the current time in the appropriate format
266 if (!intervalHasElapsed(mLastScanTime
, now
, kScanInterval
))
267 // its not yet time to rescan
270 // regardless of what happens, we don't want to scan again for a while, so reset
271 // the last scan time before proceeding
275 // obtain a lock to avoid having multiple processes scanning for changed plugins;
276 // if the lock cannot be obtained immediately, just return and do nothing
281 // we want to make sure that the lock gets released at all costs, hence
285 scanPluginDirectory();
296 // Determine if a filesystem object is a bundle that should be considered
297 // as a potential CDSA module by MDS.
301 isBundle(const char *path
)
303 static const char *bundleSuffix
= ".bundle";
305 int suffixLen
= strlen(bundleSuffix
);
306 int len
= strlen(path
);
308 return (len
>= suffixLen
) && !strcmp(path
+ len
- suffixLen
, bundleSuffix
);
312 // Scan the module directory looking for added/removed/changed plugins, and
313 // update the MDS databases accordingly. This assumes that an exclusive lock
314 // has already been obtained, and that the databases and the required tables
319 MDSSession::scanPluginDirectory()
321 printf("MDSSession::scanPluginDirectory\n");
323 // check the modification time on the plugin directory: if it has not changed
324 // since the last scan, we're done
327 if (stat(kPluginPath
, &sb
)) {
328 // can't stat the plugin directory...
329 Syslog::warning("MDS: cannot stat plugin directory \"%s\"", kPluginPath
);
333 if (sb
.st_mtimespec
<= mLastScanTime
)
334 // no changes, we're done until its time for the next scan
337 // attempt to open the plugin directory
339 DIR *dir
= opendir(kPluginPath
);
341 // no plugin directory, hence no modules. clear the MDS directory
343 Syslog::warning("MDS: cannot open plugin directory \"%s\"", kPluginPath
);
347 // build a list of the plugins are are currently in the directory, along with
348 // their modification times
351 PluginInfoList pluginList
;
353 char tempPath
[PATH_MAX
];
355 while ((dp
= readdir(dir
)) != NULL
) {
357 // stat the file to get its modification time
359 strncpy(tempPath
, kPluginPath
, PATH_MAX
);
360 strncat(tempPath
, dp
->d_name
, PATH_MAX
- strlen(kPluginPath
));
363 if (stat(tempPath
, &sb
) == 0) {
364 // do some checking to determine that this path refers to an
365 // actual bundle that is likely to be a module
366 if (isBundle(tempPath
))
367 pluginList
.push_back(new PluginInfo(tempPath
, sb
.st_mtimespec
));
373 // step 1: for any plugin in the common relation which is no longer present,
374 // or which is present but which has been modified since the last scan, remove
375 // all its records from the MDS database
377 removeOutdatedPlugins(pluginList
);
379 // step 2: for any plugin present but not in the common relation (note it may
380 // have been removed in step 1 because it was out-of-date), insert its records
381 // into the MDS database
383 insertNewPlugins(pluginList
);
385 // free the list of current plugins
387 for_each_delete(pluginList
.begin(), pluginList
.end());
391 MDSSession::removeOutdatedPlugins(const PluginInfoList
&pluginList
)
393 PluginInfoList::const_iterator it
;
394 for (it
= pluginList
.begin(); it
!= pluginList
.end(); it
++)
395 fprintf(stderr
, "%s\n", (*it
)->name());
399 MDSSession::insertNewPlugins(const PluginInfoList
&pluginList
)