]> git.saurik.com Git - apple/security.git/blame - OSX/libsecurity_mds/lib/MDSSession.cpp
Security-59306.140.5.tar.gz
[apple/security.git] / OSX / libsecurity_mds / lib / MDSSession.cpp
CommitLineData
b1ab9ed8 1/*
d8f41ccd 2 * Copyright (c) 2000-2001,2011-2014 Apple Inc. All Rights Reserved.
b1ab9ed8
A
3 *
4 * The contents of this file constitute Original Code as defined in and are
5 * subject to the Apple Public Source License Version 1.2 (the 'License').
6 * You may not use this file except in compliance with the License. Please obtain
7 * a copy of the License at http://www.apple.com/publicsource and read it before
8 * using this file.
9 *
10 * This Original Code and all software distributed under the License are
11 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESS
12 * OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, INCLUDING WITHOUT
13 * LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
14 * PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. Please see the License for the
15 * specific language governing rights and limitations under the License.
16 */
17
18
19#include "MDSSession.h"
20
21#include <security_cdsa_plugin/DbContext.h>
22#include "MDSModule.h"
23#include "MDSAttrParser.h"
24#include "MDSAttrUtils.h"
25
26#include <memory>
27#include <Security/cssmerr.h>
28#include <security_utilities/logging.h>
29#include <security_utilities/debugging.h>
30#include <security_utilities/cfutilities.h>
31#include <security_cdsa_client/dlquery.h>
32#include <securityd_client/ssclient.h>
33#include <Security/mds_schema.h>
34#include <CoreFoundation/CFBundle.h>
35
36#include <sys/types.h>
37#include <sys/param.h>
38#include <dirent.h>
39#include <fcntl.h>
40#include <assert.h>
41#include <time.h>
42#include <string>
43#include <unistd.h>
427c49bc 44#include <syslog.h>
b1ab9ed8
A
45
46using namespace CssmClient;
47
427c49bc 48/*
b1ab9ed8
A
49 * The layout of the various MDS DB files on disk is as follows:
50 *
51 * /var/db/mds -- owner = root, mode = 01777, world writable, sticky
52 * system/ -- owner = root, mode = 0755
53 * mdsObject.db -- owner = root, mode = 0644, object DB
54 * mdsDirectory.db -- owner = root, mode = 0644, MDS directory DB
55 * mds.lock -- temporary, owner = root, protects creation of and
56 * updates to previous two files
57 * mds.install.lock -- owner = root, protects MDS_Install operation
58 * <uid>/ -- owner = <uid>, mode = 0700
59 * mdsObject.db -- owner = <uid>, mode = 000, object DB
60 * mdsDirectory.db -- owner = <uid>, mode = 000, MDS directory DB
61 * mds.lock -- owner = <uid>, protects updates of previous two files
62 *
63 * The /var/db/mds/system directory is created at OS install time. The DB files in
64 * it are created by root at MDS_Install time. The ownership and mode of this directory and
65 * of its parent is also re-checked and forced to the correct state at MDS_Install time.
66 * Each user has their own private directory with two DB files and a lock. The first time
67 * a user accesses MDS, the per-user directory is created and the per-user DB files are
68 * created as copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system
69 * DB files when they are the source of these copies; this is the same mechanism
70 * used by the underlying AtomicFile.
71 *
6b200bc3 72 * The sticky bit in /var/db/mds ensures that users cannot modify other users' private
b1ab9ed8
A
73 * MDS directories.
74 */
75namespace 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 */
fa7225c8 129#define MSIoDbg(args...) secinfo("MDS_IO", ## args)
b1ab9ed8
A
130
131/* Trace cleanDir() */
fa7225c8 132#define MSCleanDirDbg(args...) secinfo("MDS_CleanDir", ## args)
b1ab9ed8
A
133
134static std::string GetMDSBaseDBDir(bool isRoot)
135{
136 // what we return depends on whether or not we are root
137 string retValue;
138 if (isRoot)
139 {
140 retValue = MDS_SYSTEM_DB_DIR;
141 }
142 else
143 {
144 char strBuffer[PATH_MAX + 1];
427c49bc
A
145 size_t result = confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer));
146 if (result == 0)
147 {
148 // we have an error, log it
866f8763
A
149 syslog(LOG_CRIT, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error: %d", errno);
150 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
427c49bc
A
151 }
152
b1ab9ed8
A
153 retValue = strBuffer;
154 }
155
156 return retValue;
157}
158
159
160
161static 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
180static std::string GetMDSObjectDBPath()
181{
182 return GetMDSDBDir() + "/" + MDS_OBJECT_DB_NAME;
183}
184
185
186
187static std::string GetMDSDirectDBPath()
188{
189 return GetMDSDBDir() + "/" + MDS_DIRECT_DB_PATH;
190}
191
192
193
194static 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 */
205static 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 */
315static 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. */
866f8763 329 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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)) {
866f8763 347 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
348 }
349 if(rmdir(filePath)) {
350 MSDebug("rmdir(%s) returned %d", filePath, errno);
866f8763 351 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
352 }
353 }
354 else {
355 if(unlink(filePath)) {
356 MSDebug("unlink(%s) returned %d", filePath, errno);
866f8763 357 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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 */
374static 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);
866f8763 400 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
401 }
402 }
403 if(directExist) {
404 if(unlink(directDbFile)) {
405 MSDebug("unlink(%s) returned %d", directDbFile, errno);
866f8763 406 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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 */
417typedef 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
424static 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 */
473static 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 */
544MDSSession::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
558MDSSession::~MDSSession ()
559{
560 MSDebug("MDSSession::~MDSSession");
561}
562
563void
564MDSSession::terminate ()
565{
566 MSDebug("MDSSession::terminate");
567 closeAll();
568}
569
570const char* kExceptionDeletePath = "messages";
571
572
573/*
574 * Called by security server via MDS_Install().
575 */
576void
577MDSSession::install ()
578{
579 //
580 // Installation requires root
581 //
582 if(geteuid() != (uid_t)0) {
866f8763 583 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
584 }
585
586 //
587 // install() is only (legitimately) called from securityd.
588 // Mark "server mode" so we don't end up waiting for ourselves when the databases open.
589 //
590 mModule.setServerMode();
591
b1ab9ed8
A
592 try {
593 /* ensure MDS base directory exists with correct permissions */
594 if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) {
595 MSDebug("Error creating base MDS dir; aborting.");
866f8763 596 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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.");
866f8763 602 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
603 }
604
427c49bc
A
605 LockHelper lh;
606
607 if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, DB_LOCK_TIMEOUT)) {
866f8763 608 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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 */
866f8763 619 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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 */
866f8763 625 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
626 }
627
628 /*
629 * Do initial population of system DBs.
630 */
631 createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE);
632 DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
633 dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH);
634 }
635 catch(...) {
b1ab9ed8
A
636 throw;
637 }
b1ab9ed8
A
638}
639
640//
641// In this implementation, the uninstall() call is not supported since
642// we do not allow programmatic deletion of the MDS databases.
643//
644
645void
646MDSSession::uninstall ()
647{
866f8763 648 CssmError::throwMeNoLogging(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
b1ab9ed8
A
649}
650
651/*
652 * Common private open routine given a full specified path.
653 */
654CSSM_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 */
676void 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) {
866f8763 701 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME);
b1ab9ed8
A
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 {
866f8763 711 CssmError::throwMeNoLogging(CSSMERR_DL_INVALID_DB_NAME);
b1ab9ed8
A
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
720CSSM_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
731void
732MDSSession::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
741void
742MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList)
743{
744 delete [] inNameList.String[0];
745 delete [] inNameList.String[1];
746 delete [] inNameList.String;
747}
748
749void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle,
750 char **DbName)
751{
752 printf("GetDbNameFromHandle: code on demand\n");
866f8763 753 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
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//
762bool
427c49bc 763MDSSession::LockHelper::obtainLock(
b1ab9ed8 764 const char *lockFile, // e.g. MDS_INSTALL_LOCK_PATH
b1ab9ed8
A
765 int timeout) // default 0
766{
427c49bc 767 mFD = -1;
b1ab9ed8 768 for(;;) {
fa7225c8 769 secinfo("mdslock", "obtainLock: calling open(%s)", lockFile);
427c49bc
A
770 mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
771 if(mFD == -1) {
b1ab9ed8 772 int err = errno;
fa7225c8 773 secinfo("mdslock", "obtainLock: open error %d", errno);
b1ab9ed8
A
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 {
fa7225c8 784 secinfo("mdslock", "obtainLock: success");
b1ab9ed8
A
785 return true;
786 }
787 }
b1ab9ed8
A
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
427c49bc 795MDSSession::LockHelper::~LockHelper()
b1ab9ed8 796{
fa7225c8 797 secinfo("mdslock", "releaseLock");
427c49bc
A
798 if (mFD == -1)
799 {
800 return;
801 }
802
803 flock(mFD, LOCK_UN);
804 close(mFD);
805 mFD = -1;
b1ab9ed8
A
806}
807
808/* given DB file name, fill in fully specified path */
809void MDSSession::dbFullPath(
810 const char *dbName,
811 char fullPath[MAXPATHLEN+1])
812{
813 mModule.getDbPath(fullPath);
814 assert(fullPath[0] != '\0');
815 strcat(fullPath, "/");
816 strcat(fullPath, dbName);
817}
818
819/*
820 * See if any per-user bundles exist in specified directory. Returns true if so.
821 * First the check for one entry....
822 */
823static bool isBundle(
824 const struct dirent *dp)
825{
826 if(dp == NULL) {
827 return false;
828 }
829 /* NFS directories show up as DT_UNKNOWN */
830 switch(dp->d_type) {
831 case DT_UNKNOWN:
832 case DT_DIR:
833 break;
834 default:
835 return false;
836 }
837 int suffixLen = strlen(MDS_BUNDLE_EXTEN);
427c49bc 838 size_t len = strlen(dp->d_name);
b1ab9ed8
A
839
840 return (len >= suffixLen) &&
841 !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN);
842}
843
844/* now the full directory search */
845static bool checkUserBundles(
846 const char *bundlePath)
847{
848 MSDebug("searching for user bundles in %s", bundlePath);
849 DIR *dir = opendir(bundlePath);
850 if (dir == NULL) {
851 return false;
852 }
853 struct dirent *dp;
854 bool rtn = false;
855 while ((dp = readdir(dir)) != NULL) {
856 if(isBundle(dp)) {
857 /* any other checking to do? */
858 rtn = true;
859 break;
860 }
861 }
862 closedir(dir);
863 MSDebug("...%s bundle(s) found", rtn ? "" : "No");
864 return rtn;
865}
866
5c19dc3a 867#define COPY_BUF_SIZE 65536
b1ab9ed8
A
868
869/*
870 * Single file copy with locking.
871 * Ensures that the source is a regular file with specified owner.
872 * Caller specifies mode of destination file.
873 * Throws a CssmError if the source file doesn't meet spec; throws a
874 * UnixError on any other error (which is generally recoverable by
875 * having the user MDS session use the system DB files).
876 */
877static void safeCopyFile(
878 const char *fromPath,
879 uid_t fromUid,
880 const char *toPath,
881 mode_t toMode)
882{
883 int error = 0;
884 bool haveLock = false;
885 int destFd = 0;
886 int srcFd = 0;
887 struct stat sb;
888 char tmpToPath[MAXPATHLEN+1];
889
890 MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath);
891
892 if(!doesFileExist(fromPath, fromUid, false, sb)) {
893 MSDebug("safeCopyFile: bad system DB file %s", fromPath);
866f8763 894 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
895 }
896
897 /* create temp destination */
898 snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath);
899 destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode);
900 if(destFd < 0) {
901 error = errno;
902 MSDebug("Error %d opening user DB file %s\n", error, tmpToPath);
866f8763 903 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
904 }
905
906 struct flock fl;
907 try {
908 /* don't get tripped up by umask */
909 if(fchmod(destFd, toMode)) {
910 error = errno;
911 MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath);
866f8763 912 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
913 }
914
915 /* open source for reading */
916 srcFd = open(fromPath, O_RDONLY, 0);
917 if(srcFd < 0) {
918 error = errno;
919 MSDebug("Error %d opening system DB file %s\n", error, fromPath);
866f8763 920 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
921 }
922
923 /* acquire the same kind of lock AtomicFile uses */
924 fl.l_start = 0;
925 fl.l_len = 1;
926 fl.l_pid = getpid();
927 fl.l_type = F_RDLCK; // AtomicFile gets F_WRLCK
928 fl.l_whence = SEEK_SET;
929
930 // Keep trying to obtain the lock if we get interupted.
931 for (;;) {
932 if (::fcntl(srcFd, F_SETLKW, &fl) == -1) {
933 error = errno;
934 if (error == EINTR) {
935 error = 0;
936 continue;
937 }
938 MSDebug("Error %d locking system DB file %s\n", error, fromPath);
866f8763 939 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
940 }
941 else {
942 break;
6b200bc3 943 //haveLock = true;
b1ab9ed8
A
944 }
945 }
946
947 /* copy */
5c19dc3a 948 char *buf = new char[COPY_BUF_SIZE];
b1ab9ed8 949 while(1) {
5c19dc3a
A
950 ssize_t bytesRead;
951
952 do {
953 bytesRead = read(srcFd, buf, COPY_BUF_SIZE);
954 } while (bytesRead < 0 && errno == EINTR);
955
b1ab9ed8
A
956 if(bytesRead == 0) {
957 break;
958 }
959 if(bytesRead < 0) {
5c19dc3a 960 delete [] buf;
b1ab9ed8
A
961 error = errno;
962 MSDebug("Error %d reading system DB file %s\n", error, fromPath);
866f8763 963 UnixError::throwMeNoLogging(error);
b1ab9ed8 964 }
5c19dc3a
A
965
966 ssize_t bytesWritten;
967
968 do {
969 bytesWritten = write(destFd, buf, bytesRead);
970 } while (bytesWritten < 0 && errno == EINTR);
971
b1ab9ed8 972 if(bytesWritten < 0) {
5c19dc3a 973 delete [] buf;
b1ab9ed8
A
974 error = errno;
975 MSDebug("Error %d writing user DB file %s\n", error, tmpToPath);
866f8763 976 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
977 }
978 }
5c19dc3a 979 delete [] buf;
b1ab9ed8
A
980 }
981 catch(...) {
982 /* error is nonzero, we'll re-throw below...still have some cleanup */
983 }
984
985 /* unlock source and close both */
986 if(haveLock) {
987 fl.l_type = F_UNLCK;
988 if (::fcntl(srcFd, F_SETLK, &fl) == -1) {
989 MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath);
990 }
991 }
992 MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath);
993 if(srcFd) {
994 close(srcFd);
995 }
996 if(destFd) {
997 close(destFd);
998 }
999 if(error == 0) {
1000 /* commit temp file */
1001 if(::rename(tmpToPath, toPath)) {
1002 error = errno;
1003 MSDebug("Error %d committing %s\n", error, toPath);
1004 }
1005 }
1006 if(error) {
866f8763 1007 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
1008 }
1009}
1010
1011/*
1012 * Copy system DB files to specified user dir. Caller holds user DB lock.
1013 * Throws a UnixError on error.
1014 */
1015static void copySystemDbs(
1016 const char *userDbFileDir)
1017{
1018 char toPath[MAXPATHLEN+1];
1019
1020 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME);
1021 safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1022 snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME);
1023 safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1024}
1025
1026/*
1027 * Ensure current DB files exist and are up-to-date.
1028 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
1029 * other public functions which access a DB from scratch.
1030 */
1031void MDSSession::updateDataBases()
1032{
1033 RecursionBlock::Once once(mUpdating);
1034 if (once())
1035 return; // already updating; don't recurse
1036
1037 uid_t ourUid = geteuid();
1038 bool isRoot = (ourUid == 0);
1039
1040 /* if we scanned recently, we're done */
1041 double delta = mModule.timeSinceLastScan();
1042 if(delta < (double)MDS_SCAN_INTERVAL) {
1043 return;
1044 }
1045
1046 /*
1047 * If we're root, the first thing we do is to ensure that system DBs are present.
1048 * Note that this is a necessary artifact of the problem behind Radar 3800811.
1049 * When that is fixed, install() should ONLY be called from the public MDS_Install()
1050 * routine.
1051 * Anyway, if we *do* have to install here, we're done.
1052 */
1053 if(isRoot && !systemDatabasesPresent(false)) {
1054 install();
1055 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1056 mModule.lastScanIsNow();
1057 return;
1058 }
1059
1060 /*
1061 * Obtain various per-user paths. Root is a special case but follows most
1062 * of the same logic from here on.
1063 */
1064 std::string userDBFileDir = GetMDSDBDir();
1065 std::string userObjDBFilePath = GetMDSObjectDBPath();
1066 std::string userDirectDBFilePath = GetMDSDirectDBPath();
1067 char userBundlePath[MAXPATHLEN+1];
1068 std::string userDbLockPath = GetMDSDBLockPath();
1069
1070 /* this means "no user bundles" */
1071 userBundlePath[0] = '\0';
1072 if(!isRoot) {
1073 char *userHome = getenv("HOME");
1074 if((userHome == NULL) ||
1075 (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) {
1076 /* Can't check for user bundles */
1077 MSDebug("missing or invalid HOME; skipping user bundle check");
1078 }
1079 /* TBD: any other checking of userHome? */
1080 else {
1081 snprintf(userBundlePath, sizeof(userBundlePath),
1082 "%s/%s", userHome, MDS_USER_BUNDLE);
1083 }
1084 }
1085
1086 /*
1087 * Create the per-user directory...that's where the lock we'll be using lives.
1088 */
1089 if(!isRoot) {
1090 if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) {
1091 /*
1092 * We'll just have to limp along using the read-only system DBs.
1093 * Note that this protects (somewhat) against the DoS attack in
1094 * Radar 3801292. The only problem is that this user won't be able
1095 * to use per-user bundles.
1096 */
1097 MSDebug("Error creating user DBs; using system DBs");
1098 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1099 return;
1100 }
1101 }
1102
1103 /* always release userLockFd no matter what happens */
427c49bc
A
1104 LockHelper lh;
1105
1106 if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) {
866f8763 1107 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
1108 }
1109 try {
1110 if(!isRoot) {
1111 try {
1112 /*
1113 * We copy the system DBs to the per-user DBs in two cases:
1114 * -- user DBs don't exist, or
1115 * -- system DBs have changed since the the last update to the user DBs.
1116 * This happens on smart card insertion and removal.
1117 */
1118 bool doCopySystem = false;
1119 struct stat userObjStat, userDirectStat;
1120 if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true,
1121 userObjStat, userDirectStat)) {
1122 doCopySystem = true;
1123 }
1124 else {
1125 /* compare the two mdsDirectory.db files */
1126 MSIoDbg("stat %s, %s in updateDataBases",
1127 MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str());
1128 struct stat sysStat;
1129 if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) {
1130 doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) ||
1131 ((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) &&
1132 (sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec));
1133 if(doCopySystem) {
1134 MSDebug("user DB files obsolete at %s", userDBFileDir.c_str());
1135 }
1136 }
1137 }
1138 if(doCopySystem) {
1139 /* copy system DBs to user DBs */
1140 MSDebug("copying system DBs to user at %s", userDBFileDir.c_str());
1141 copySystemDbs(userDBFileDir.c_str());
1142 }
1143 else {
1144 MSDebug("Using existing user DBs at %s", userDBFileDir.c_str());
1145 }
1146 }
1147 catch(const CssmError &cerror) {
1148 /*
1149 * Bad system DB file detected. Fatal.
1150 */
1151 throw;
1152 }
1153 catch(...) {
1154 /*
1155 * Error on delete or create user DBs; fall back on system DBs.
1156 */
1157 MSDebug("doFilesExist(purge) error; using system DBs");
1158 mModule.setDbPath(MDS_SYSTEM_DB_DIR);
b1ab9ed8
A
1159 return;
1160 }
1161 }
1162 else {
1163 MSDebug("Using system DBs only");
1164 }
1165
1166 /*
1167 * Update per-user DBs from both bundle sources (System bundles, user bundles)
1168 * as appropriate.
1169 */
1170 DbFilesInfo dbFiles(*this, userDBFileDir.c_str());
1171 dbFiles.removeOutdatedPlugins();
1172 dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH);
1173 if(userBundlePath[0]) {
1174 /* skip for invalid or missing $HOME... */
1175 if(checkUserBundles(userBundlePath)) {
1176 dbFiles.updateForBundleDir(userBundlePath);
1177 }
1178 }
1179 mModule.setDbPath(userDBFileDir.c_str());
1180 } /* main block protected by mLockFd */
1181 catch(...) {
b1ab9ed8
A
1182 throw;
1183 }
1184 mModule.lastScanIsNow();
b1ab9ed8
A
1185}
1186
1187/*
1188 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
1189 */
1190void MDSSession::removeRecordsForGuid(
1191 const char *guid,
1192 CSSM_DB_HANDLE dbHand)
1193{
1194 // tell the DB to flush its intermediate data to disk
1195 PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1196 CssmClient::Query query = Attribute("ModuleID") == guid;
1197 clearRecords(dbHand, query.cssmQuery());
1198}
1199
1200
1201void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query)
1202{
1203 CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1204 CSSM_HANDLE resultHand = DataGetFirst(dbHand,
1205 &query,
1206 NULL,
1207 NULL, // No data
1208 record);
1209 if (resultHand == CSSM_INVALID_HANDLE)
1210 return; // no matches
1211 try {
1212 do {
1213 DataDelete(dbHand, *record);
1214 FreeUniqueRecord(dbHand, *record);
1215 record = NULL;
1216 } while (DataGetNext(dbHand,
1217 resultHand,
1218 NULL,
1219 NULL,
1220 record));
1221 } catch (...) {
1222 if (record)
1223 FreeUniqueRecord(dbHand, *record);
1224 DataAbortQuery(dbHand, resultHand);
1225 }
1226}
1227
1228
1229/*
1230 * Determine if system databases are present.
1231 * If the purge argument is true, we'll ensure that either both or neither
1232 * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH.
1233 */
1234bool MDSSession::systemDatabasesPresent(bool purge)
1235{
1236 bool rtn = false;
1237
1238 try {
1239 /*
1240 * This can throw on a failed attempt to delete sole existing file....
1241 * But if that happens while we're root, our goose is fully cooked.
1242 */
1243 struct stat objDbSb, directDbSb;
1244 if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH,
1245 MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) {
1246 rtn = true;
1247 }
1248 }
1249 catch(...) {
1250
1251 }
1252 return rtn;
1253}
1254
1255/*
1256 * Given a DB name (which is used as an absolute path) and an array of
1257 * RelationInfos, create a DB.
1258 */
1259void
1260MDSSession::createSystemDatabase(
1261 const char *dbName,
1262 const RelationInfo *relationInfo,
1263 unsigned numRelations,
1264 CSSM_BOOL autoCommit,
1265 mode_t mode,
1266 CSSM_DB_HANDLE &dbHand) // RETURNED
1267{
1268 CSSM_DBINFO dbInfo;
1269 CSSM_DBINFO_PTR dbInfoP = &dbInfo;
1270
1271 memset(dbInfoP, 0, sizeof(CSSM_DBINFO));
1272 dbInfoP->NumberOfRecordTypes = numRelations;
1273 dbInfoP->IsLocal = CSSM_TRUE; // TBD - what does this mean?
1274 dbInfoP->AccessPath = NULL; // TBD
1275
1276 /* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
1277 * info arrays */
1278 unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations;
1279 dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
1280 memset(dbInfoP->DefaultParsingModules, 0, size);
1281 size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations;
1282 dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
1283 memset(dbInfoP->RecordAttributeNames, 0, size);
1284 size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations;
1285 dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
1286 memset(dbInfoP->RecordIndexes, 0, size);
1287
1288 /* cook up attribute and index info for each relation */
1289 unsigned relation;
1290 for(relation=0; relation<numRelations; relation++) {
1291 const struct RelationInfo *relp = &relationInfo[relation]; // source
1292 CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
1293 &dbInfoP->RecordAttributeNames[relation]; // dest 1
1294 CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
1295 &dbInfoP->RecordIndexes[relation]; // dest 2
1296
1297 attrInfo->DataRecordType = relp->DataRecordType;
1298 attrInfo->NumberOfAttributes = relp->NumberOfAttributes;
1299 attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo;
1300
1301 indexInfo->DataRecordType = relp->DataRecordType;
1302 indexInfo->NumberOfIndexes = relp->NumberOfIndexes;
1303 indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo;
1304 }
1305
1306 /* set autocommit and mode */
1307 CSSM_APPLEDL_OPEN_PARAMETERS openParams;
1308 memset(&openParams, 0, sizeof(openParams));
1309 openParams.length = sizeof(openParams);
1310 openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
1311 openParams.autoCommit = autoCommit;
1312 openParams.mask = kCSSM_APPLEDL_MASK_MODE;
1313 openParams.mode = mode;
1314
1315 try {
1316 DbCreate(dbName,
1317 NULL, // DbLocation
1318 *dbInfoP,
1319 CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
1320 NULL, // CredAndAclEntry
1321 &openParams,
1322 dbHand);
1323 }
1324 catch(...) {
1325 MSDebug("Error on DbCreate");
1326 free(dbInfoP->DefaultParsingModules);
1327 free(dbInfoP->RecordAttributeNames);
1328 free(dbInfoP->RecordIndexes);
866f8763 1329 CssmError::throwMeNoLogging(CSSM_ERRCODE_MDS_ERROR);
b1ab9ed8
A
1330 }
1331 free(dbInfoP->DefaultParsingModules);
1332 free(dbInfoP->RecordAttributeNames);
1333 free(dbInfoP->RecordIndexes);
1334
1335}
1336
1337/*
1338 * Create system databases from scratch if they do not already exist.
1339 * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
1340 * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH).
1341 * Returns true if we actually built the files, false if they already
1342 * existed.
1343 */
1344bool MDSSession::createSystemDatabases(
1345 CSSM_BOOL autoCommit,
1346 mode_t mode)
1347{
1348 CSSM_DB_HANDLE objectDbHand = 0;
1349 CSSM_DB_HANDLE directoryDbHand = 0;
1350
1351 assert(geteuid() == (uid_t)0);
1352 if(systemDatabasesPresent(true)) {
1353 /* both databases exist as regular files with correct owner - we're done */
1354 MSDebug("system DBs already exist");
1355 return false;
1356 }
1357
1358 /* create two DBs - any exception here results in deleting both of them */
1359 MSDebug("Creating MDS DBs");
1360 try {
1361 createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1,
1362 autoCommit, mode, objectDbHand);
1363 MSIoDbg("close objectDbHand in createSystemDatabases");
1364 DbClose(objectDbHand);
1365 objectDbHand = 0;
1366 createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations,
1367 autoCommit, mode, directoryDbHand);
1368 MSIoDbg("close directoryDbHand in createSystemDatabases");
1369 DbClose(directoryDbHand);
1370 directoryDbHand = 0;
1371 }
1372 catch (...) {
1373 MSDebug("Error creating MDS DBs - deleting both DB files");
1374 unlink(MDS_OBJECT_DB_PATH);
1375 unlink(MDS_DIRECT_DB_PATH);
1376 throw;
1377 }
1378 return true;
1379}
1380
1381/*
1382 * DbFilesInfo helper class
1383 */
1384
1385/* Note both DB files MUST exist at construction time */
1386MDSSession::DbFilesInfo::DbFilesInfo(
1387 MDSSession &session,
1388 const char *dbPath) :
1389 mSession(session),
1390 mObjDbHand(0),
1391 mDirectDbHand(0),
1392 mLaterTimestamp(0)
1393{
1394 assert(strlen(dbPath) < MAXPATHLEN);
1395 strcpy(mDbPath, dbPath);
1396
1397 /* stat the two DB files, snag the later timestamp */
1398 char path[MAXPATHLEN];
1399 sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1400 struct stat sb;
1401 MSIoDbg("stat %s in DbFilesInfo()", path);
1402 int rtn = ::stat(path, &sb);
1403 if(rtn) {
1404 int error = errno;
1405 MSDebug("Error %d statting DB file %s", error, path);
866f8763 1406 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
1407 }
1408 mLaterTimestamp = sb.st_mtimespec.tv_sec;
1409 sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1410 MSIoDbg("stat %s in DbFilesInfo()", path);
1411 rtn = ::stat(path, &sb);
1412 if(rtn) {
1413 int error = errno;
1414 MSDebug("Error %d statting DB file %s", error, path);
866f8763 1415 UnixError::throwMeNoLogging(error);
b1ab9ed8
A
1416 }
1417 if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1418 mLaterTimestamp = sb.st_mtimespec.tv_sec;
1419 }
1420}
1421
1422MDSSession::DbFilesInfo::~DbFilesInfo()
1423{
1424 if(mObjDbHand != 0) {
1425 /* autocommit on, henceforth */
1426 mSession.PassThrough(mObjDbHand,
1427 CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1428 MSIoDbg("close objectDbHand in ~DbFilesInfo()");
1429 mSession.DbClose(mObjDbHand);
1430 mObjDbHand = 0;
1431 }
1432 if(mDirectDbHand != 0) {
1433 mSession.PassThrough(mDirectDbHand,
1434 CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1435 MSIoDbg("close mDirectDbHand in ~DbFilesInfo()");
1436 mSession.DbClose(mDirectDbHand);
1437 mDirectDbHand = 0;
1438 }
1439}
1440
1441