]> git.saurik.com Git - apple/security.git/blob - cdsa/mds/MDSSession.cpp
Security-28.tar.gz
[apple/security.git] / cdsa / mds / MDSSession.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
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
8 * using this file.
9 *
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.
16 */
17
18
19 #include "MDSSession.h"
20
21 #include <Security/DbContext.h>
22 #include "MDSModule.h"
23
24 #include <memory>
25 #include <Security/cssmerr.h>
26 #include <Security/utilities.h>
27 #include <Security/logging.h>
28
29 #include <sys/types.h>
30 #include <dirent.h>
31 #include <fcntl.h>
32 #include <time.h>
33
34 // Location of security plugins.
35
36 #define kPluginPath "/System/Library/Security/"
37
38 // Location of MDS database and lock files.
39
40 #define kDatabasePath "/var/tmp/"
41 #define kLockFilename kDatabasePath "mds.lock"
42
43 // Minimum interval, in seconds, between rescans for plugin changes.
44
45 #define kScanInterval 10
46
47 //
48 // Get the current time in a format that matches that in which
49 // a file's modification time is expressed.
50 //
51
52 static void
53 getCurrentTime(struct timespec &now)
54 {
55 struct timeval tv;
56 gettimeofday(&tv, NULL);
57 TIMEVAL_TO_TIMESPEC(&tv, &now);
58 }
59
60 //
61 // Create an MDS session.
62 //
63
64 MDSSession::MDSSession (const Guid *inCallerGuid,
65 const CSSM_MEMORY_FUNCS &inMemoryFunctions) :
66 DatabaseSession(MDSModule::get().databaseManager()),
67 mCssmMemoryFunctions (inMemoryFunctions),
68 mLockFd(-1)
69 {
70 fprintf(stderr, "MDSSession::MDSSession\n");
71
72 mCallerGuidPresent = inCallerGuid != nil;
73 if (mCallerGuidPresent)
74 mCallerGuid = *inCallerGuid;
75
76 // make sure the MDS databases have been created, and the required
77 // tables have been constructed
78 initializeDatabases();
79
80 // schedule a scan for plugin changes
81 getCurrentTime(mLastScanTime);
82 }
83
84 MDSSession::~MDSSession ()
85 {
86 fprintf(stderr, "MDSSession::~MDSSession\n");
87 releaseLock();
88 }
89
90 void
91 MDSSession::terminate ()
92 {
93 fprintf(stderr, "MDSSession::terminate\n");
94
95 closeAll();
96 }
97
98 //
99 // In this implementation, install() does nothing, since the databases
100 // are implicitly created as needed by initialize().
101 //
102
103 void
104 MDSSession::install ()
105 {
106 // this space intentionally left blank
107 }
108
109 //
110 // In this implementation, the uninstall() call is not supported since
111 // we do not allow programmatic deletion of the MDS databases.
112 //
113
114 void
115 MDSSession::uninstall ()
116 {
117 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
118 }
119
120 //
121 // Obtain and free a list of names of current databases.
122 //
123
124 void
125 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList)
126 {
127 outNameList = mDatabaseManager.getDbNames(*this);
128 }
129
130 void
131 MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList)
132 {
133 mDatabaseManager.freeNameList(*this, inNameList);
134 }
135
136 //
137 // Scan the plugin directory.
138 //
139
140 static bool intervalHasElapsed(const struct timespec &then, const struct timespec &now,
141 int intervalSeconds)
142 {
143 return (now.tv_sec - then.tv_sec > intervalSeconds) ||
144 ((now.tv_sec - then.tv_sec == intervalSeconds) && (now.tv_nsec >= then.tv_nsec));
145 }
146
147 static bool operator <=(const struct timespec &a, const struct timespec &b)
148 {
149 return (a.tv_sec < b.tv_sec) || ((a.tv_sec == b.tv_sec) && (a.tv_nsec <= b.tv_nsec));
150 }
151
152 class PluginInfo
153 {
154 public:
155 PluginInfo(const char *pluginName, const struct timespec &modTime) : mModTime(modTime) {
156 mPluginName = new char[strlen(pluginName) + 1];
157 strcpy(mPluginName, pluginName);
158 }
159
160 ~PluginInfo() { delete [] mPluginName; }
161
162 const char *name() { return mPluginName; }
163 const struct timespec &modTime() { return mModTime; }
164
165 private:
166 char *mPluginName;
167 struct timespec mModTime;
168 };
169
170 //
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.
175 //
176
177 bool
178 MDSSession::obtainLock(int timeout /* = 0 */)
179 {
180 static const int kRetryDelay = 250; // ms
181
182 if (mLockFd >= 0)
183 // this session already holds the lock
184 return true;
185
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);
191 }
192
193 return (mLockFd != -1);
194 }
195
196 //
197 // Release the exclusive lock over the MDS databases. If this session
198 // does not hold the lock, this method does nothing.
199 //
200
201 void
202 MDSSession::releaseLock()
203 {
204 if (mLockFd != -1) {
205 close(mLockFd);
206 unlink(kLockFilename);
207 mLockFd = -1;
208 }
209 }
210
211 //
212 // If necessary, create the two MDS databases and construct the required
213 // tables in each database.
214 //
215
216 void
217 MDSSession::initializeDatabases()
218 {
219 printf("MDSSession::initializeDatabases\n");
220
221 static int kLockTimeout = 2000; // ms
222
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
225
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);
230
231 try {
232 // check for the existence of the MDS database file; if it exists,
233 // assume that the databases have already been properly created
234
235 // look for added/removed/changed plugins
236
237 scanPluginDirectory();
238 }
239 catch (...) {
240 releaseLock();
241 throw;
242 }
243
244 // release the exclusive lock
245
246 releaseLock();
247 }
248
249 //
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.
256 //
257
258 void
259 MDSSession::updateDatabases()
260 {
261 // get the current time in the appropriate format
262
263 struct timespec now;
264 getCurrentTime(now);
265
266 if (!intervalHasElapsed(mLastScanTime, now, kScanInterval))
267 // its not yet time to rescan
268 return;
269
270 // regardless of what happens, we don't want to scan again for a while, so reset
271 // the last scan time before proceeding
272
273 mLastScanTime = now;
274
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
277
278 if (!obtainLock())
279 return;
280
281 // we want to make sure that the lock gets released at all costs, hence
282 // this try block:
283
284 try {
285 scanPluginDirectory();
286 }
287 catch (...) {
288 releaseLock();
289 throw;
290 }
291
292 releaseLock();
293 }
294
295 //
296 // Determine if a filesystem object is a bundle that should be considered
297 // as a potential CDSA module by MDS.
298 //
299
300 static bool
301 isBundle(const char *path)
302 {
303 static const char *bundleSuffix = ".bundle";
304
305 int suffixLen = strlen(bundleSuffix);
306 int len = strlen(path);
307
308 return (len >= suffixLen) && !strcmp(path + len - suffixLen, bundleSuffix);
309 }
310
311 //
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
315 // already exist.
316 //
317
318 void
319 MDSSession::scanPluginDirectory()
320 {
321 printf("MDSSession::scanPluginDirectory\n");
322
323 // check the modification time on the plugin directory: if it has not changed
324 // since the last scan, we're done
325
326 struct stat sb;
327 if (stat(kPluginPath, &sb)) {
328 // can't stat the plugin directory...
329 Syslog::warning("MDS: cannot stat plugin directory \"%s\"", kPluginPath);
330 return;
331 }
332
333 if (sb.st_mtimespec <= mLastScanTime)
334 // no changes, we're done until its time for the next scan
335 return;
336
337 // attempt to open the plugin directory
338
339 DIR *dir = opendir(kPluginPath);
340 if (dir == NULL) {
341 // no plugin directory, hence no modules. clear the MDS directory
342 // and log a warning
343 Syslog::warning("MDS: cannot open plugin directory \"%s\"", kPluginPath);
344 return;
345 }
346
347 // build a list of the plugins are are currently in the directory, along with
348 // their modification times
349
350 struct dirent *dp;
351 PluginInfoList pluginList;
352
353 char tempPath[PATH_MAX];
354
355 while ((dp = readdir(dir)) != NULL) {
356
357 // stat the file to get its modification time
358
359 strncpy(tempPath, kPluginPath, PATH_MAX);
360 strncat(tempPath, dp->d_name, PATH_MAX - strlen(kPluginPath));
361
362 struct stat sb;
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));
368 }
369 }
370
371 closedir(dir);
372
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
376
377 removeOutdatedPlugins(pluginList);
378
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
382
383 insertNewPlugins(pluginList);
384
385 // free the list of current plugins
386
387 for_each_delete(pluginList.begin(), pluginList.end());
388 }
389
390 void
391 MDSSession::removeOutdatedPlugins(const PluginInfoList &pluginList)
392 {
393 PluginInfoList::const_iterator it;
394 for (it = pluginList.begin(); it != pluginList.end(); it++)
395 fprintf(stderr, "%s\n", (*it)->name());
396 }
397
398 void
399 MDSSession::insertNewPlugins(const PluginInfoList &pluginList)
400 {
401 }
402