]> git.saurik.com Git - apple/security.git/blob - libsecurity_mds/lib/MDSSession.cpp
Security-55163.44.tar.gz
[apple/security.git] / libsecurity_mds / lib / MDSSession.cpp
1 /*
2 * Copyright (c) 2000-2001 Apple Computer, Inc. All Rights Reserved.
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19 #include "MDSSession.h"
20
21 #include <security_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
45 using namespace CssmClient;
46
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 confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer));
146 retValue = strBuffer;
147 }
148
149 return retValue;
150 }
151
152
153
154 static std::string GetMDSDBDir()
155 {
156 string retValue;
157 bool isRoot = geteuid() == 0;
158
159 if (isRoot)
160 {
161 retValue = MDS_SYSTEM_DB_DIR;
162 }
163 else
164 {
165 retValue = GetMDSBaseDBDir(isRoot) + "/" + MDS_USER_DB_COMP;
166 }
167
168 return retValue;
169 }
170
171
172
173 static std::string GetMDSObjectDBPath()
174 {
175 return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME;
176 }
177
178
179
180 static std::string GetMDSDirectDBPath()
181 {
182 return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH;
183 }
184
185
186
187 static std::string GetMDSDBLockPath()
188 {
189 return GetMDSDBDir() + "/" + MDS_LOCK_FILE_NAME;
190 }
191
192
193
194 /*
195 * Given a path to a directory, remove everything in the directory except for the optional
196 * keepFileNames. Returns 0 on success, else an errno.
197 */
198 static int cleanDir(
199 const char *dirPath,
200 const char **keepFileNames, // array of strings, size numKeepFileNames
201 unsigned numKeepFileNames)
202 {
203 DIR *dirp;
204 struct dirent *dp;
205 char fullPath[MAXPATHLEN];
206 int rtn = 0;
207
208 MSCleanDirDbg("cleanDir(%s) top", dirPath);
209 if ((dirp = opendir(dirPath)) == NULL) {
210 rtn = errno;
211 MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn);
212 return rtn;
213 }
214
215 for(;;) {
216 bool skip = false;
217 const char *d_name = NULL;
218
219 /* this block is for breaking on unqualified entries */
220 do {
221 errno = 0;
222 dp = readdir(dirp);
223 if(dp == NULL) {
224 /* end of directory or error */
225 rtn = errno;
226 if(rtn) {
227 MSCleanDirDbg("cleanDir(%s): readdir err %d", dirPath, rtn);
228 }
229 break;
230 }
231 d_name = dp->d_name;
232
233 /* skip "." and ".." */
234 if( (d_name[0] == '.') &&
235 ( (d_name[1] == '\0') ||
236 ( (d_name[1] == '.') && (d_name[2] == '\0') ) ) ) {
237 skip = true;
238 break;
239 }
240
241 /* skip entries in keepFileNames */
242 for(unsigned dex=0; dex<numKeepFileNames; dex++) {
243 if(!strcmp(keepFileNames[dex], d_name)) {
244 skip = true;
245 break;
246 }
247 }
248 } while(0);
249 if(rtn || (dp == NULL)) {
250 /* one way or another, we're done */
251 break;
252 }
253 if(skip) {
254 /* try again */
255 continue;
256 }
257
258 /* We have an entry to delete. Delete it, or recurse. */
259
260 snprintf(fullPath, sizeof(fullPath), "%s/%s", dirPath, d_name);
261 if(dp->d_type == DT_DIR) {
262 /* directory. Clean it, then delete. */
263 MSCleanDirDbg("cleanDir recursing for dir %s", fullPath);
264 rtn = cleanDir(fullPath, NULL, 0);
265 if(rtn) {
266 break;
267 }
268 MSCleanDirDbg("cleanDir deleting dir %s", fullPath);
269 if(rmdir(fullPath)) {
270 rtn = errno;
271 MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn);
272 break;
273 }
274 }
275 else {
276 MSCleanDirDbg("cleanDir deleting file %s", fullPath);
277 if(unlink(fullPath)) {
278 rtn = errno;
279 MSCleanDirDbg("unlink(%s) returned %d", fullPath, rtn);
280 break;
281 }
282 }
283
284 /*
285 * Back to beginning of directory for clean scan.
286 * Normally we'd just do a rewinddir() here but the RAMDisk filesystem,
287 * used when booting from DVD, does not implement that properly.
288 */
289 closedir(dirp);
290 if ((dirp = opendir(dirPath)) == NULL) {
291 rtn = errno;
292 MSCleanDirDbg("opendir(%s) returned %d", dirPath, rtn);
293 return rtn;
294 }
295 } /* main loop */
296
297 closedir(dirp);
298 return rtn;
299 }
300
301 /*
302 * Determine if a file exists as regular file with specified owner. Returns true if so.
303 * If the purge argument is true, and there is something at the specified path that
304 * doesn't meet spec, we do everything we can to delete it. If that fails we throw
305 * CssmError(CSSM_ERRCODE_MDS_ERROR). If the delete succeeds we return false.
306 * Returns the stat info on success for further processing by caller.
307 */
308 static bool doesFileExist(
309 const char *filePath,
310 uid_t forUid,
311 bool purge,
312 struct stat &sb) // RETURNED
313 {
314 MSIoDbg("stat %s in doesFileExist", filePath);
315 if(lstat(filePath, &sb)) {
316 /* doesn't exist or we can't even get to it. */
317 if(errno == ENOENT) {
318 return false;
319 }
320 if(purge) {
321 /* If we can't stat it we sure can't delete it. */
322 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
323 }
324 return false;
325 }
326
327 /* it's there...how does it look? */
328 mode_t fileType = sb.st_mode & S_IFMT;
329 if((fileType == S_IFREG) && (sb.st_uid == forUid)) {
330 return true;
331 }
332 if(!purge) {
333 return false;
334 }
335
336 /* not what we want: get rid of it. */
337 if(fileType == S_IFDIR) {
338 /* directory: clean then remove */
339 if(cleanDir(filePath, NULL, 0)) {
340 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
341 }
342 if(rmdir(filePath)) {
343 MSDebug("rmdir(%s) returned %d", filePath, errno);
344 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
345 }
346 }
347 else {
348 if(unlink(filePath)) {
349 MSDebug("unlink(%s) returned %d", filePath, errno);
350 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
351 }
352 }
353
354 /* caller should be somewhat happy */
355 return false;
356 }
357
358 /*
359 * Determine if both of the specified DB files exist as accessible regular files with specified
360 * owner. Returns true if they do.
361 *
362 * If the purge argument is true, we'll ensure that either both files exist with
363 * the right owner, or neither of the files exist on exit. An error on that operation
364 * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed).
365 * Returns the stat info for both files on success for further processing by caller.
366 */
367 static bool doFilesExist(
368 const char *objDbFile,
369 const char *directDbFile,
370 uid_t forUid,
371 bool purge, // false means "passive" check
372 struct stat &objDbSb, // RETURNED
373 struct stat &directDbSb) // RETURNED
374
375 {
376 bool objectExist = doesFileExist(objDbFile, forUid, purge, objDbSb);
377 bool directExist = doesFileExist(directDbFile, forUid, purge, directDbSb);
378 if(objectExist && directExist) {
379 return true;
380 }
381 else if(!purge) {
382 return false;
383 }
384
385 /*
386 * At least one does not exist - ensure neither of them do.
387 * Note that if we got this far, we know the one that exists is a regular file
388 * so it's safe to just unlink it.
389 */
390 if(objectExist) {
391 if(unlink(objDbFile)) {
392 MSDebug("unlink(%s) returned %d", objDbFile, errno);
393 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
394 }
395 }
396 if(directExist) {
397 if(unlink(directDbFile)) {
398 MSDebug("unlink(%s) returned %d", directDbFile, errno);
399 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
400 }
401 }
402 return false;
403 }
404
405 /*
406 * Determine if specified directory exists with specified owner and mode.
407 * Returns true if copacetic, else returns false and also indicates
408 * via the directStatus out param what went wrong.
409 */
410 typedef enum {
411 MDS_NotPresent, /* nothing there */
412 MDS_NotDirectory, /* not a directory */
413 MDS_BadOwnerMode, /* wrong owner or mode */
414 MDS_Access /* couldn't search the directories */
415 } MdsDirectStatus;
416
417 static bool doesDirectExist(
418 const char *dirPath,
419 uid_t forUid,
420 mode_t mode,
421 MdsDirectStatus &directStatus) /* RETURNED */
422 {
423 struct stat sb;
424
425 MSIoDbg("stat %s in doesDirectExist", dirPath);
426 if (lstat(dirPath, &sb)) {
427 int err = errno;
428 switch(err) {
429 case EACCES:
430 directStatus = MDS_Access;
431 break;
432 case ENOENT:
433 directStatus = MDS_NotPresent;
434 break;
435 /* Any others? Is this a good SWAG to handle the default? */
436 default:
437 directStatus = MDS_NotDirectory;
438 break;
439 }
440 return false;
441 }
442 mode_t fileType = sb.st_mode & S_IFMT;
443 if(fileType != S_IFDIR) {
444 directStatus = MDS_NotDirectory;
445 return false;
446 }
447 if(sb.st_uid != forUid) {
448 directStatus = MDS_BadOwnerMode;
449 return false;
450 }
451 if((sb.st_mode & 07777) != mode) {
452 directStatus = MDS_BadOwnerMode;
453 return false;
454 }
455 return true;
456 }
457
458 /*
459 * Create specified directory if it doesn't already exist. If there is something
460 * already there that doesn't meet spec (not a directory, wrong mode, wrong owner)
461 * we'll do everything we can do delete what is there and then try to create it
462 * correctly.
463 *
464 * Returns an errno on any unrecoverable error.
465 */
466 static int createDir(
467 const char *dirPath,
468 uid_t forUid, // for checking - we don't try to set this
469 mode_t dirMode)
470 {
471 MdsDirectStatus directStatus;
472
473 if(doesDirectExist(dirPath, forUid, dirMode, directStatus)) {
474 /* we're done */
475 return 0;
476 }
477
478 /*
479 * Attempt recovery if there is *something* there.
480 * Anything other than "not present" should be considered to be a possible
481 * attack; syslog it.
482 */
483 int rtn;
484 switch(directStatus) {
485 case MDS_NotPresent:
486 /* normal trivial case: proceed. */
487 break;
488
489 case MDS_NotDirectory:
490 /* there's a file or symlink in the way */
491 if(unlink(dirPath)) {
492 rtn = errno;
493 MSDebug("createDir(%s): unlink() returned %d", dirPath, rtn);
494 return rtn;
495 }
496 break;
497
498 case MDS_BadOwnerMode:
499 /*
500 * It's a directory; try to clean it out (which may well fail if we're
501 * not root).
502 */
503 rtn = cleanDir(dirPath, NULL, 0);
504 if(rtn) {
505 return rtn;
506 }
507 if(rmdir(dirPath)) {
508 rtn = errno;
509 MSDebug("createDir(%s): rmdir() returned %d", dirPath, rtn);
510 return rtn;
511 }
512 /* good to go */
513 break;
514
515 case MDS_Access: /* hopeless */
516 MSDebug("createDir(%s): access failure, bailing", dirPath);
517 return EACCES;
518 }
519 rtn = mkdir(dirPath, dirMode);
520 if(rtn) {
521 rtn = errno;
522 MSDebug("createDir(%s): mkdir() returned %d", dirPath, errno);
523 }
524 else {
525 /* make sure umask does't trick us */
526 rtn = chmod(dirPath, dirMode);
527 if(rtn) {
528 MSDebug("chmod(%s) returned %d", dirPath, errno);
529 }
530 }
531 return rtn;
532 }
533
534 /*
535 * Create an MDS session.
536 */
537 MDSSession::MDSSession (const Guid *inCallerGuid,
538 const CSSM_MEMORY_FUNCS &inMemoryFunctions) :
539 DatabaseSession(MDSModule::get().databaseManager()),
540 mCssmMemoryFunctions (inMemoryFunctions),
541 mModule(MDSModule::get())
542 {
543 MSDebug("MDSSession::MDSSession");
544
545 mCallerGuidPresent = inCallerGuid != nil;
546 if (mCallerGuidPresent) {
547 mCallerGuid = *inCallerGuid;
548 }
549 }
550
551 MDSSession::~MDSSession ()
552 {
553 MSDebug("MDSSession::~MDSSession");
554 }
555
556 void
557 MDSSession::terminate ()
558 {
559 MSDebug("MDSSession::terminate");
560 closeAll();
561 }
562
563 const char* kExceptionDeletePath = "messages";
564
565
566 /*
567 * Called by security server via MDS_Install().
568 */
569 void
570 MDSSession::install ()
571 {
572 //
573 // Installation requires root
574 //
575 if(geteuid() != (uid_t)0) {
576 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
577 }
578
579 //
580 // install() is only (legitimately) called from securityd.
581 // Mark "server mode" so we don't end up waiting for ourselves when the databases open.
582 //
583 mModule.setServerMode();
584
585 int sysFdLock = -1;
586 try {
587 /* ensure MDS base directory exists with correct permissions */
588 if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) {
589 MSDebug("Error creating base MDS dir; aborting.");
590 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
591 }
592
593 /* ensure the the system MDS DB directory exists with correct permissions */
594 if(createDir(MDS_SYSTEM_DB_DIR, MDS_SYSTEM_UID, MDS_SYSTEM_DB_DIR_MODE)) {
595 MSDebug("Error creating system MDS dir; aborting.");
596 CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
597 }
598
599 if(!obtainLock(MDS_INSTALL_LOCK_PATH, sysFdLock, DB_LOCK_TIMEOUT)) {
600 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
601 }
602
603 /*
604 * We own the whole MDS system. Clean everything out except for our lock
605 * (and the directory it's in :-)
606 */
607
608 const char *savedFile = MDS_INSTALL_LOCK_NAME;
609 if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) {
610 /* this should never happen - we're root */
611 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
612 }
613
614 const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath};
615 if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) {
616 /* this should never happen - we're root */
617 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
618 }
619
620 /*
621 * Do initial population of system DBs.
622 */
623 createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE);
624 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
625 dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH);
626 }
627 catch(...) {
628 if(sysFdLock != -1) {
629 releaseLock(sysFdLock);
630 }
631 throw;
632 }
633 releaseLock(sysFdLock);
634 }
635
636 //
637 // In this implementation, the uninstall() call is not supported since
638 // we do not allow programmatic deletion of the MDS databases.
639 //
640
641 void
642 MDSSession::uninstall ()
643 {
644 CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
645 }
646
647 /*
648 * Common private open routine given a full specified path.
649 */
650 CSSM_DB_HANDLE MDSSession::dbOpen(const char *dbName, bool batched)
651 {
652 static CSSM_APPLEDL_OPEN_PARAMETERS batchOpenParams = {
653 sizeof(CSSM_APPLEDL_OPEN_PARAMETERS),
654 CSSM_APPLEDL_OPEN_PARAMETERS_VERSION,
655 CSSM_FALSE, // do not auto-commit
656 0 // mask - do not use following fields
657 };
658
659 MSDebug("Opening %s%s", dbName, batched ? " in batch mode" : "");
660 MSIoDbg("open %s in dbOpen(name, batched)", dbName);
661 CSSM_DB_HANDLE dbHand;
662 DatabaseSession::DbOpen(dbName,
663 NULL, // DbLocation
664 CSSM_DB_ACCESS_READ,
665 NULL, // AccessCred - hopefully optional
666 batched ? &batchOpenParams : NULL,
667 dbHand);
668 return dbHand;
669 }
670
671 /* DatabaseSession routines we need to override */
672 void MDSSession::DbOpen(const char *DbName,
673 const CSSM_NET_ADDRESS *DbLocation,
674 CSSM_DB_ACCESS_TYPE AccessRequest,
675 const AccessCredentials *AccessCred,
676 const void *OpenParameters,
677 CSSM_DB_HANDLE &DbHandle)
678 {
679 if (!mModule.serverMode()) {
680 /*
681 * Make sure securityd has finished initializing (system) MDS data.
682 * Note that activate() only does IPC once and retains global state after that.
683 */
684 SecurityServer::ClientSession client(Allocator::standard(), Allocator::standard());
685 client.activate(); /* contact securityd - won't return until MDS is ready */
686 }
687
688 /* make sure DBs are up-to-date */
689 updateDataBases();
690
691 /*
692 * Only task here is map incoming DbName - specified in the CDSA
693 * spec - to a filename we actually use (which is a path to either
694 * a system MDS DB file or a per-user MDS DB file).
695 */
696 if(DbName == NULL) {
697 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
698 }
699 const char *dbName;
700 if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) {
701 dbName = MDS_OBJECT_DB_NAME;
702 }
703 else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) {
704 dbName = MDS_DIRECT_DB_NAME;
705 }
706 else {
707 CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
708 }
709 char fullPath[MAXPATHLEN];
710 dbFullPath(dbName, fullPath);
711 MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName);
712 DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred,
713 OpenParameters, DbHandle);
714 }
715
716 CSSM_HANDLE MDSSession::DataGetFirst(CSSM_DB_HANDLE DBHandle,
717 const CssmQuery *Query,
718 CSSM_DB_RECORD_ATTRIBUTE_DATA_PTR Attributes,
719 CssmData *Data,
720 CSSM_DB_UNIQUE_RECORD_PTR &UniqueId)
721 {
722 updateDataBases();
723 return DatabaseSession::DataGetFirst(DBHandle, Query, Attributes, Data, UniqueId);
724 }
725
726
727 void
728 MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList)
729 {
730 outNameList = new CSSM_NAME_LIST[1];
731 outNameList->NumStrings = 2;
732 outNameList->String = new char*[2];
733 outNameList->String[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME);
734 outNameList->String[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME);
735 }
736
737 void
738 MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList)
739 {
740 delete [] inNameList.String[0];
741 delete [] inNameList.String[1];
742 delete [] inNameList.String;
743 }
744
745 void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle,
746 char **DbName)
747 {
748 printf("GetDbNameFromHandle: code on demand\n");
749 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
750 }
751
752 //
753 // Attempt to obtain an exclusive lock over the the MDS databases. The
754 // parameter is the maximum amount of time, in milliseconds, to spend
755 // trying to obtain the lock. A value of zero means to return failure
756 // right away if the lock cannot be obtained.
757 //
758 bool
759 MDSSession::obtainLock(
760 const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH
761 int &fd, // IN/OUT
762 int timeout) // default 0
763 {
764 fd = -1;
765 for(;;) {
766 secdebug("mdslock", "obtainLock: calling open(%s)", lockFile);
767 fd = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
768 if(fd == -1) {
769 int err = errno;
770 secdebug("mdslock", "obtainLock: open error %d", errno);
771 if(err == EINTR) {
772 /* got a signal, go again */
773 continue;
774 }
775 else {
776 /* theoretically should never happen */
777 return false;
778 }
779 }
780 else {
781 secdebug("mdslock", "obtainLock: success");
782 return true;
783 }
784 }
785
786 /* not reached */
787 return false;
788 }
789
790 //
791 // Release the exclusive lock over the MDS databases. If this session
792 // does not hold the lock, this method does nothing.
793 //
794
795 void
796 MDSSession::releaseLock(int &fd)
797 {
798 secdebug("mdslock", "releaseLock");
799 assert(fd != -1);
800 flock(fd, LOCK_UN);
801 close(fd);
802 fd = -1;
803 }
804
805 /* given DB file name, fill in fully specified path */
806 void MDSSession::dbFullPath(
807 const char *dbName,
808 char fullPath[MAXPATHLEN+1])
809 {
810 mModule.getDbPath(fullPath);
811 assert(fullPath[0] != '\0');
812 strcat(fullPath, "/");
813 strcat(fullPath, dbName);
814 }
815
816 /*
817 * See if any per-user bundles exist in specified directory. Returns true if so.
818 * First the check for one entry....
819 */
820 static bool isBundle(
821 const struct dirent *dp)
822 {
823 if(dp == NULL) {
824 return false;
825 }
826 /* NFS directories show up as DT_UNKNOWN */
827 switch(dp->d_type) {
828 case DT_UNKNOWN:
829 case DT_DIR:
830 break;
831 default:
832 return false;
833 }
834 int suffixLen = strlen(MDS_BUNDLE_EXTEN);
835 int len = strlen(dp->d_name);
836
837 return (len >= suffixLen) &&
838 !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN);
839 }
840
841 /* now the full directory search */
842 static bool checkUserBundles(
843 const char *bundlePath)
844 {
845 MSDebug("searching for user bundles in %s", bundlePath);
846 DIR *dir = opendir(bundlePath);
847 if (dir == NULL) {
848 return false;
849 }
850 struct dirent *dp;
851 bool rtn = false;
852 while ((dp = readdir(dir)) != NULL) {
853 if(isBundle(dp)) {
854 /* any other checking to do? */
855 rtn = true;
856 break;
857 }
858 }
859 closedir(dir);
860 MSDebug("...%s bundle(s) found", rtn ? "" : "No");
861 return rtn;
862 }
863
864 #define COPY_BUF_SIZE 1024
865
866 /*
867 * Single file copy with locking.
868 * Ensures that the source is a regular file with specified owner.
869 * Caller specifies mode of destination file.
870 * Throws a CssmError if the source file doesn't meet spec; throws a
871 * UnixError on any other error (which is generally recoverable by
872 * having the user MDS session use the system DB files).
873 */
874 static void safeCopyFile(
875 const char *fromPath,
876 uid_t fromUid,
877 const char *toPath,
878 mode_t toMode)
879 {
880 int error = 0;
881 bool haveLock = false;
882 int destFd = 0;
883 int srcFd = 0;
884 struct stat sb;
885 char tmpToPath[MAXPATHLEN+1];
886
887 MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath);
888
889 if(!doesFileExist(fromPath, fromUid, false, sb)) {
890 MSDebug("safeCopyFile: bad system DB file %s", fromPath);
891 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
892 }
893
894 /* create temp destination */
895 snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath);
896 destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode);
897 if(destFd < 0) {
898 error = errno;
899 MSDebug("Error %d opening user DB file %s\n", error, tmpToPath);
900 UnixError::throwMe(error);
901 }
902
903 struct flock fl;
904 try {
905 /* don't get tripped up by umask */
906 if(fchmod(destFd, toMode)) {
907 error = errno;
908 MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath);
909 UnixError::throwMe(error);
910 }
911
912 /* open source for reading */
913 srcFd = open(fromPath, O_RDONLY, 0);
914 if(srcFd < 0) {
915 error = errno;
916 MSDebug("Error %d opening system DB file %s\n", error, fromPath);
917 UnixError::throwMe(error);
918 }
919
920 /* acquire the same kind of lock AtomicFile uses */
921 fl.l_start = 0;
922 fl.l_len = 1;
923 fl.l_pid = getpid();
924 fl.l_type = F_RDLCK; // AtomicFile gets F_WRLCK
925 fl.l_whence = SEEK_SET;
926
927 // Keep trying to obtain the lock if we get interupted.
928 for (;;) {
929 if (::fcntl(srcFd, F_SETLKW, &fl) == -1) {
930 error = errno;
931 if (error == EINTR) {
932 error = 0;
933 continue;
934 }
935 MSDebug("Error %d locking system DB file %s\n", error, fromPath);
936 UnixError::throwMe(error);
937 }
938 else {
939 break;
940 haveLock = true;
941 }
942 }
943
944 /* copy */
945 char buf[COPY_BUF_SIZE];
946 while(1) {
947 int bytesRead = read(srcFd, buf, COPY_BUF_SIZE);
948 if(bytesRead == 0) {
949 break;
950 }
951 if(bytesRead < 0) {
952 error = errno;
953 MSDebug("Error %d reading system DB file %s\n", error, fromPath);
954 UnixError::throwMe(error);
955 }
956 int bytesWritten = write(destFd, buf, bytesRead);
957 if(bytesWritten < 0) {
958 error = errno;
959 MSDebug("Error %d writing user DB file %s\n", error, tmpToPath);
960 UnixError::throwMe(error);
961 }
962 }
963 }
964 catch(...) {
965 /* error is nonzero, we'll re-throw below...still have some cleanup */
966 }
967
968 /* unlock source and close both */
969 if(haveLock) {
970 fl.l_type = F_UNLCK;
971 if (::fcntl(srcFd, F_SETLK, &fl) == -1) {
972 MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath);
973 }
974 }
975 MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath);
976 if(srcFd) {
977 close(srcFd);
978 }
979 if(destFd) {
980 close(destFd);
981 }
982 if(error == 0) {
983 /* commit temp file */
984 if(::rename(tmpToPath, toPath)) {
985 error = errno;
986 MSDebug("Error %d committing %s\n", error, toPath);
987 }
988 }
989 if(error) {
990 UnixError::throwMe(error);
991 }
992 }
993
994 /*
995 * Copy system DB files to specified user dir. Caller holds user DB lock.
996 * Throws a UnixError on error.
997 */
998 static void copySystemDbs(
999 const char *userDbFileDir)
1000 {
1001 char toPath[MAXPATHLEN+1];
1002
1003 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME);
1004 safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1005 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME);
1006 safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1007 }
1008
1009 /*
1010 * Ensure current DB files exist and are up-to-date.
1011 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
1012 * other public functions which access a DB from scratch.
1013 */
1014 void MDSSession::updateDataBases()
1015 {
1016 RecursionBlock::Once once(mUpdating);
1017 if (once())
1018 return; // already updating; don't recurse
1019
1020 uid_t ourUid = geteuid();
1021 bool isRoot = (ourUid == 0);
1022
1023 /* if we scanned recently, we're done */
1024 double delta = mModule.timeSinceLastScan();
1025 if(delta < (double)MDS_SCAN_INTERVAL) {
1026 return;
1027 }
1028
1029 /*
1030 * If we're root, the first thing we do is to ensure that system DBs are present.
1031 * Note that this is a necessary artifact of the problem behind Radar 3800811.
1032 * When that is fixed, install() should ONLY be called from the public MDS_Install()
1033 * routine.
1034 * Anyway, if we *do* have to install here, we're done.
1035 */
1036 if(isRoot && !systemDatabasesPresent(false)) {
1037 install();
1038 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1039 mModule.lastScanIsNow();
1040 return;
1041 }
1042
1043 /*
1044 * Obtain various per-user paths. Root is a special case but follows most
1045 * of the same logic from here on.
1046 */
1047 std::string userDBFileDir = GetMDSDBDir();
1048 std::string userObjDBFilePath = GetMDSObjectDBPath();
1049 std::string userDirectDBFilePath = GetMDSDirectDBPath();
1050 char userBundlePath[MAXPATHLEN+1];
1051 std::string userDbLockPath = GetMDSDBLockPath();
1052
1053 /* this means "no user bundles" */
1054 userBundlePath[0] = '\0';
1055 if(!isRoot) {
1056 char *userHome = getenv("HOME");
1057 if((userHome == NULL) ||
1058 (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) {
1059 /* Can't check for user bundles */
1060 MSDebug("missing or invalid HOME; skipping user bundle check");
1061 }
1062 /* TBD: any other checking of userHome? */
1063 else {
1064 snprintf(userBundlePath, sizeof(userBundlePath),
1065 "%s/%s", userHome, MDS_USER_BUNDLE);
1066 }
1067 }
1068
1069 /*
1070 * Create the per-user directory...that's where the lock we'll be using lives.
1071 */
1072 if(!isRoot) {
1073 if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) {
1074 /*
1075 * We'll just have to limp along using the read-only system DBs.
1076 * Note that this protects (somewhat) against the DoS attack in
1077 * Radar 3801292. The only problem is that this user won't be able
1078 * to use per-user bundles.
1079 */
1080 MSDebug("Error creating user DBs; using system DBs");
1081 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1082 return;
1083 }
1084 }
1085
1086 /* always release userLockFd no matter what happens */
1087 int userLockFd = -1;
1088 if(!obtainLock(userDbLockPath.c_str(), userLockFd, DB_LOCK_TIMEOUT)) {
1089 CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
1090 }
1091 try {
1092 if(!isRoot) {
1093 try {
1094 /*
1095 * We copy the system DBs to the per-user DBs in two cases:
1096 * -- user DBs don't exist, or
1097 * -- system DBs have changed since the the last update to the user DBs.
1098 * This happens on smart card insertion and removal.
1099 */
1100 bool doCopySystem = false;
1101 struct stat userObjStat, userDirectStat;
1102 if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true,
1103 userObjStat, userDirectStat)) {
1104 doCopySystem = true;
1105 }
1106 else {
1107 /* compare the two mdsDirectory.db files */
1108 MSIoDbg("stat %s, %s in updateDataBases",
1109 MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str());
1110 struct stat sysStat;
1111 if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) {
1112 doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) ||
1113 ((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) &&
1114 (sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec));
1115 if(doCopySystem) {
1116 MSDebug("user DB files obsolete at %s", userDBFileDir.c_str());
1117 }
1118 }
1119 }
1120 if(doCopySystem) {
1121 /* copy system DBs to user DBs */
1122 MSDebug("copying system DBs to user at %s", userDBFileDir.c_str());
1123 copySystemDbs(userDBFileDir.c_str());
1124 }
1125 else {
1126 MSDebug("Using existing user DBs at %s", userDBFileDir.c_str());
1127 }
1128 }
1129 catch(const CssmError &cerror) {
1130 /*
1131 * Bad system DB file detected. Fatal.
1132 */
1133 throw;
1134 }
1135 catch(...) {
1136 /*
1137 * Error on delete or create user DBs; fall back on system DBs.
1138 */
1139 MSDebug("doFilesExist(purge) error; using system DBs");
1140 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1141 releaseLock(userLockFd);
1142 return;
1143 }
1144 }
1145 else {
1146 MSDebug("Using system DBs only");
1147 }
1148
1149 /*
1150 * Update per-user DBs from both bundle sources (System bundles, user bundles)
1151 * as appropriate.
1152 */
1153 DbFilesInfo dbFiles(*this, userDBFileDir.c_str());
1154 dbFiles.removeOutdatedPlugins();
1155 dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH);
1156 if(userBundlePath[0]) {
1157 /* skip for invalid or missing $HOME... */
1158 if(checkUserBundles(userBundlePath)) {
1159 dbFiles.updateForBundleDir(userBundlePath);
1160 }
1161 }
1162 mModule.setDbPath(userDBFileDir.c_str());
1163 } /* main block protected by mLockFd */
1164 catch(...) {
1165 releaseLock(userLockFd);
1166 throw;
1167 }
1168 mModule.lastScanIsNow();
1169 releaseLock(userLockFd);
1170 }
1171
1172 /*
1173 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
1174 */
1175 void MDSSession::removeRecordsForGuid(
1176 const char *guid,
1177 CSSM_DB_HANDLE dbHand)
1178 {
1179 // tell the DB to flush its intermediate data to disk
1180 PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1181 CssmClient::Query query = Attribute("ModuleID") == guid;
1182 clearRecords(dbHand, query.cssmQuery());
1183 }
1184
1185
1186 void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query)
1187 {
1188 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1189 CSSM_HANDLE resultHand = DataGetFirst(dbHand,
1190 &query,
1191 NULL,
1192 NULL, // No data
1193 record);
1194 if (resultHand == CSSM_INVALID_HANDLE)
1195 return; // no matches
1196 try {
1197 do {
1198 DataDelete(dbHand, *record);
1199 FreeUniqueRecord(dbHand, *record);
1200 record = NULL;
1201 } while (DataGetNext(dbHand,
1202 resultHand,
1203 NULL,
1204 NULL,
1205 record));
1206 } catch (...) {
1207 if (record)
1208 FreeUniqueRecord(dbHand, *record);
1209 DataAbortQuery(dbHand, resultHand);
1210 }
1211 }
1212
1213
1214 /*
1215 * Determine if system databases are present.
1216 * If the purge argument is true, we'll ensure that either both or neither
1217 * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH.
1218 */
1219 bool MDSSession::systemDatabasesPresent(bool purge)
1220 {
1221 bool rtn = false;
1222
1223 try {
1224 /*
1225 * This can throw on a failed attempt to delete sole existing file....
1226 * But if that happens while we're root, our goose is fully cooked.
1227 */
1228 struct stat objDbSb, directDbSb;
1229 if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH,
1230 MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) {
1231 rtn = true;
1232 }
1233 }
1234 catch(...) {
1235
1236 }
1237 return rtn;
1238 }
1239
1240 /*
1241 * Given a DB name (which is used as an absolute path) and an array of
1242 * RelationInfos, create a DB.
1243 */
1244 void
1245 MDSSession::createSystemDatabase(
1246 const char *dbName,
1247 const RelationInfo *relationInfo,
1248 unsigned numRelations,
1249 CSSM_BOOL autoCommit,
1250 mode_t mode,
1251 CSSM_DB_HANDLE &dbHand) // RETURNED
1252 {
1253 CSSM_DBINFO dbInfo;
1254 CSSM_DBINFO_PTR dbInfoP = &dbInfo;
1255
1256 memset(dbInfoP, 0, sizeof(CSSM_DBINFO));
1257 dbInfoP->NumberOfRecordTypes = numRelations;
1258 dbInfoP->IsLocal = CSSM_TRUE; // TBD - what does this mean?
1259 dbInfoP->AccessPath = NULL; // TBD
1260
1261 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
1262 * info arrays */
1263 unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations;
1264 dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
1265 memset(dbInfoP->DefaultParsingModules, 0, size);
1266 size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations;
1267 dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
1268 memset(dbInfoP->RecordAttributeNames, 0, size);
1269 size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations;
1270 dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
1271 memset(dbInfoP->RecordIndexes, 0, size);
1272
1273 /* cook up attribute and index info for each relation */
1274 unsigned relation;
1275 for(relation=0; relation<numRelations; relation++) {
1276 const struct RelationInfo *relp = &relationInfo[relation]; // source
1277 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
1278 &dbInfoP->RecordAttributeNames[relation]; // dest 1
1279 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
1280 &dbInfoP->RecordIndexes[relation]; // dest 2
1281
1282 attrInfo->DataRecordType = relp->DataRecordType;
1283 attrInfo->NumberOfAttributes = relp->NumberOfAttributes;
1284 attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo;
1285
1286 indexInfo->DataRecordType = relp->DataRecordType;
1287 indexInfo->NumberOfIndexes = relp->NumberOfIndexes;
1288 indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo;
1289 }
1290
1291 /* set autocommit and mode */
1292 CSSM_APPLEDL_OPEN_PARAMETERS openParams;
1293 memset(&openParams, 0, sizeof(openParams));
1294 openParams.length = sizeof(openParams);
1295 openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
1296 openParams.autoCommit = autoCommit;
1297 openParams.mask = kCSSM_APPLEDL_MASK_MODE;
1298 openParams.mode = mode;
1299
1300 try {
1301 DbCreate(dbName,
1302 NULL, // DbLocation
1303 *dbInfoP,
1304 CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
1305 NULL, // CredAndAclEntry
1306 &openParams,
1307 dbHand);
1308 }
1309 catch(...) {
1310 MSDebug("Error on DbCreate");
1311 free(dbInfoP->DefaultParsingModules);
1312 free(dbInfoP->RecordAttributeNames);
1313 free(dbInfoP->RecordIndexes);
1314 throw;
1315 }
1316 free(dbInfoP->DefaultParsingModules);
1317 free(dbInfoP->RecordAttributeNames);
1318 free(dbInfoP->RecordIndexes);
1319
1320 }
1321
1322 /*
1323 * Create system databases from scratch if they do not already exist.
1324 * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
1325 * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH).
1326 * Returns true if we actually built the files, false if they already
1327 * existed.
1328 */
1329 bool MDSSession::createSystemDatabases(
1330 CSSM_BOOL autoCommit,
1331 mode_t mode)
1332 {
1333 CSSM_DB_HANDLE objectDbHand = 0;
1334 CSSM_DB_HANDLE directoryDbHand = 0;
1335
1336 assert(geteuid() == (uid_t)0);
1337 if(systemDatabasesPresent(true)) {
1338 /* both databases exist as regular files with correct owner - we're done */
1339 MSDebug("system DBs already exist");
1340 return false;
1341 }
1342
1343 /* create two DBs - any exception here results in deleting both of them */
1344 MSDebug("Creating MDS DBs");
1345 try {
1346 createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1,
1347 autoCommit, mode, objectDbHand);
1348 MSIoDbg("close objectDbHand in createSystemDatabases");
1349 DbClose(objectDbHand);
1350 objectDbHand = 0;
1351 createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations,
1352 autoCommit, mode, directoryDbHand);
1353 MSIoDbg("close directoryDbHand in createSystemDatabases");
1354 DbClose(directoryDbHand);
1355 directoryDbHand = 0;
1356 }
1357 catch (...) {
1358 MSDebug("Error creating MDS DBs - deleting both DB files");
1359 unlink(MDS_OBJECT_DB_PATH);
1360 unlink(MDS_DIRECT_DB_PATH);
1361 throw;
1362 }
1363 return true;
1364 }
1365
1366 /*
1367 * DbFilesInfo helper class
1368 */
1369
1370 /* Note both DB files MUST exist at construction time */
1371 MDSSession::DbFilesInfo::DbFilesInfo(
1372 MDSSession &session,
1373 const char *dbPath) :
1374 mSession(session),
1375 mObjDbHand(0),
1376 mDirectDbHand(0),
1377 mLaterTimestamp(0)
1378 {
1379 assert(strlen(dbPath) < MAXPATHLEN);
1380 strcpy(mDbPath, dbPath);
1381
1382 /* stat the two DB files, snag the later timestamp */
1383 char path[MAXPATHLEN];
1384 sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1385 struct stat sb;
1386 MSIoDbg("stat %s in DbFilesInfo()", path);
1387 int rtn = ::stat(path, &sb);
1388 if(rtn) {
1389 int error = errno;
1390 MSDebug("Error %d statting DB file %s", error, path);
1391 UnixError::throwMe(error);
1392 }
1393 mLaterTimestamp = sb.st_mtimespec.tv_sec;
1394 sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1395 MSIoDbg("stat %s in DbFilesInfo()", path);
1396 rtn = ::stat(path, &sb);
1397 if(rtn) {
1398 int error = errno;
1399 MSDebug("Error %d statting DB file %s", error, path);
1400 UnixError::throwMe(error);
1401 }
1402 if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1403 mLaterTimestamp = sb.st_mtimespec.tv_sec;
1404 }
1405 }
1406
1407 MDSSession::DbFilesInfo::~DbFilesInfo()
1408 {
1409 if(mObjDbHand != 0) {
1410 /* autocommit on, henceforth */
1411 mSession.PassThrough(mObjDbHand,
1412 CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1413 MSIoDbg("close objectDbHand in ~DbFilesInfo()");
1414 mSession.DbClose(mObjDbHand);
1415 mObjDbHand = 0;
1416 }
1417 if(mDirectDbHand != 0) {
1418 mSession.PassThrough(mDirectDbHand,
1419 CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1420 MSIoDbg("close mDirectDbHand in ~DbFilesInfo()");
1421 mSession.DbClose(mDirectDbHand);
1422 mDirectDbHand = 0;
1423 }
1424 }
1425
1426 /* lazy evaluation of both DB handlesÊ*/
1427 CSSM_DB_HANDLE MDSSession::DbFilesInfo::objDbHand()
1428 {
1429 if(mObjDbHand != 0) {
1430 return mObjDbHand;
1431 }
1432 char fullPath[MAXPATHLEN + 1];
1433 sprintf(fullPath, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1434 MSIoDbg("open %s in objDbHand()", fullPath);
1435 mObjDbHand = mSession.dbOpen(fullPath, true); // batch mode
1436 return mObjDbHand;
1437 }
1438
1439 CSSM_DB_HANDLE MDSSession::DbFilesInfo::directDbHand()
1440 {
1441 if(mDirectDbHand != 0) {
1442 return mDirectDbHand;
1443 }
1444 char fullPath[MAXPATHLEN + 1];
1445 sprintf(fullPath, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1446 MSIoDbg("open %s in directDbHand()", fullPath);
1447 mDirectDbHand = mSession.dbOpen(fullPath, true); // batch mode
1448 return mDirectDbHand;
1449 }
1450
1451 /*
1452 * Update the info for Security.framework and the system bundles.
1453 */
1454 void MDSSession::DbFilesInfo::updateSystemDbInfo(
1455 const char *systemPath, // e.g., /System/Library/Frameworks
1456 const char *bundlePath) // e.g., /System/Library/Security
1457 {
1458 /*
1459 * Security.framework - CSSM and built-in modules - only for initial population of
1460 * system DB files.
1461 */
1462 if (systemPath) {
1463 string path;
1464 if (CFRef<CFBundleRef> me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")))
1465 if (CFRef<CFURLRef> url = CFBundleCopyBundleURL(me))
1466 if (CFRef<CFStringRef> cfpath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle))
1467 path = cfString(cfpath); // path to my bundle
1468
1469 if (path.empty()) // use system default
1470 path = string(systemPath) + "/" MDS_SYSTEM_FRAME;
1471 updateForBundle(path.c_str());
1472 }
1473
1474 /* Standard loadable bundles */
1475 updateForBundleDir(bundlePath);
1476 }
1477
1478
1479 MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1480 const CSSM_DATA &guid)
1481 {
1482 assert(guid.Length <= MAX_GUID_LEN);
1483 assert(guid.Length != 0);
1484 memmove(mGuid, guid.Data, guid.Length);
1485 if(mGuid[guid.Length - 1] != '\0') {
1486 mGuid[guid.Length] = '\0';
1487 }
1488 }
1489
1490 /*
1491 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1492 * If so, add to tbdVector.
1493 */
1494 void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1495 const CSSM_DATA &pathValue,
1496 const CSSM_DATA &guidValue,
1497 TbdVector &tbdVector)
1498 {
1499 /* stat the specified plugin */
1500 struct stat sb;
1501 bool obsolete = false;
1502 string path = CssmData::overlay(pathValue).toString();
1503 if (!path.empty() && path[0] == '*') {
1504 /* builtin pseudo-path; never obsolete this */
1505 return;
1506 }
1507 MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str());
1508 int rtn = ::stat(path.c_str(), &sb);
1509 if(rtn) {
1510 /* not there or inaccessible; delete */
1511 obsolete = true;
1512 }
1513 else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1514 /* timestamp of plugin's main directory later than that of DBs */
1515 obsolete = true;
1516 }
1517 if(obsolete) {
1518 TbdRecord *tbdRecord = new TbdRecord(guidValue);
1519 tbdVector.push_back(tbdRecord);
1520 MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str());
1521 }
1522 }
1523
1524 /*
1525 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1526 * i.e., with any path) which are either not present on disk, or which
1527 * have changed since dbFiles.laterTimestamp().
1528 */
1529 void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1530 {
1531 CSSM_QUERY query;
1532 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1533 CSSM_HANDLE resultHand;
1534 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
1535 CSSM_DB_ATTRIBUTE_DATA theAttrs[2];
1536 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo;
1537 TbdVector tbdRecords;
1538
1539 /*
1540 * First, scan object directory. All we need are the path and GUID attributes.
1541 */
1542 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1543 recordAttrs.SemanticInformation = 0;
1544 recordAttrs.NumberOfAttributes = 2;
1545 recordAttrs.AttributeData = theAttrs;
1546
1547 attrInfo = &theAttrs[0].Info;
1548 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1549 attrInfo->Label.AttributeName = (char*) "ModuleID";
1550 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1551 theAttrs[0].NumberOfValues = 0;
1552 theAttrs[0].Value = NULL;
1553 attrInfo = &theAttrs[1].Info;
1554 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1555 attrInfo->Label.AttributeName = (char*) "Path";
1556 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1557 theAttrs[1].NumberOfValues = 0;
1558 theAttrs[1].Value = NULL;
1559
1560 /* just search by recordType, no predicates */
1561 query.RecordType = MDS_OBJECT_RECORDTYPE;
1562 query.Conjunctive = CSSM_DB_NONE;
1563 query.NumSelectionPredicates = 0;
1564 query.SelectionPredicate = NULL;
1565 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
1566 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
1567 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1568
1569 CssmQuery perryQuery(query);
1570 try {
1571 resultHand = mSession.DataGetFirst(objDbHand(),
1572 &perryQuery,
1573 &recordAttrs,
1574 NULL, // No data
1575 record);
1576 }
1577 catch(...) {
1578 MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1579 return; // ???
1580 }
1581 if(record) {
1582 mSession.FreeUniqueRecord(mObjDbHand, *record);
1583 }
1584 if(resultHand) {
1585 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1586 checkOutdatedPlugin(*theAttrs[1].Value, *theAttrs[0].Value,
1587 tbdRecords);
1588 }
1589 else {
1590 MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1591 }
1592 for(unsigned dex=0; dex<2; dex++) {
1593 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1594 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1595 if(attr->Value[attrDex].Data) {
1596 mSession.free(attr->Value[attrDex].Data);
1597 }
1598 }
1599 if(attr->Value) {
1600 mSession.free(attr->Value);
1601 }
1602 }
1603 }
1604 else {
1605 /* empty Object DB - we're done */
1606 MSDebug("removeOutdatedPlugins: empty object DB");
1607 return;
1608 }
1609
1610 /* now the rest of the object DB records */
1611 for(;;) {
1612 bool brtn = mSession.DataGetNext(objDbHand(),
1613 resultHand,
1614 &recordAttrs,
1615 NULL,
1616 record);
1617 if(!brtn) {
1618 /* end of data */
1619 break;
1620 }
1621 if(record) {
1622 mSession.FreeUniqueRecord(mObjDbHand, *record);
1623 }
1624 if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1625 checkOutdatedPlugin(*theAttrs[1].Value,
1626 *theAttrs[0].Value,
1627 tbdRecords);
1628 }
1629 else {
1630 MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1631 }
1632 for(unsigned dex=0; dex<2; dex++) {
1633 CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1634 for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1635 if(attr->Value[attrDex].Data) {
1636 mSession.free(attr->Value[attrDex].Data);
1637 }
1638 }
1639 if(attr->Value) {
1640 mSession.free(attr->Value);
1641 }
1642 }
1643 }
1644 /* no DataAbortQuery needed; we scanned until completion */
1645
1646 /*
1647 * We have a vector of plugins to be deleted. Remove all records from both
1648 * DBs associated with the plugins, as specified by guid.
1649 */
1650 unsigned numRecords = tbdRecords.size();
1651 for(unsigned i=0; i<numRecords; i++) {
1652 TbdRecord *tbdRecord = tbdRecords[i];
1653 mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand());
1654 mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand());
1655 }
1656 for(unsigned i=0; i<numRecords; i++) {
1657 delete tbdRecords[i];
1658 }
1659 }
1660
1661
1662 /*
1663 * Update DBs for all bundles in specified directory.
1664 */
1665 void MDSSession::DbFilesInfo::updateForBundleDir(
1666 const char *bundleDirPath)
1667 {
1668 /* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1669 * much too heavyweight */
1670 MSDebug("...updating DBs for dir %s", bundleDirPath);
1671 DIR *dir = opendir(bundleDirPath);
1672 if (dir == NULL) {
1673 MSDebug("updateForBundleDir: error %d opening %s", errno, bundleDirPath);
1674 return;
1675 }
1676 struct dirent *dp;
1677 char fullPath[MAXPATHLEN];
1678 while ((dp = readdir(dir)) != NULL) {
1679 if(isBundle(dp)) {
1680 sprintf(fullPath, "%s/%s", bundleDirPath, dp->d_name);
1681 updateForBundle(fullPath);
1682 }
1683 }
1684 closedir(dir);
1685 }
1686
1687 /*
1688 * lookup by path - just returns true if there is a record assoociated with the path
1689 * in mObjDbHand.
1690 */
1691 bool MDSSession::DbFilesInfo::lookupForPath(
1692 const char *path)
1693 {
1694 CSSM_QUERY query;
1695 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1696 CSSM_HANDLE resultHand = 0;
1697 CSSM_DB_RECORD_ATTRIBUTE_DATA recordAttrs;
1698 CSSM_DB_ATTRIBUTE_DATA theAttr;
1699 CSSM_DB_ATTRIBUTE_INFO_PTR attrInfo = &theAttr.Info;
1700 CSSM_SELECTION_PREDICATE predicate;
1701 CSSM_DATA predData;
1702
1703 recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1704 recordAttrs.SemanticInformation = 0;
1705 recordAttrs.NumberOfAttributes = 1;
1706 recordAttrs.AttributeData = &theAttr;
1707
1708 attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1709 attrInfo->Label.AttributeName = (char*) "Path";
1710 attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1711
1712 theAttr.NumberOfValues = 0;
1713 theAttr.Value = NULL;
1714
1715 predicate.DbOperator = CSSM_DB_EQUAL;
1716 predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1717 predicate.Attribute.Info.Label.AttributeName = (char*) "Path";
1718 predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1719 predData.Data = (uint8 *)path;
1720 predData.Length = strlen(path);
1721 predicate.Attribute.Value = &predData;
1722 predicate.Attribute.NumberOfValues = 1;
1723
1724 query.RecordType = MDS_OBJECT_RECORDTYPE;
1725 query.Conjunctive = CSSM_DB_NONE;
1726 query.NumSelectionPredicates = 1;
1727 query.SelectionPredicate = &predicate;
1728 query.QueryLimits.TimeLimit = 0; // FIXME - meaningful?
1729 query.QueryLimits.SizeLimit = 1; // FIXME - meaningful?
1730 query.QueryFlags = 0; // CSSM_QUERY_RETURN_DATA...FIXME - used?
1731
1732 bool ourRtn = true;
1733 try {
1734 CssmQuery perryQuery(query);
1735 resultHand = mSession.DataGetFirst(objDbHand(),
1736 &perryQuery,
1737 &recordAttrs,
1738 NULL, // No data
1739 record);
1740 }
1741 catch (...) {
1742 ourRtn = false;
1743 }
1744 if(record) {
1745 mSession.FreeUniqueRecord(mObjDbHand, *record);
1746 }
1747 else {
1748 ourRtn = false;
1749 }
1750 if(resultHand && ourRtn) {
1751 /* more resulting pending; terminate the search */
1752 try {
1753 mSession.DataAbortQuery(mObjDbHand, resultHand);
1754 }
1755 catch(...) {
1756 MSDebug("exception on DataAbortQuery in lookupForPath");
1757 }
1758 }
1759 for(unsigned dex=0; dex<theAttr.NumberOfValues; dex++) {
1760 if(theAttr.Value[dex].Data) {
1761 mSession.free(theAttr.Value[dex].Data);
1762 }
1763 }
1764 mSession.free(theAttr.Value);
1765 return ourRtn;
1766 }
1767
1768 /* update entry for one bundle, which is known to exist */
1769 void MDSSession::DbFilesInfo::updateForBundle(
1770 const char *bundlePath)
1771 {
1772 MSDebug("...updating DBs for bundle %s", bundlePath);
1773
1774 /* Quick lookup - do we have ANY entry for a bundle with this path? */
1775 if(lookupForPath(bundlePath)) {
1776 /* Yep, we're done */
1777 return;
1778 }
1779 MDSAttrParser parser(bundlePath,
1780 mSession,
1781 objDbHand(),
1782 directDbHand());
1783 try {
1784 parser.parseAttrs();
1785 }
1786 catch (const CssmError &err) {
1787 // a corrupt MDS info file invalidates the entire plugin
1788 const char *guid = parser.guid();
1789 if (guid) {
1790 mSession.removeRecordsForGuid(guid, objDbHand());
1791 mSession.removeRecordsForGuid(guid, directDbHand());
1792 }
1793 }
1794 }
1795
1796
1797 //
1798 // Private API: add MDS records from contents of file
1799 // These files are typically written by securityd and handed to us in this call.
1800 //
1801 void MDSSession::installFile(const MDS_InstallDefaults *defaults,
1802 const char *inBundlePath, const char *subdir, const char *file)
1803 {
1804 string bundlePath = inBundlePath ? inBundlePath : cfString(CFBundleGetMainBundle());
1805 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1806 MDSAttrParser parser(bundlePath.c_str(),
1807 *this,
1808 dbFiles.objDbHand(),
1809 dbFiles.directDbHand());
1810 parser.setDefaults(defaults);
1811
1812 try {
1813 if (file == NULL) // parse a directory
1814 if (subdir) // a particular directory
1815 parser.parseAttrs(CFTempString(subdir));
1816 else // all resources in bundle
1817 parser.parseAttrs(NULL);
1818 else // parse just one file
1819 parser.parseFile(CFRef<CFURLRef>(makeCFURL(file)), CFTempString(subdir));
1820 }
1821 catch (const CssmError &err) {
1822 const char *guid = parser.guid();
1823 if (guid) {
1824 removeRecordsForGuid(guid, dbFiles.objDbHand());
1825 removeRecordsForGuid(guid, dbFiles.directDbHand());
1826 }
1827 }
1828 }
1829
1830
1831 //
1832 // Private API: Remove all records for a guid/subservice
1833 //
1834 // Note: Multicursors searching for SSID fail because not all records in the
1835 // database have this attribute. So we have to explicitly run through all tables
1836 // that do.
1837 //
1838 void MDSSession::removeSubservice(const char *guid, uint32 ssid)
1839 {
1840 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1841
1842 CssmClient::Query query =
1843 Attribute("ModuleID") == guid &&
1844 Attribute("SSID") == ssid;
1845
1846 // only CSP and DL tables are cleared here
1847 // (this function is private to securityd, which only handles those types)
1848 clearRecords(dbFiles.directDbHand(),
1849 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE));
1850 clearRecords(dbFiles.directDbHand(),
1851 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE));
1852 clearRecords(dbFiles.directDbHand(),
1853 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE));
1854 clearRecords(dbFiles.directDbHand(),
1855 CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE));
1856 clearRecords(dbFiles.directDbHand(),
1857 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE));
1858 clearRecords(dbFiles.directDbHand(),
1859 CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE));
1860 }
1861
1862
1863 } // end namespace Security