]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
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_cdsa_plugin/DbContext.h> | |
22 | #include "MDSModule.h" | |
23 | #include "MDSAttrParser.h" | |
24 | #include "MDSAttrUtils.h" | |
25 | ||
26 | #include <memory> | |
27 | #include <Security/cssmerr.h> | |
28 | #include <security_utilities/logging.h> | |
29 | #include <security_utilities/debugging.h> | |
30 | #include <security_utilities/cfutilities.h> | |
31 | #include <security_cdsa_client/dlquery.h> | |
32 | #include <securityd_client/ssclient.h> | |
33 | #include <Security/mds_schema.h> | |
34 | #include <CoreFoundation/CFBundle.h> | |
35 | ||
36 | #include <sys/types.h> | |
37 | #include <sys/param.h> | |
38 | #include <dirent.h> | |
39 | #include <fcntl.h> | |
40 | #include <assert.h> | |
41 | #include <time.h> | |
42 | #include <string> | |
43 | #include <unistd.h> | |
427c49bc | 44 | #include <syslog.h> |
b1ab9ed8 A |
45 | |
46 | using namespace CssmClient; | |
47 | ||
427c49bc | 48 | /* |
b1ab9ed8 A |
49 | * The layout of the various MDS DB files on disk is as follows: |
50 | * | |
51 | * /var/db/mds -- owner = root, mode = 01777, world writable, sticky | |
52 | * system/ -- owner = root, mode = 0755 | |
53 | * mdsObject.db -- owner = root, mode = 0644, object DB | |
54 | * mdsDirectory.db -- owner = root, mode = 0644, MDS directory DB | |
55 | * mds.lock -- temporary, owner = root, protects creation of and | |
56 | * updates to previous two files | |
57 | * mds.install.lock -- owner = root, protects MDS_Install operation | |
58 | * <uid>/ -- owner = <uid>, mode = 0700 | |
59 | * mdsObject.db -- owner = <uid>, mode = 000, object DB | |
60 | * mdsDirectory.db -- owner = <uid>, mode = 000, MDS directory DB | |
61 | * mds.lock -- owner = <uid>, protects updates of previous two files | |
62 | * | |
63 | * The /var/db/mds/system directory is created at OS install time. The DB files in | |
64 | * it are created by root at MDS_Install time. The ownership and mode of this directory and | |
65 | * of its parent is also re-checked and forced to the correct state at MDS_Install time. | |
66 | * Each user has their own private directory with two DB files and a lock. The first time | |
67 | * a user accesses MDS, the per-user directory is created and the per-user DB files are | |
68 | * created as copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system | |
69 | * DB files when they are the source of these copies; this is the same mechanism | |
70 | * used by the underlying AtomicFile. | |
71 | * | |
72 | * The sticky bit in /var/db/mds ensures that users cannot modify other userss private | |
73 | * MDS directories. | |
74 | */ | |
75 | namespace Security | |
76 | { | |
77 | ||
78 | /* | |
79 | * Nominal location of Security.framework. | |
80 | */ | |
81 | #define MDS_SYSTEM_PATH "/System/Library/Frameworks" | |
82 | #define MDS_SYSTEM_FRAME "Security.framework" | |
83 | ||
84 | /* | |
85 | * Nominal location of standard plugins. | |
86 | */ | |
87 | #define MDS_BUNDLE_PATH "/System/Library/Security" | |
88 | #define MDS_BUNDLE_EXTEN ".bundle" | |
89 | ||
90 | ||
91 | /* | |
92 | * Location of MDS database and lock files. | |
93 | */ | |
94 | #define MDS_BASE_DB_DIR "/private/var/db/mds" | |
95 | #define MDS_SYSTEM_DB_COMP "system" | |
96 | #define MDS_SYSTEM_DB_DIR MDS_BASE_DB_DIR "/" MDS_SYSTEM_DB_COMP | |
97 | #define MDS_USER_DB_COMP "mds" | |
98 | ||
99 | #define MDS_LOCK_FILE_NAME "mds.lock" | |
100 | #define MDS_INSTALL_LOCK_NAME "mds.install.lock" | |
101 | #define MDS_OBJECT_DB_NAME "mdsObject.db" | |
102 | #define MDS_DIRECT_DB_NAME "mdsDirectory.db" | |
103 | ||
104 | #define MDS_INSTALL_LOCK_PATH MDS_SYSTEM_DB_DIR "/" MDS_INSTALL_LOCK_NAME | |
105 | #define MDS_OBJECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_OBJECT_DB_NAME | |
106 | #define MDS_DIRECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_DIRECT_DB_NAME | |
107 | ||
108 | /* hard coded modes and a symbolic UID for root */ | |
109 | #define MDS_BASE_DB_DIR_MODE (mode_t)0755 | |
110 | #define MDS_SYSTEM_DB_DIR_MODE (mode_t)0755 | |
111 | #define MDS_SYSTEM_DB_MODE (mode_t)0644 | |
112 | #define MDS_USER_DB_DIR_MODE (mode_t)0700 | |
113 | #define MDS_USER_DB_MODE (mode_t)0600 | |
114 | #define MDS_SYSTEM_UID (uid_t)0 | |
115 | ||
116 | /* | |
117 | * Location of per-user bundles, relative to home directory. | |
118 | * Per-user DB files are in MDS_BASE_DB_DIR/<uid>/. | |
119 | */ | |
120 | #define MDS_USER_BUNDLE "Library/Security" | |
121 | ||
122 | /* time to wait in ms trying to acquire lock */ | |
123 | #define DB_LOCK_TIMEOUT (2 * 1000) | |
124 | ||
125 | /* Minimum interval, in seconds, between rescans for plugin changes */ | |
126 | #define MDS_SCAN_INTERVAL 5 | |
127 | ||
128 | /* trace file I/O */ | |
129 | #define MSIoDbg(args...) secdebug("MDS_IO", ## args) | |
130 | ||
131 | /* Trace cleanDir() */ | |
132 | #define MSCleanDirDbg(args...) secdebug("MDS_CleanDir", ## args) | |
133 | ||
134 | static std::string GetMDSBaseDBDir(bool isRoot) | |
135 | { | |
136 | // what we return depends on whether or not we are root | |
137 | string retValue; | |
138 | if (isRoot) | |
139 | { | |
140 | retValue = MDS_SYSTEM_DB_DIR; | |
141 | } | |
142 | else | |
143 | { | |
144 | char strBuffer[PATH_MAX + 1]; | |
427c49bc A |
145 | size_t result = confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer)); |
146 | if (result == 0) | |
147 | { | |
148 | // we have an error, log it | |
149 | syslog(LOG_CRIT, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error."); | |
150 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
151 | } | |
152 | ||
b1ab9ed8 A |
153 | retValue = strBuffer; |
154 | } | |
155 | ||
156 | return retValue; | |
157 | } | |
158 | ||
159 | ||
160 | ||
161 | static std::string GetMDSDBDir() | |
162 | { | |
163 | string retValue; | |
164 | bool isRoot = geteuid() == 0; | |
165 | ||
166 | if (isRoot) | |
167 | { | |
168 | retValue = MDS_SYSTEM_DB_DIR; | |
169 | } | |
170 | else | |
171 | { | |
172 | retValue = GetMDSBaseDBDir(isRoot) + "/" + MDS_USER_DB_COMP; | |
173 | } | |
174 | ||
175 | return retValue; | |
176 | } | |
177 | ||
178 | ||
179 | ||
180 | static std::string GetMDSObjectDBPath() | |
181 | { | |
182 | return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME; | |
183 | } | |
184 | ||
185 | ||
186 | ||
187 | static std::string GetMDSDirectDBPath() | |
188 | { | |
189 | return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH; | |
190 | } | |
191 | ||
192 | ||
193 | ||
194 | static std::string GetMDSDBLockPath() | |
195 | { | |
196 | return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME; | |
197 | } | |
198 | ||
199 | ||
200 | ||
201 | /* | |
202 | * Given a path to a directory, remove everything in the directory except for the optional | |
203 | * keepFileNames. Returns 0 on success, else an errno. | |
204 | */ | |
205 | static int cleanDir( | |
206 | const char *dirPath, | |
207 | const char **keepFileNames, // array of strings, size numKeepFileNames | |
208 | unsigned numKeepFileNames) | |
209 | { | |
210 | DIR *dirp; | |
211 | struct dirent *dp; | |
212 | char fullPath[MAXPATHLEN]; | |
213 | int rtn = 0; | |
214 | ||
215 | MSCleanDirDbg("cleanDir(%s) top", dirPath); | |
216 | if ((dirp = opendir(dirPath)) == NULL) { | |
217 | rtn = errno; | |
218 | MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn); | |
219 | return rtn; | |
220 | } | |
221 | ||
222 | for(;;) { | |
223 | bool skip = false; | |
224 | const char *d_name = NULL; | |
225 | ||
226 | /* this block is for breaking on unqualified entries */ | |
227 | do { | |
228 | errno = 0; | |
229 | dp = readdir(dirp); | |
230 | if(dp == NULL) { | |
231 | /* end of directory or error */ | |
232 | rtn = errno; | |
233 | if(rtn) { | |
234 | MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath, rtn); | |
235 | } | |
236 | break; | |
237 | } | |
238 | d_name = dp->d_name; | |
239 | ||
240 | /* skip "." and ".." */ | |
241 | if( (d_name[0] == '.') && | |
242 | ( (d_name[1] == '\0') || | |
243 | ( (d_name[1] == '.') && (d_name[2] == '\0') ) ) ) { | |
244 | skip = true; | |
245 | break; | |
246 | } | |
247 | ||
248 | /* skip entries in keepFileNames */ | |
249 | for(unsigned dex=0; dex<numKeepFileNames; dex++) { | |
250 | if(!strcmp(keepFileNames[dex], d_name)) { | |
251 | skip = true; | |
252 | break; | |
253 | } | |
254 | } | |
255 | } while(0); | |
256 | if(rtn || (dp == NULL)) { | |
257 | /* one way or another, we're done */ | |
258 | break; | |
259 | } | |
260 | if(skip) { | |
261 | /* try again */ | |
262 | continue; | |
263 | } | |
264 | ||
265 | /* We have an entry to delete. Delete it, or recurse. */ | |
266 | ||
267 | snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, d_name); | |
268 | if(dp->d_type == DT_DIR) { | |
269 | /* directory. Clean it, then delete. */ | |
270 | MSCleanDirDbg("cleanDir recursing for dir %s", fullPath); | |
271 | rtn = cleanDir(fullPath, NULL, 0); | |
272 | if(rtn) { | |
273 | break; | |
274 | } | |
275 | MSCleanDirDbg("cleanDir deleting dir %s", fullPath); | |
276 | if(rmdir(fullPath)) { | |
277 | rtn = errno; | |
278 | MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn); | |
279 | break; | |
280 | } | |
281 | } | |
282 | else { | |
283 | MSCleanDirDbg("cleanDir deleting file %s", fullPath); | |
284 | if(unlink(fullPath)) { | |
285 | rtn = errno; | |
286 | MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn); | |
287 | break; | |
288 | } | |
289 | } | |
290 | ||
291 | /* | |
292 | * Back to beginning of directory for clean scan. | |
293 | * Normally we'd just do a rewinddir() here but the RAMDisk filesystem, | |
294 | * used when booting from DVD, does not implement that properly. | |
295 | */ | |
296 | closedir(dirp); | |
297 | if ((dirp = opendir(dirPath)) == NULL) { | |
298 | rtn = errno; | |
299 | MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn); | |
300 | return rtn; | |
301 | } | |
302 | } /* main loop */ | |
303 | ||
304 | closedir(dirp); | |
305 | return rtn; | |
306 | } | |
307 | ||
308 | /* | |
309 | * Determine if a file exists as regular file with specified owner. Returns true if so. | |
310 | * If the purge argument is true, and there is something at the specified path that | |
311 | * doesn't meet spec, we do everything we can to delete it. If that fails we throw | |
312 | * CssmError(CSSM_ERRCODE_MDS_ERROR). If the delete succeeds we return false. | |
313 | * Returns the stat info on success for further processing by caller. | |
314 | */ | |
315 | static bool doesFileExist( | |
316 | const char *filePath, | |
317 | uid_t forUid, | |
318 | bool purge, | |
319 | struct stat &sb) // RETURNED | |
320 | { | |
321 | MSIoDbg("stat %s in doesFileExist", filePath); | |
322 | if(lstat(filePath, &sb)) { | |
323 | /* doesn't exist or we can't even get to it. */ | |
324 | if(errno == ENOENT) { | |
325 | return false; | |
326 | } | |
327 | if(purge) { | |
328 | /* If we can't stat it we sure can't delete it. */ | |
329 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
330 | } | |
331 | return false; | |
332 | } | |
333 | ||
334 | /* it's there...how does it look? */ | |
335 | mode_t fileType = sb.st_mode & S_IFMT; | |
336 | if((fileType == S_IFREG) && (sb.st_uid == forUid)) { | |
337 | return true; | |
338 | } | |
339 | if(!purge) { | |
340 | return false; | |
341 | } | |
342 | ||
343 | /* not what we want: get rid of it. */ | |
344 | if(fileType == S_IFDIR) { | |
345 | /* directory: clean then remove */ | |
346 | if(cleanDir(filePath, NULL, 0)) { | |
347 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
348 | } | |
349 | if(rmdir(filePath)) { | |
350 | MSDebug("rmdir(%s) returned %d", filePath, errno); | |
351 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
352 | } | |
353 | } | |
354 | else { | |
355 | if(unlink(filePath)) { | |
356 | MSDebug("unlink(%s) returned %d", filePath, errno); | |
357 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
358 | } | |
359 | } | |
360 | ||
361 | /* caller should be somewhat happy */ | |
362 | return false; | |
363 | } | |
364 | ||
365 | /* | |
366 | * Determine if both of the specified DB files exist as accessible regular files with specified | |
367 | * owner. Returns true if they do. | |
368 | * | |
369 | * If the purge argument is true, we'll ensure that either both files exist with | |
370 | * the right owner, or neither of the files exist on exit. An error on that operation | |
371 | * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed). | |
372 | * Returns the stat info for both files on success for further processing by caller. | |
373 | */ | |
374 | static bool doFilesExist( | |
375 | const char *objDbFile, | |
376 | const char *directDbFile, | |
377 | uid_t forUid, | |
378 | bool purge, // false means "passive" check | |
379 | struct stat &objDbSb, // RETURNED | |
380 | struct stat &directDbSb) // RETURNED | |
381 | ||
382 | { | |
383 | bool objectExist = doesFileExist(objDbFile, forUid, purge, objDbSb); | |
384 | bool directExist = doesFileExist(directDbFile, forUid, purge, directDbSb); | |
385 | if(objectExist && directExist) { | |
386 | return true; | |
387 | } | |
388 | else if(!purge) { | |
389 | return false; | |
390 | } | |
391 | ||
392 | /* | |
393 | * At least one does not exist - ensure neither of them do. | |
394 | * Note that if we got this far, we know the one that exists is a regular file | |
395 | * so it's safe to just unlink it. | |
396 | */ | |
397 | if(objectExist) { | |
398 | if(unlink(objDbFile)) { | |
399 | MSDebug("unlink(%s) returned %d", objDbFile, errno); | |
400 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
401 | } | |
402 | } | |
403 | if(directExist) { | |
404 | if(unlink(directDbFile)) { | |
405 | MSDebug("unlink(%s) returned %d", directDbFile, errno); | |
406 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
407 | } | |
408 | } | |
409 | return false; | |
410 | } | |
411 | ||
412 | /* | |
413 | * Determine if specified directory exists with specified owner and mode. | |
414 | * Returns true if copacetic, else returns false and also indicates | |
415 | * via the directStatus out param what went wrong. | |
416 | */ | |
417 | typedef enum { | |
418 | MDS_NotPresent, /* nothing there */ | |
419 | MDS_NotDirectory, /* not a directory */ | |
420 | MDS_BadOwnerMode, /* wrong owner or mode */ | |
421 | MDS_Access /* couldn't search the directories */ | |
422 | } MdsDirectStatus; | |
423 | ||
424 | static bool doesDirectExist( | |
425 | const char *dirPath, | |
426 | uid_t forUid, | |
427 | mode_t mode, | |
428 | MdsDirectStatus &directStatus) /* RETURNED */ | |
429 | { | |
430 | struct stat sb; | |
431 | ||
432 | MSIoDbg("stat %s in doesDirectExist", dirPath); | |
433 | if (lstat(dirPath, &sb)) { | |
434 | int err = errno; | |
435 | switch(err) { | |
436 | case EACCES: | |
437 | directStatus = MDS_Access; | |
438 | break; | |
439 | case ENOENT: | |
440 | directStatus = MDS_NotPresent; | |
441 | break; | |
442 | /* Any others? Is this a good SWAG to handle the default? */ | |
443 | default: | |
444 | directStatus = MDS_NotDirectory; | |
445 | break; | |
446 | } | |
447 | return false; | |
448 | } | |
449 | mode_t fileType = sb.st_mode & S_IFMT; | |
450 | if(fileType != S_IFDIR) { | |
451 | directStatus = MDS_NotDirectory; | |
452 | return false; | |
453 | } | |
454 | if(sb.st_uid != forUid) { | |
455 | directStatus = MDS_BadOwnerMode; | |
456 | return false; | |
457 | } | |
458 | if((sb.st_mode & 07777) != mode) { | |
459 | directStatus = MDS_BadOwnerMode; | |
460 | return false; | |
461 | } | |
462 | return true; | |
463 | } | |
464 | ||
465 | /* | |
466 | * Create specified directory if it doesn't already exist. If there is something | |
467 | * already there that doesn't meet spec (not a directory, wrong mode, wrong owner) | |
468 | * we'll do everything we can do delete what is there and then try to create it | |
469 | * correctly. | |
470 | * | |
471 | * Returns an errno on any unrecoverable error. | |
472 | */ | |
473 | static int createDir( | |
474 | const char *dirPath, | |
475 | uid_t forUid, // for checking - we don't try to set this | |
476 | mode_t dirMode) | |
477 | { | |
478 | MdsDirectStatus directStatus; | |
479 | ||
480 | if(doesDirectExist(dirPath, forUid, dirMode, directStatus)) { | |
481 | /* we're done */ | |
482 | return 0; | |
483 | } | |
484 | ||
485 | /* | |
486 | * Attempt recovery if there is *something* there. | |
487 | * Anything other than "not present" should be considered to be a possible | |
488 | * attack; syslog it. | |
489 | */ | |
490 | int rtn; | |
491 | switch(directStatus) { | |
492 | case MDS_NotPresent: | |
493 | /* normal trivial case: proceed. */ | |
494 | break; | |
495 | ||
496 | case MDS_NotDirectory: | |
497 | /* there's a file or symlink in the way */ | |
498 | if(unlink(dirPath)) { | |
499 | rtn = errno; | |
500 | MSDebug("createDir(%s): unlink() returned %d", dirPath, rtn); | |
501 | return rtn; | |
502 | } | |
503 | break; | |
504 | ||
505 | case MDS_BadOwnerMode: | |
506 | /* | |
507 | * It's a directory; try to clean it out (which may well fail if we're | |
508 | * not root). | |
509 | */ | |
510 | rtn = cleanDir(dirPath, NULL, 0); | |
511 | if(rtn) { | |
512 | return rtn; | |
513 | } | |
514 | if(rmdir(dirPath)) { | |
515 | rtn = errno; | |
516 | MSDebug("createDir(%s): rmdir() returned %d", dirPath, rtn); | |
517 | return rtn; | |
518 | } | |
519 | /* good to go */ | |
520 | break; | |
521 | ||
522 | case MDS_Access: /* hopeless */ | |
523 | MSDebug("createDir(%s): access failure, bailing", dirPath); | |
524 | return EACCES; | |
525 | } | |
526 | rtn = mkdir(dirPath, dirMode); | |
527 | if(rtn) { | |
528 | rtn = errno; | |
529 | MSDebug("createDir(%s): mkdir() returned %d", dirPath, errno); | |
530 | } | |
531 | else { | |
532 | /* make sure umask does't trick us */ | |
533 | rtn = chmod(dirPath, dirMode); | |
534 | if(rtn) { | |
535 | MSDebug("chmod(%s) returned %d", dirPath, errno); | |
536 | } | |
537 | } | |
538 | return rtn; | |
539 | } | |
540 | ||
541 | /* | |
542 | * Create an MDS session. | |
543 | */ | |
544 | MDSSession::MDSSession (const Guid *inCallerGuid, | |
545 | const CSSM_MEMORY_FUNCS &inMemoryFunctions) : | |
546 | DatabaseSession(MDSModule::get().databaseManager()), | |
547 | mCssmMemoryFunctions (inMemoryFunctions), | |
548 | mModule(MDSModule::get()) | |
549 | { | |
550 | MSDebug("MDSSession::MDSSession"); | |
551 | ||
552 | mCallerGuidPresent = inCallerGuid != nil; | |
553 | if (mCallerGuidPresent) { | |
554 | mCallerGuid = *inCallerGuid; | |
555 | } | |
556 | } | |
557 | ||
558 | MDSSession::~MDSSession () | |
559 | { | |
560 | MSDebug("MDSSession::~MDSSession"); | |
561 | } | |
562 | ||
563 | void | |
564 | MDSSession::terminate () | |
565 | { | |
566 | MSDebug("MDSSession::terminate"); | |
567 | closeAll(); | |
568 | } | |
569 | ||
570 | const char* kExceptionDeletePath = "messages"; | |
571 | ||
572 | ||
573 | /* | |
574 | * Called by security server via MDS_Install(). | |
575 | */ | |
576 | void | |
577 | MDSSession::install () | |
578 | { | |
579 | // | |
580 | // Installation requires root | |
581 | // | |
582 | if(geteuid() != (uid_t)0) { | |
583 | CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); | |
584 | } | |
585 | ||
586 | // | |
587 | // install() is only (legitimately) called from securityd. | |
588 | // Mark "server mode" so we don't end up waiting for ourselves when the databases open. | |
589 | // | |
590 | mModule.setServerMode(); | |
591 | ||
b1ab9ed8 A |
592 | try { |
593 | /* ensure MDS base directory exists with correct permissions */ | |
594 | if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) { | |
595 | MSDebug("Error creating base MDS dir; aborting."); | |
596 | CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); | |
597 | } | |
598 | ||
599 | /* ensure the the system MDS DB directory exists with correct permissions */ | |
600 | if(createDir(MDS_SYSTEM_DB_DIR, MDS_SYSTEM_UID, MDS_SYSTEM_DB_DIR_MODE)) { | |
601 | MSDebug("Error creating system MDS dir; aborting."); | |
602 | CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); | |
603 | } | |
604 | ||
427c49bc A |
605 | LockHelper lh; |
606 | ||
607 | if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, DB_LOCK_TIMEOUT)) { | |
b1ab9ed8 A |
608 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); |
609 | } | |
610 | ||
611 | /* | |
612 | * We own the whole MDS system. Clean everything out except for our lock | |
613 | * (and the directory it's in :-) | |
614 | */ | |
615 | ||
616 | const char *savedFile = MDS_INSTALL_LOCK_NAME; | |
617 | if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) { | |
618 | /* this should never happen - we're root */ | |
619 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
620 | } | |
621 | ||
622 | const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath}; | |
623 | if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) { | |
624 | /* this should never happen - we're root */ | |
625 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
626 | } | |
627 | ||
628 | /* | |
629 | * Do initial population of system DBs. | |
630 | */ | |
631 | createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE); | |
632 | DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); | |
633 | dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH); | |
634 | } | |
635 | catch(...) { | |
b1ab9ed8 A |
636 | throw; |
637 | } | |
b1ab9ed8 A |
638 | } |
639 | ||
640 | // | |
641 | // In this implementation, the uninstall() call is not supported since | |
642 | // we do not allow programmatic deletion of the MDS databases. | |
643 | // | |
644 | ||
645 | void | |
646 | MDSSession::uninstall () | |
647 | { | |
648 | CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); | |
649 | } | |
650 | ||
651 | /* | |
652 | * Common private open routine given a full specified path. | |
653 | */ | |
654 | CSSM_DB_HANDLE MDSSession::dbOpen(const char *dbName, bool batched) | |
655 | { | |
656 | static CSSM_APPLEDL_OPEN_PARAMETERS batchOpenParams = { | |
657 | sizeof(CSSM_APPLEDL_OPEN_PARAMETERS), | |
658 | CSSM_APPLEDL_OPEN_PARAMETERS_VERSION, | |
659 | CSSM_FALSE, // do not auto-commit | |
660 | 0 // mask - do not use following fields | |
661 | }; | |
662 | ||
663 | MSDebug("Opening %s%s", dbName, batched ? " in batch mode" : ""); | |
664 | MSIoDbg("open %s in dbOpen(name, batched)", dbName); | |
665 | CSSM_DB_HANDLE dbHand; | |
666 | DatabaseSession::DbOpen(dbName, | |
667 | NULL, // DbLocation | |
668 | CSSM_DB_ACCESS_READ, | |
669 | NULL, // AccessCred - hopefully optional | |
670 | batched ? &batchOpenParams : NULL, | |
671 | dbHand); | |
672 | return dbHand; | |
673 | } | |
674 | ||
675 | /* DatabaseSession routines we need to override */ | |
676 | void MDSSession::DbOpen(const char *DbName, | |
677 | const CSSM_NET_ADDRESS *DbLocation, | |
678 | CSSM_DB_ACCESS_TYPE AccessRequest, | |
679 | const AccessCredentials *AccessCred, | |
680 | const void *OpenParameters, | |
681 | CSSM_DB_HANDLE &DbHandle) | |
682 | { | |
683 | if (!mModule.serverMode()) { | |
684 | /* | |
685 | * Make sure securityd has finished initializing (system) MDS data. | |
686 | * Note that activate() only does IPC once and retains global state after that. | |
687 | */ | |
688 | SecurityServer::ClientSession client(Allocator::standard(), Allocator::standard()); | |
689 | client.activate(); /* contact securityd - won't return until MDS is ready */ | |
690 | } | |
691 | ||
692 | /* make sure DBs are up-to-date */ | |
693 | updateDataBases(); | |
694 | ||
695 | /* | |
696 | * Only task here is map incoming DbName - specified in the CDSA | |
697 | * spec - to a filename we actually use (which is a path to either | |
698 | * a system MDS DB file or a per-user MDS DB file). | |
699 | */ | |
700 | if(DbName == NULL) { | |
701 | CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME); | |
702 | } | |
703 | const char *dbName; | |
704 | if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) { | |
705 | dbName = MDS_OBJECT_DB_NAME; | |
706 | } | |
707 | else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) { | |
708 | dbName = MDS_DIRECT_DB_NAME; | |
709 | } | |
710 | else { | |
711 | CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME); | |
712 | } | |
713 | char fullPath[MAXPATHLEN]; | |
714 | dbFullPath(dbName, fullPath); | |
715 | MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName); | |
716 | DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred, | |
717 | OpenParameters, DbHandle); | |
718 | } | |
719 | ||
720 | CSSM_HANDLE MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle, | |
721 | const CssmQuery *Query, | |
722 | CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes, | |
723 | CssmData *Data, | |
724 | CSSM_DB_UNIQUE_RECORD_PTR &UniqueId) | |
725 | { | |
726 | updateDataBases(); | |
727 | return DatabaseSession::DataGetFirst(DBHandle, Query, Attributes, Data, UniqueId); | |
728 | } | |
729 | ||
730 | ||
731 | void | |
732 | MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList) | |
733 | { | |
734 | outNameList = new CSSM_NAME_LIST[1]; | |
735 | outNameList->NumStrings = 2; | |
736 | outNameList->String = new char*[2]; | |
737 | outNameList->String[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME); | |
738 | outNameList->String[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME); | |
739 | } | |
740 | ||
741 | void | |
742 | MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList) | |
743 | { | |
744 | delete [] inNameList.String[0]; | |
745 | delete [] inNameList.String[1]; | |
746 | delete [] inNameList.String; | |
747 | } | |
748 | ||
749 | void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle, | |
750 | char **DbName) | |
751 | { | |
752 | printf("GetDbNameFromHandle: code on demand\n"); | |
753 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
754 | } | |
755 | ||
756 | // | |
757 | // Attempt to obtain an exclusive lock over the the MDS databases. The | |
758 | // parameter is the maximum amount of time, in milliseconds, to spend | |
759 | // trying to obtain the lock. A value of zero means to return failure | |
760 | // right away if the lock cannot be obtained. | |
761 | // | |
762 | bool | |
427c49bc | 763 | MDSSession::LockHelper::obtainLock( |
b1ab9ed8 | 764 | const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH |
b1ab9ed8 A |
765 | int timeout) // default 0 |
766 | { | |
427c49bc | 767 | mFD = -1; |
b1ab9ed8 A |
768 | for(;;) { |
769 | secdebug("mdslock", "obtainLock: calling open(%s)", lockFile); | |
427c49bc A |
770 | mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644); |
771 | if(mFD == -1) { | |
b1ab9ed8 A |
772 | int err = errno; |
773 | secdebug("mdslock", "obtainLock: open error %d", errno); | |
774 | if(err == EINTR) { | |
775 | /* got a signal, go again */ | |
776 | continue; | |
777 | } | |
778 | else { | |
779 | /* theoretically should never happen */ | |
780 | return false; | |
781 | } | |
782 | } | |
783 | else { | |
784 | secdebug("mdslock", "obtainLock: success"); | |
785 | return true; | |
786 | } | |
787 | } | |
788 | ||
789 | /* not reached */ | |
790 | return false; | |
791 | } | |
792 | ||
793 | // | |
794 | // Release the exclusive lock over the MDS databases. If this session | |
795 | // does not hold the lock, this method does nothing. | |
796 | // | |
797 | ||
427c49bc | 798 | MDSSession::LockHelper::~LockHelper() |
b1ab9ed8 A |
799 | { |
800 | secdebug("mdslock", "releaseLock"); | |
427c49bc A |
801 | if (mFD == -1) |
802 | { | |
803 | return; | |
804 | } | |
805 | ||
806 | flock(mFD, LOCK_UN); | |
807 | close(mFD); | |
808 | mFD = -1; | |
b1ab9ed8 A |
809 | } |
810 | ||
811 | /* given DB file name, fill in fully specified path */ | |
812 | void MDSSession::dbFullPath( | |
813 | const char *dbName, | |
814 | char fullPath[MAXPATHLEN+1]) | |
815 | { | |
816 | mModule.getDbPath(fullPath); | |
817 | assert(fullPath[0] != '\0'); | |
818 | strcat(fullPath, "/"); | |
819 | strcat(fullPath, dbName); | |
820 | } | |
821 | ||
822 | /* | |
823 | * See if any per-user bundles exist in specified directory. Returns true if so. | |
824 | * First the check for one entry.... | |
825 | */ | |
826 | static bool isBundle( | |
827 | const struct dirent *dp) | |
828 | { | |
829 | if(dp == NULL) { | |
830 | return false; | |
831 | } | |
832 | /* NFS directories show up as DT_UNKNOWN */ | |
833 | switch(dp->d_type) { | |
834 | case DT_UNKNOWN: | |
835 | case DT_DIR: | |
836 | break; | |
837 | default: | |
838 | return false; | |
839 | } | |
840 | int suffixLen = strlen(MDS_BUNDLE_EXTEN); | |
427c49bc | 841 | size_t len = strlen(dp->d_name); |
b1ab9ed8 A |
842 | |
843 | return (len >= suffixLen) && | |
844 | !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN); | |
845 | } | |
846 | ||
847 | /* now the full directory search */ | |
848 | static bool checkUserBundles( | |
849 | const char *bundlePath) | |
850 | { | |
851 | MSDebug("searching for user bundles in %s", bundlePath); | |
852 | DIR *dir = opendir(bundlePath); | |
853 | if (dir == NULL) { | |
854 | return false; | |
855 | } | |
856 | struct dirent *dp; | |
857 | bool rtn = false; | |
858 | while ((dp = readdir(dir)) != NULL) { | |
859 | if(isBundle(dp)) { | |
860 | /* any other checking to do? */ | |
861 | rtn = true; | |
862 | break; | |
863 | } | |
864 | } | |
865 | closedir(dir); | |
866 | MSDebug("...%s bundle(s) found", rtn ? "" : "No"); | |
867 | return rtn; | |
868 | } | |
869 | ||
870 | #define COPY_BUF_SIZE 1024 | |
871 | ||
872 | /* | |
873 | * Single file copy with locking. | |
874 | * Ensures that the source is a regular file with specified owner. | |
875 | * Caller specifies mode of destination file. | |
876 | * Throws a CssmError if the source file doesn't meet spec; throws a | |
877 | * UnixError on any other error (which is generally recoverable by | |
878 | * having the user MDS session use the system DB files). | |
879 | */ | |
880 | static void safeCopyFile( | |
881 | const char *fromPath, | |
882 | uid_t fromUid, | |
883 | const char *toPath, | |
884 | mode_t toMode) | |
885 | { | |
886 | int error = 0; | |
887 | bool haveLock = false; | |
888 | int destFd = 0; | |
889 | int srcFd = 0; | |
890 | struct stat sb; | |
891 | char tmpToPath[MAXPATHLEN+1]; | |
892 | ||
893 | MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath); | |
894 | ||
895 | if(!doesFileExist(fromPath, fromUid, false, sb)) { | |
896 | MSDebug("safeCopyFile: bad system DB file %s", fromPath); | |
897 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
898 | } | |
899 | ||
900 | /* create temp destination */ | |
901 | snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath); | |
902 | destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode); | |
903 | if(destFd < 0) { | |
904 | error = errno; | |
905 | MSDebug("Error %d opening user DB file %s\n", error, tmpToPath); | |
906 | UnixError::throwMe(error); | |
907 | } | |
908 | ||
909 | struct flock fl; | |
910 | try { | |
911 | /* don't get tripped up by umask */ | |
912 | if(fchmod(destFd, toMode)) { | |
913 | error = errno; | |
914 | MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath); | |
915 | UnixError::throwMe(error); | |
916 | } | |
917 | ||
918 | /* open source for reading */ | |
919 | srcFd = open(fromPath, O_RDONLY, 0); | |
920 | if(srcFd < 0) { | |
921 | error = errno; | |
922 | MSDebug("Error %d opening system DB file %s\n", error, fromPath); | |
923 | UnixError::throwMe(error); | |
924 | } | |
925 | ||
926 | /* acquire the same kind of lock AtomicFile uses */ | |
927 | fl.l_start = 0; | |
928 | fl.l_len = 1; | |
929 | fl.l_pid = getpid(); | |
930 | fl.l_type = F_RDLCK; // AtomicFile gets F_WRLCK | |
931 | fl.l_whence = SEEK_SET; | |
932 | ||
933 | // Keep trying to obtain the lock if we get interupted. | |
934 | for (;;) { | |
935 | if (::fcntl(srcFd, F_SETLKW, &fl) == -1) { | |
936 | error = errno; | |
937 | if (error == EINTR) { | |
938 | error = 0; | |
939 | continue; | |
940 | } | |
941 | MSDebug("Error %d locking system DB file %s\n", error, fromPath); | |
942 | UnixError::throwMe(error); | |
943 | } | |
944 | else { | |
945 | break; | |
946 | haveLock = true; | |
947 | } | |
948 | } | |
949 | ||
950 | /* copy */ | |
951 | char buf[COPY_BUF_SIZE]; | |
952 | while(1) { | |
427c49bc | 953 | ssize_t bytesRead = read(srcFd, buf, COPY_BUF_SIZE); |
b1ab9ed8 A |
954 | if(bytesRead == 0) { |
955 | break; | |
956 | } | |
957 | if(bytesRead < 0) { | |
958 | error = errno; | |
959 | MSDebug("Error %d reading system DB file %s\n", error, fromPath); | |
960 | UnixError::throwMe(error); | |
961 | } | |
427c49bc | 962 | ssize_t bytesWritten = write(destFd, buf, bytesRead); |
b1ab9ed8 A |
963 | if(bytesWritten < 0) { |
964 | error = errno; | |
965 | MSDebug("Error %d writing user DB file %s\n", error, tmpToPath); | |
966 | UnixError::throwMe(error); | |
967 | } | |
968 | } | |
969 | } | |
970 | catch(...) { | |
971 | /* error is nonzero, we'll re-throw below...still have some cleanup */ | |
972 | } | |
973 | ||
974 | /* unlock source and close both */ | |
975 | if(haveLock) { | |
976 | fl.l_type = F_UNLCK; | |
977 | if (::fcntl(srcFd, F_SETLK, &fl) == -1) { | |
978 | MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath); | |
979 | } | |
980 | } | |
981 | MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath); | |
982 | if(srcFd) { | |
983 | close(srcFd); | |
984 | } | |
985 | if(destFd) { | |
986 | close(destFd); | |
987 | } | |
988 | if(error == 0) { | |
989 | /* commit temp file */ | |
990 | if(::rename(tmpToPath, toPath)) { | |
991 | error = errno; | |
992 | MSDebug("Error %d committing %s\n", error, toPath); | |
993 | } | |
994 | } | |
995 | if(error) { | |
996 | UnixError::throwMe(error); | |
997 | } | |
998 | } | |
999 | ||
1000 | /* | |
1001 | * Copy system DB files to specified user dir. Caller holds user DB lock. | |
1002 | * Throws a UnixError on error. | |
1003 | */ | |
1004 | static void copySystemDbs( | |
1005 | const char *userDbFileDir) | |
1006 | { | |
1007 | char toPath[MAXPATHLEN+1]; | |
1008 | ||
1009 | snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); | |
1010 | safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE); | |
1011 | snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); | |
1012 | safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE); | |
1013 | } | |
1014 | ||
1015 | /* | |
1016 | * Ensure current DB files exist and are up-to-date. | |
1017 | * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any | |
1018 | * other public functions which access a DB from scratch. | |
1019 | */ | |
1020 | void MDSSession::updateDataBases() | |
1021 | { | |
1022 | RecursionBlock::Once once(mUpdating); | |
1023 | if (once()) | |
1024 | return; // already updating; don't recurse | |
1025 | ||
1026 | uid_t ourUid = geteuid(); | |
1027 | bool isRoot = (ourUid == 0); | |
1028 | ||
1029 | /* if we scanned recently, we're done */ | |
1030 | double delta = mModule.timeSinceLastScan(); | |
1031 | if(delta < (double)MDS_SCAN_INTERVAL) { | |
1032 | return; | |
1033 | } | |
1034 | ||
1035 | /* | |
1036 | * If we're root, the first thing we do is to ensure that system DBs are present. | |
1037 | * Note that this is a necessary artifact of the problem behind Radar 3800811. | |
1038 | * When that is fixed, install() should ONLY be called from the public MDS_Install() | |
1039 | * routine. | |
1040 | * Anyway, if we *do* have to install here, we're done. | |
1041 | */ | |
1042 | if(isRoot && !systemDatabasesPresent(false)) { | |
1043 | install(); | |
1044 | mModule.setDbPath(MDS_SYSTEM_DB_DIR); | |
1045 | mModule.lastScanIsNow(); | |
1046 | return; | |
1047 | } | |
1048 | ||
1049 | /* | |
1050 | * Obtain various per-user paths. Root is a special case but follows most | |
1051 | * of the same logic from here on. | |
1052 | */ | |
1053 | std::string userDBFileDir = GetMDSDBDir(); | |
1054 | std::string userObjDBFilePath = GetMDSObjectDBPath(); | |
1055 | std::string userDirectDBFilePath = GetMDSDirectDBPath(); | |
1056 | char userBundlePath[MAXPATHLEN+1]; | |
1057 | std::string userDbLockPath = GetMDSDBLockPath(); | |
1058 | ||
1059 | /* this means "no user bundles" */ | |
1060 | userBundlePath[0] = '\0'; | |
1061 | if(!isRoot) { | |
1062 | char *userHome = getenv("HOME"); | |
1063 | if((userHome == NULL) || | |
1064 | (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) { | |
1065 | /* Can't check for user bundles */ | |
1066 | MSDebug("missing or invalid HOME; skipping user bundle check"); | |
1067 | } | |
1068 | /* TBD: any other checking of userHome? */ | |
1069 | else { | |
1070 | snprintf(userBundlePath, sizeof(userBundlePath), | |
1071 | "%s/%s", userHome, MDS_USER_BUNDLE); | |
1072 | } | |
1073 | } | |
1074 | ||
1075 | /* | |
1076 | * Create the per-user directory...that's where the lock we'll be using lives. | |
1077 | */ | |
1078 | if(!isRoot) { | |
1079 | if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) { | |
1080 | /* | |
1081 | * We'll just have to limp along using the read-only system DBs. | |
1082 | * Note that this protects (somewhat) against the DoS attack in | |
1083 | * Radar 3801292. The only problem is that this user won't be able | |
1084 | * to use per-user bundles. | |
1085 | */ | |
1086 | MSDebug("Error creating user DBs; using system DBs"); | |
1087 | mModule.setDbPath(MDS_SYSTEM_DB_DIR); | |
1088 | return; | |
1089 | } | |
1090 | } | |
1091 | ||
1092 | /* always release userLockFd no matter what happens */ | |
427c49bc A |
1093 | LockHelper lh; |
1094 | ||
1095 | if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) { | |
b1ab9ed8 A |
1096 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); |
1097 | } | |
1098 | try { | |
1099 | if(!isRoot) { | |
1100 | try { | |
1101 | /* | |
1102 | * We copy the system DBs to the per-user DBs in two cases: | |
1103 | * -- user DBs don't exist, or | |
1104 | * -- system DBs have changed since the the last update to the user DBs. | |
1105 | * This happens on smart card insertion and removal. | |
1106 | */ | |
1107 | bool doCopySystem = false; | |
1108 | struct stat userObjStat, userDirectStat; | |
1109 | if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true, | |
1110 | userObjStat, userDirectStat)) { | |
1111 | doCopySystem = true; | |
1112 | } | |
1113 | else { | |
1114 | /* compare the two mdsDirectory.db files */ | |
1115 | MSIoDbg("stat %s, %s in updateDataBases", | |
1116 | MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str()); | |
1117 | struct stat sysStat; | |
1118 | if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) { | |
1119 | doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) || | |
1120 | ((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) && | |
1121 | (sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec)); | |
1122 | if(doCopySystem) { | |
1123 | MSDebug("user DB files obsolete at %s", userDBFileDir.c_str()); | |
1124 | } | |
1125 | } | |
1126 | } | |
1127 | if(doCopySystem) { | |
1128 | /* copy system DBs to user DBs */ | |
1129 | MSDebug("copying system DBs to user at %s", userDBFileDir.c_str()); | |
1130 | copySystemDbs(userDBFileDir.c_str()); | |
1131 | } | |
1132 | else { | |
1133 | MSDebug("Using existing user DBs at %s", userDBFileDir.c_str()); | |
1134 | } | |
1135 | } | |
1136 | catch(const CssmError &cerror) { | |
1137 | /* | |
1138 | * Bad system DB file detected. Fatal. | |
1139 | */ | |
1140 | throw; | |
1141 | } | |
1142 | catch(...) { | |
1143 | /* | |
1144 | * Error on delete or create user DBs; fall back on system DBs. | |
1145 | */ | |
1146 | MSDebug("doFilesExist(purge) error; using system DBs"); | |
1147 | mModule.setDbPath(MDS_SYSTEM_DB_DIR); | |
b1ab9ed8 A |
1148 | return; |
1149 | } | |
1150 | } | |
1151 | else { | |
1152 | MSDebug("Using system DBs only"); | |
1153 | } | |
1154 | ||
1155 | /* | |
1156 | * Update per-user DBs from both bundle sources (System bundles, user bundles) | |
1157 | * as appropriate. | |
1158 | */ | |
1159 | DbFilesInfo dbFiles(*this, userDBFileDir.c_str()); | |
1160 | dbFiles.removeOutdatedPlugins(); | |
1161 | dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH); | |
1162 | if(userBundlePath[0]) { | |
1163 | /* skip for invalid or missing $HOME... */ | |
1164 | if(checkUserBundles(userBundlePath)) { | |
1165 | dbFiles.updateForBundleDir(userBundlePath); | |
1166 | } | |
1167 | } | |
1168 | mModule.setDbPath(userDBFileDir.c_str()); | |
1169 | } /* main block protected by mLockFd */ | |
1170 | catch(...) { | |
b1ab9ed8 A |
1171 | throw; |
1172 | } | |
1173 | mModule.lastScanIsNow(); | |
b1ab9ed8 A |
1174 | } |
1175 | ||
1176 | /* | |
1177 | * Remove all records with specified guid (a.k.a. ModuleID) from specified DB. | |
1178 | */ | |
1179 | void MDSSession::removeRecordsForGuid( | |
1180 | const char *guid, | |
1181 | CSSM_DB_HANDLE dbHand) | |
1182 | { | |
1183 | // tell the DB to flush its intermediate data to disk | |
1184 | PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL); | |
1185 | CssmClient::Query query = Attribute("ModuleID") == guid; | |
1186 | clearRecords(dbHand, query.cssmQuery()); | |
1187 | } | |
1188 | ||
1189 | ||
1190 | void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query) | |
1191 | { | |
1192 | CSSM_DB_UNIQUE_RECORD_PTR record = NULL; | |
1193 | CSSM_HANDLE resultHand = DataGetFirst(dbHand, | |
1194 | &query, | |
1195 | NULL, | |
1196 | NULL, // No data | |
1197 | record); | |
1198 | if (resultHand == CSSM_INVALID_HANDLE) | |
1199 | return; // no matches | |
1200 | try { | |
1201 | do { | |
1202 | DataDelete(dbHand, *record); | |
1203 | FreeUniqueRecord(dbHand, *record); | |
1204 | record = NULL; | |
1205 | } while (DataGetNext(dbHand, | |
1206 | resultHand, | |
1207 | NULL, | |
1208 | NULL, | |
1209 | record)); | |
1210 | } catch (...) { | |
1211 | if (record) | |
1212 | FreeUniqueRecord(dbHand, *record); | |
1213 | DataAbortQuery(dbHand, resultHand); | |
1214 | } | |
1215 | } | |
1216 | ||
1217 | ||
1218 | /* | |
1219 | * Determine if system databases are present. | |
1220 | * If the purge argument is true, we'll ensure that either both or neither | |
1221 | * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH. | |
1222 | */ | |
1223 | bool MDSSession::systemDatabasesPresent(bool purge) | |
1224 | { | |
1225 | bool rtn = false; | |
1226 | ||
1227 | try { | |
1228 | /* | |
1229 | * This can throw on a failed attempt to delete sole existing file.... | |
1230 | * But if that happens while we're root, our goose is fully cooked. | |
1231 | */ | |
1232 | struct stat objDbSb, directDbSb; | |
1233 | if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH, | |
1234 | MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) { | |
1235 | rtn = true; | |
1236 | } | |
1237 | } | |
1238 | catch(...) { | |
1239 | ||
1240 | } | |
1241 | return rtn; | |
1242 | } | |
1243 | ||
1244 | /* | |
1245 | * Given a DB name (which is used as an absolute path) and an array of | |
1246 | * RelationInfos, create a DB. | |
1247 | */ | |
1248 | void | |
1249 | MDSSession::createSystemDatabase( | |
1250 | const char *dbName, | |
1251 | const RelationInfo *relationInfo, | |
1252 | unsigned numRelations, | |
1253 | CSSM_BOOL autoCommit, | |
1254 | mode_t mode, | |
1255 | CSSM_DB_HANDLE &dbHand) // RETURNED | |
1256 | { | |
1257 | CSSM_DBINFO dbInfo; | |
1258 | CSSM_DBINFO_PTR dbInfoP = &dbInfo; | |
1259 | ||
1260 | memset(dbInfoP, 0, sizeof(CSSM_DBINFO)); | |
1261 | dbInfoP->NumberOfRecordTypes = numRelations; | |
1262 | dbInfoP->IsLocal = CSSM_TRUE; // TBD - what does this mean? | |
1263 | dbInfoP->AccessPath = NULL; // TBD | |
1264 | ||
1265 | /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex | |
1266 | * info arrays */ | |
1267 | unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations; | |
1268 | dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size); | |
1269 | memset(dbInfoP->DefaultParsingModules, 0, size); | |
1270 | size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations; | |
1271 | dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size); | |
1272 | memset(dbInfoP->RecordAttributeNames, 0, size); | |
1273 | size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations; | |
1274 | dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size); | |
1275 | memset(dbInfoP->RecordIndexes, 0, size); | |
1276 | ||
1277 | /* cook up attribute and index info for each relation */ | |
1278 | unsigned relation; | |
1279 | for(relation=0; relation<numRelations; relation++) { | |
1280 | const struct RelationInfo *relp = &relationInfo[relation]; // source | |
1281 | CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo = | |
1282 | &dbInfoP->RecordAttributeNames[relation]; // dest 1 | |
1283 | CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo = | |
1284 | &dbInfoP->RecordIndexes[relation]; // dest 2 | |
1285 | ||
1286 | attrInfo->DataRecordType = relp->DataRecordType; | |
1287 | attrInfo->NumberOfAttributes = relp->NumberOfAttributes; | |
1288 | attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo; | |
1289 | ||
1290 | indexInfo->DataRecordType = relp->DataRecordType; | |
1291 | indexInfo->NumberOfIndexes = relp->NumberOfIndexes; | |
1292 | indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo; | |
1293 | } | |
1294 | ||
1295 | /* set autocommit and mode */ | |
1296 | CSSM_APPLEDL_OPEN_PARAMETERS openParams; | |
1297 | memset(&openParams, 0, sizeof(openParams)); | |
1298 | openParams.length = sizeof(openParams); | |
1299 | openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION; | |
1300 | openParams.autoCommit = autoCommit; | |
1301 | openParams.mask = kCSSM_APPLEDL_MASK_MODE; | |
1302 | openParams.mode = mode; | |
1303 | ||
1304 | try { | |
1305 | DbCreate(dbName, | |
1306 | NULL, // DbLocation | |
1307 | *dbInfoP, | |
1308 | CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE, | |
1309 | NULL, // CredAndAclEntry | |
1310 | &openParams, | |
1311 | dbHand); | |
1312 | } | |
1313 | catch(...) { | |
1314 | MSDebug("Error on DbCreate"); | |
1315 | free(dbInfoP->DefaultParsingModules); | |
1316 | free(dbInfoP->RecordAttributeNames); | |
1317 | free(dbInfoP->RecordIndexes); | |
1318 | throw; | |
1319 | } | |
1320 | free(dbInfoP->DefaultParsingModules); | |
1321 | free(dbInfoP->RecordAttributeNames); | |
1322 | free(dbInfoP->RecordIndexes); | |
1323 | ||
1324 | } | |
1325 | ||
1326 | /* | |
1327 | * Create system databases from scratch if they do not already exist. | |
1328 | * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to | |
1329 | * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH). | |
1330 | * Returns true if we actually built the files, false if they already | |
1331 | * existed. | |
1332 | */ | |
1333 | bool MDSSession::createSystemDatabases( | |
1334 | CSSM_BOOL autoCommit, | |
1335 | mode_t mode) | |
1336 | { | |
1337 | CSSM_DB_HANDLE objectDbHand = 0; | |
1338 | CSSM_DB_HANDLE directoryDbHand = 0; | |
1339 | ||
1340 | assert(geteuid() == (uid_t)0); | |
1341 | if(systemDatabasesPresent(true)) { | |
1342 | /* both databases exist as regular files with correct owner - we're done */ | |
1343 | MSDebug("system DBs already exist"); | |
1344 | return false; | |
1345 | } | |
1346 | ||
1347 | /* create two DBs - any exception here results in deleting both of them */ | |
1348 | MSDebug("Creating MDS DBs"); | |
1349 | try { | |
1350 | createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1, | |
1351 | autoCommit, mode, objectDbHand); | |
1352 | MSIoDbg("close objectDbHand in createSystemDatabases"); | |
1353 | DbClose(objectDbHand); | |
1354 | objectDbHand = 0; | |
1355 | createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations, | |
1356 | autoCommit, mode, directoryDbHand); | |
1357 | MSIoDbg("close directoryDbHand in createSystemDatabases"); | |
1358 | DbClose(directoryDbHand); | |
1359 | directoryDbHand = 0; | |
1360 | } | |
1361 | catch (...) { | |
1362 | MSDebug("Error creating MDS DBs - deleting both DB files"); | |
1363 | unlink(MDS_OBJECT_DB_PATH); | |
1364 | unlink(MDS_DIRECT_DB_PATH); | |
1365 | throw; | |
1366 | } | |
1367 | return true; | |
1368 | } | |
1369 | ||
1370 | /* | |
1371 | * DbFilesInfo helper class | |
1372 | */ | |
1373 | ||
1374 | /* Note both DB files MUST exist at construction time */ | |
1375 | MDSSession::DbFilesInfo::DbFilesInfo( | |
1376 | MDSSession &session, | |
1377 | const char *dbPath) : | |
1378 | mSession(session), | |
1379 | mObjDbHand(0), | |
1380 | mDirectDbHand(0), | |
1381 | mLaterTimestamp(0) | |
1382 | { | |
1383 | assert(strlen(dbPath) < MAXPATHLEN); | |
1384 | strcpy(mDbPath, dbPath); | |
1385 | ||
1386 | /* stat the two DB files, snag the later timestamp */ | |
1387 | char path[MAXPATHLEN]; | |
1388 | sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME); | |
1389 | struct stat sb; | |
1390 | MSIoDbg("stat %s in DbFilesInfo()", path); | |
1391 | int rtn = ::stat(path, &sb); | |
1392 | if(rtn) { | |
1393 | int error = errno; | |
1394 | MSDebug("Error %d statting DB file %s", error, path); | |
1395 | UnixError::throwMe(error); | |
1396 | } | |
1397 | mLaterTimestamp = sb.st_mtimespec.tv_sec; | |
1398 | sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME); | |
1399 | MSIoDbg("stat %s in DbFilesInfo()", path); | |
1400 | rtn = ::stat(path, &sb); | |
1401 | if(rtn) { | |
1402 | int error = errno; | |
1403 | MSDebug("Error %d statting DB file %s", error, path); | |
1404 | UnixError::throwMe(error); | |
1405 | } | |
1406 | if(sb.st_mtimespec.tv_sec > mLaterTimestamp) { | |
1407 | mLaterTimestamp = sb.st_mtimespec.tv_sec; | |
1408 | } | |
1409 | } | |
1410 | ||
1411 | MDSSession::DbFilesInfo::~DbFilesInfo() | |
1412 | { | |
1413 | if(mObjDbHand != 0) { | |
1414 | /* autocommit on, henceforth */ | |
1415 | mSession.PassThrough(mObjDbHand, | |
1416 | CSSM_APPLEFILEDL_COMMIT, NULL, NULL); | |
1417 | MSIoDbg("close objectDbHand in ~DbFilesInfo()"); | |
1418 | mSession.DbClose(mObjDbHand); | |
1419 | mObjDbHand = 0; | |
1420 | } | |
1421 | if(mDirectDbHand != 0) { | |
1422 | mSession.PassThrough(mDirectDbHand, | |
1423 | CSSM_APPLEFILEDL_COMMIT, NULL, NULL); | |
1424 | MSIoDbg("close mDirectDbHand in ~DbFilesInfo()"); | |
1425 | mSession.DbClose(mDirectDbHand); | |
1426 | mDirectDbHand = 0; | |
1427 | } | |
1428 | } | |
1429 | ||
1430 |