]> git.saurik.com Git - apple/security.git/blob - Security/libsecurity_mds/lib/MDSSession.cpp
Security-57031.10.10.tar.gz
[apple/security.git] / Security / libsecurity_mds / lib / MDSSession.cpp
1 /*
2 * Copyright (c) 2000-2001,2011-2014 Apple 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>
44 #include <syslog.h>
45
46 using namespace CssmClient;
47
48 /*
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];
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
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
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
605 LockHelper lh;
606
607 if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, DB_LOCK_TIMEOUT)) {
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(...) {
636 throw;
637 }
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
763 MDSSession::LockHelper::obtainLock(
764 const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH
765 int timeout) // default 0
766 {
767 mFD = -1;
768 for(;;) {
769 secdebug("mdslock", "obtainLock: calling open(%s)", lockFile);
770 mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
771 if(mFD == -1) {
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
798 MDSSession::LockHelper::~LockHelper()
799 {
800 secdebug("mdslock", "releaseLock");
801 if (mFD == -1)
802 {
803 return;
804 }
805
806 flock(mFD, LOCK_UN);
807 close(mFD);
808 mFD = -1;
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);
841 size_t len = strlen(dp->d_name);
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) {
953 ssize_t bytesRead = read(srcFd, buf, COPY_BUF_SIZE);
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 }
962 ssize_t bytesWritten = write(destFd, buf, bytesRead);
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 */
1093 LockHelper lh;
1094
1095 if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) {
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);
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(...) {
1171 throw;
1172 }
1173 mModule.lastScanIsNow();
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 /* lazy evaluation of both DB handlesÊ*/
1431 CSSM_DB_HANDLE MDSSession::DbFilesInfo::objDbHand()
1432 {
1433 if(mObjDbHand != 0) {
1434 return mObjDbHand;
1435 }
1436 char fullPath[MAXPATHLEN + 1];
1437 sprintf(fullPath, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1438 MSIoDbg("open %s in objDbHand()", fullPath);
1439 mObjDbHand = mSession.dbOpen(fullPath, true); // batch mode
1440 return mObjDbHand;
1441 }
1442
1443 CSSM_DB_HANDLE MDSSession::DbFilesInfo::directDbHand()
1444 {
1445 if(mDirectDbHand != 0) {
1446 return mDirectDbHand;
1447 }
1448 char fullPath[MAXPATHLEN + 1];
1449 sprintf(fullPath, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1450 MSIoDbg("open %s in directDbHand()", fullPath);
1451 mDirectDbHand = mSession.dbOpen(fullPath, true); // batch mode
1452 return mDirectDbHand;
1453 }
1454
1455 /*
1456 * Update the info for Security.framework and the system bundles.
1457 */
1458 void MDSSession::DbFilesInfo::updateSystemDbInfo(
1459 const char *systemPath, // e.g., /System/Library/Frameworks
1460 const char *bundlePath) // e.g., /System/Library/Security
1461 {
1462 /*
1463 * Security.framework - CSSM and built-in modules - only for initial population of
1464 * system DB files.
1465 */
1466 if (systemPath) {
1467 string path;
1468 if (CFRef<CFBundleRef> me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")))
1469 if (CFRef<CFURLRef> url = CFBundleCopyBundleURL(me))
1470 if (CFRef<CFStringRef> cfpath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle))
1471 path = cfString(cfpath); // path to my bundle
1472
1473 if (path.empty()) // use system default
1474 path = string(systemPath) + "/" MDS_SYSTEM_FRAME;
1475 updateForBundle(path.c_str());
1476 }
1477
1478 /* Standard loadable bundles */
1479 updateForBundleDir(bundlePath);
1480 }
1481
1482
1483 MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1484 const CSSM_DATA &guid)
1485 {
1486 assert(guid.Length <= MAX_GUID_LEN);
1487 assert(guid.Length != 0);
1488 memmove(mGuid, guid.Data, guid.Length);
1489 if(mGuid[guid.Length - 1] != '\0') {
1490 mGuid[guid.Length] = '\0';
1491 }
1492 }
1493
1494 /*
1495 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1496 * If so, add to tbdVector.
1497 */
1498 void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1499 const CSSM_DATA &pathValue,
1500 const CSSM_DATA &guidValue,
1501 TbdVector &tbdVector)
1502 {
1503 /* stat the specified plugin */
1504 struct stat sb;
1505 bool obsolete = false;
1506 string path = CssmData::overlay(pathValue).toString();
1507 if (!path.empty() && path[0] == '*') {
1508 /* builtin pseudo-path; never obsolete this */
1509 return;
1510 }
1511 MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str());
1512 int rtn = ::stat(path.c_str(), &sb);
1513 if(rtn) {
1514 /* not there or inaccessible; delete */
1515 obsolete = true;
1516 }
1517 else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1518 /* timestamp of plugin's main directory later than that of DBs */
1519 obsolete = true;
1520 }
1521 if(obsolete) {
1522 TbdRecord *tbdRecord = new TbdRecord(guidValue);
1523 tbdVector.push_back(tbdRecord);
1524 MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str());
1525 }
1526 }
1527
1528 /*
1529 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1530 * i.e., with any path) which are either not present on disk, or which
1531 * have changed since dbFiles.laterTimestamp().
1532 */
1533 void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1534 {
1535 CSSM_QUERY query;
1536 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1537 CSSM_HANDLE resultHand;
1538 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
1539 CSSM_DB_ATTRIBUTE_DATA theAttrs[2];
1540 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo;
1541 TbdVector tbdRecords;
1542
1543 /*
1544 * First, scan object directory. All we need are the path and GUID attributes.
1545 */
1546 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1547 recordAttrs.SemanticInformation = 0;
1548 recordAttrs.NumberOfAttributes = 2;
1549 recordAttrs.AttributeData = theAttrs;
1550
1551 attrInfo = &theAttrs[0].Info;
1552 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1553 attrInfo->Label.AttributeName = (char*) "ModuleID";
1554 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1555 theAttrs[0].NumberOfValues = 0;
1556 theAttrs[0].Value = NULL;
1557 attrInfo = &theAttrs[1].Info;
1558 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1559 attrInfo->Label.AttributeName = (char*) "Path";
1560 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1561 theAttrs[1].NumberOfValues = 0;
1562 theAttrs[1].Value = NULL;
1563
1564 /* just search by recordType, no predicates */
1565 query.RecordType = MDS_OBJECT_RECORDTYPE;
1566 query.Conjunctive = CSSM_DB_NONE;
1567 query.NumSelectionPredicates = 0;
1568 query.SelectionPredicate = NULL;
1569 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
1570 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
1571 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1572
1573 CssmQuery perryQuery(query);
1574 try {
1575 resultHand = mSession.DataGetFirst(objDbHand(),
1576 &perryQuery,
1577 &recordAttrs,
1578 NULL, // No data
1579 record);
1580 }
1581 catch(...) {
1582 MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1583 return; // ???
1584 }
1585 if(record) {
1586 mSession.FreeUniqueRecord(mObjDbHand, *record);
1587 }
1588 if(resultHand) {
1589 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1590 checkOutdatedPlugin(*theAttrs[1].Value, *theAttrs[0].Value,
1591 tbdRecords);
1592 }
1593 else {
1594 MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1595 }
1596 for(unsigned dex=0; dex<2; dex++) {
1597 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1598 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1599 if(attr->Value[attrDex].Data) {
1600 mSession.free(attr->Value[attrDex].Data);
1601 }
1602 }
1603 if(attr->Value) {
1604 mSession.free(attr->Value);
1605 }
1606 }
1607 }
1608 else {
1609 /* empty Object DB - we're done */
1610 MSDebug("removeOutdatedPlugins: empty object DB");
1611 return;
1612 }
1613
1614 /* now the rest of the object DB records */
1615 for(;;) {
1616 bool brtn = mSession.DataGetNext(objDbHand(),
1617 resultHand,
1618 &recordAttrs,
1619 NULL,
1620 record);
1621 if(!brtn) {
1622 /* end of data */
1623 break;
1624 }
1625 if(record) {
1626 mSession.FreeUniqueRecord(mObjDbHand, *record);
1627 }
1628 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1629 checkOutdatedPlugin(*theAttrs[1].Value,
1630 *theAttrs[0].Value,
1631 tbdRecords);
1632 }
1633 else {
1634 MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1635 }
1636 for(unsigned dex=0; dex<2; dex++) {
1637 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1638 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1639 if(attr->Value[attrDex].Data) {
1640 mSession.free(attr->Value[attrDex].Data);
1641 }
1642 }
1643 if(attr->Value) {
1644 mSession.free(attr->Value);
1645 }
1646 }
1647 }
1648 /* no DataAbortQuery needed; we scanned until completion */
1649
1650 /*
1651 * We have a vector of plugins to be deleted. Remove all records from both
1652 * DBs associated with the plugins, as specified by guid.
1653 */
1654 size_t numRecords = tbdRecords.size();
1655 for(size_t i=0; i<numRecords; i++) {
1656 TbdRecord *tbdRecord = tbdRecords[i];
1657 mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand());
1658 mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand());
1659 }
1660 for(size_t i=0; i<numRecords; i++) {
1661 delete tbdRecords[i];
1662 }
1663 }
1664
1665
1666 /*
1667 * Update DBs for all bundles in specified directory.
1668 */
1669 void MDSSession::DbFilesInfo::updateForBundleDir(
1670 const char *bundleDirPath)
1671 {
1672 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1673 * much too heavyweight */
1674 MSDebug("...updating DBs for dir %s", bundleDirPath);
1675 DIR *dir = opendir(bundleDirPath);
1676 if (dir == NULL) {
1677 MSDebug("updateForBundleDir: error %d opening %s", errno, bundleDirPath);
1678 return;
1679 }
1680 struct dirent *dp;
1681 char fullPath[MAXPATHLEN];
1682 while ((dp = readdir(dir)) != NULL) {
1683 if(isBundle(dp)) {
1684 sprintf(fullPath, "%s/%s", bundleDirPath, dp->d_name);
1685 updateForBundle(fullPath);
1686 }
1687 }
1688 closedir(dir);
1689 }
1690
1691 /*
1692 * lookup by path - just returns true if there is a record assoociated with the path
1693 * in mObjDbHand.
1694 */
1695 bool MDSSession::DbFilesInfo::lookupForPath(
1696 const char *path)
1697 {
1698 CSSM_QUERY query;
1699 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1700 CSSM_HANDLE resultHand = 0;
1701 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
1702 CSSM_DB_ATTRIBUTE_DATA theAttr;
1703 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo = &theAttr.Info;
1704 CSSM_SELECTION_PREDICATE predicate;
1705 CSSM_DATA predData;
1706
1707 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1708 recordAttrs.SemanticInformation = 0;
1709 recordAttrs.NumberOfAttributes = 1;
1710 recordAttrs.AttributeData = &theAttr;
1711
1712 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1713 attrInfo->Label.AttributeName = (char*) "Path";
1714 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1715
1716 theAttr.NumberOfValues = 0;
1717 theAttr.Value = NULL;
1718
1719 predicate.DbOperator = CSSM_DB_EQUAL;
1720 predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1721 predicate.Attribute.Info.Label.AttributeName = (char*) "Path";
1722 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1723 predData.Data = (uint8 *)path;
1724 predData.Length = strlen(path);
1725 predicate.Attribute.Value = &predData;
1726 predicate.Attribute.NumberOfValues = 1;
1727
1728 query.RecordType = MDS_OBJECT_RECORDTYPE;
1729 query.Conjunctive = CSSM_DB_NONE;
1730 query.NumSelectionPredicates = 1;
1731 query.SelectionPredicate = &predicate;
1732 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
1733 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
1734 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1735
1736 bool ourRtn = true;
1737 try {
1738 CssmQuery perryQuery(query);
1739 resultHand = mSession.DataGetFirst(objDbHand(),
1740 &perryQuery,
1741 &recordAttrs,
1742 NULL, // No data
1743 record);
1744 }
1745 catch (...) {
1746 ourRtn = false;
1747 }
1748 if(record) {
1749 mSession.FreeUniqueRecord(mObjDbHand, *record);
1750 }
1751 else {
1752 ourRtn = false;
1753 }
1754 if(resultHand && ourRtn) {
1755 /* more resulting pending; terminate the search */
1756 try {
1757 mSession.DataAbortQuery(mObjDbHand, resultHand);
1758 }
1759 catch(...) {
1760 MSDebug("exception on DataAbortQuery in lookupForPath");
1761 }
1762 }
1763 for(unsigned dex=0; dex<theAttr.NumberOfValues; dex++) {
1764 if(theAttr.Value[dex].Data) {
1765 mSession.free(theAttr.Value[dex].Data);
1766 }
1767 }
1768 mSession.free(theAttr.Value);
1769 return ourRtn;
1770 }
1771
1772 /* update entry for one bundle, which is known to exist */
1773 void MDSSession::DbFilesInfo::updateForBundle(
1774 const char *bundlePath)
1775 {
1776 MSDebug("...updating DBs for bundle %s", bundlePath);
1777
1778 /* Quick lookup - do we have ANY entry for a bundle with this path? */
1779 if(lookupForPath(bundlePath)) {
1780 /* Yep, we're done */
1781 return;
1782 }
1783 MDSAttrParser parser(bundlePath,
1784 mSession,
1785 objDbHand(),
1786 directDbHand());
1787 try {
1788 parser.parseAttrs();
1789 }
1790 catch (const CssmError &err) {
1791 // a corrupt MDS info file invalidates the entire plugin
1792 const char *guid = parser.guid();
1793 if (guid) {
1794 mSession.removeRecordsForGuid(guid, objDbHand());
1795 mSession.removeRecordsForGuid(guid, directDbHand());
1796 }
1797 }
1798 }
1799
1800
1801 //
1802 // Private API: add MDS records from contents of file
1803 // These files are typically written by securityd and handed to us in this call.
1804 //
1805 void MDSSession::installFile(const MDS_InstallDefaults *defaults,
1806 const char *inBundlePath, const char *subdir, const char *file)
1807 {
1808 string bundlePath = inBundlePath ? inBundlePath : cfString(CFBundleGetMainBundle());
1809 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1810 MDSAttrParser parser(bundlePath.c_str(),
1811 *this,
1812 dbFiles.objDbHand(),
1813 dbFiles.directDbHand());
1814 parser.setDefaults(defaults);
1815
1816 try {
1817 if (file == NULL) // parse a directory
1818 if (subdir) // a particular directory
1819 parser.parseAttrs(CFTempString(subdir));
1820 else // all resources in bundle
1821 parser.parseAttrs(NULL);
1822 else // parse just one file
1823 parser.parseFile(CFRef<CFURLRef>(makeCFURL(file)), CFTempString(subdir));
1824 }
1825 catch (const CssmError &err) {
1826 const char *guid = parser.guid();
1827 if (guid) {
1828 removeRecordsForGuid(guid, dbFiles.objDbHand());
1829 removeRecordsForGuid(guid, dbFiles.directDbHand());
1830 }
1831 }
1832 }
1833
1834
1835 //
1836 // Private API: Remove all records for a guid/subservice
1837 //
1838 // Note: Multicursors searching for SSID fail because not all records in the
1839 // database have this attribute. So we have to explicitly run through all tables
1840 // that do.
1841 //
1842 void MDSSession::removeSubservice(const char *guid, uint32 ssid)
1843 {
1844 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1845
1846 CssmClient::Query query =
1847 Attribute("ModuleID") == guid &&
1848 Attribute("SSID") == ssid;
1849
1850 // only CSP and DL tables are cleared here
1851 // (this function is private to securityd, which only handles those types)
1852 clearRecords(dbFiles.directDbHand(),
1853 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE));
1854 clearRecords(dbFiles.directDbHand(),
1855 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE));
1856 clearRecords(dbFiles.directDbHand(),
1857 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE));
1858 clearRecords(dbFiles.directDbHand(),
1859 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE));
1860 clearRecords(dbFiles.directDbHand(),
1861 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE));
1862 clearRecords(dbFiles.directDbHand(),
1863 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE));
1864 }
1865
1866
1867 } // end namespace Security