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#include <syslog.h>
45
46using namespace CssmClient;
47
48/*
49 * The layout of the various MDS DB files on disk is as follows:
50 *
51 * /var/db/mds				-- owner = root, mode = 01777, world writable, sticky
52 *    system/				-- owner = root, mode = 0755
53 *       mdsObject.db		-- owner = root, mode = 0644, object DB
54 *       mdsDirectory.db	-- owner = root, mode = 0644, MDS directory DB
55 *	     mds.lock           -- temporary, owner = root, protects creation of and
56 *							   updates to previous two files
57 *       mds.install.lock	-- owner = root, protects MDS_Install operation
58 *    <uid>/				-- owner = <uid>, mode = 0700
59 *     	 mdsObject.db		-- owner = <uid>, mode = 000, object DB
60 *       mdsDirectory.db	-- owner = <uid>, mode = 000, MDS directory DB
61 *	     mds.lock			-- owner = <uid>, protects updates of previous two files
62 *
63 * The /var/db/mds/system directory is created at OS install time. The DB files in
64 * it are created by root at MDS_Install time. The ownership and mode of this directory and
65 * of its parent is also re-checked and forced to the correct state at MDS_Install time.
66 * Each user has their own private directory with two DB files and a lock. The first time
67 * a user accesses MDS, the per-user directory is created and the per-user DB files are
68 * created as copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system
69 * DB files when they are the source of these copies; this is the same mechanism
70 * used by the underlying AtomicFile.
71 *
72 * The sticky bit in /var/db/mds ensures that users cannot modify other userss private
73 * MDS directories.
74 */
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 */
129#define MSIoDbg(args...)		secdebug("MDS_IO", ## args)
130
131/* Trace cleanDir() */
132#define MSCleanDirDbg(args...)	secdebug("MDS_CleanDir", ## args)
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];
145		size_t result = confstr(_CS_DARWIN_USER_CACHE_DIR, strBuffer, sizeof(strBuffer));
146		if (result == 0)
147		{
148			// we have an error, log it
149			syslog(LOG_CRIT, "confstr on _CS_DARWIN_USER_CACHE_DIR returned an error.");
150			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
151		}
152
153		retValue = strBuffer;
154	}
155
156	return retValue;
157}
158
159
160
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. */
329			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
330		}
331		return false;
332	}
333
334	/* it's there...how does it look? */
335	mode_t fileType = sb.st_mode & S_IFMT;
336	if((fileType == S_IFREG) && (sb.st_uid == forUid)) {
337		return true;
338	}
339	if(!purge) {
340		return false;
341	}
342
343	/* not what we want: get rid of it. */
344	if(fileType == S_IFDIR) {
345		/* directory: clean then remove */
346		if(cleanDir(filePath, NULL, 0)) {
347			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
348		}
349		if(rmdir(filePath)) {
350			MSDebug("rmdir(%s) returned %d", filePath, errno);
351			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
352		}
353	}
354	else {
355		if(unlink(filePath)) {
356			MSDebug("unlink(%s) returned %d", filePath, errno);
357			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
358		}
359	}
360
361	/* caller should be somewhat happy */
362	return false;
363}
364
365/*
366 * Determine if both of the specified DB files exist as accessible regular files with specified
367 * owner. Returns true if they do.
368 *
369 * If the purge argument is true, we'll ensure that either both files exist with
370 * the right owner, or neither of the files exist on exit. An error on that operation
371 * throws a CSSM_ERRCODE_MDS_ERROR CssmError exception (i.e., we're hosed).
372 * Returns the stat info for both files on success for further processing by caller.
373 */
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);
400			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
401		}
402	}
403	if(directExist) {
404		if(unlink(directDbFile)) {
405			MSDebug("unlink(%s) returned %d", directDbFile, errno);
406			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
407		}
408	}
409	return false;
410}
411
412/*
413 * Determine if specified directory exists with specified owner and mode.
414 * Returns true if copacetic, else returns false and also indicates
415 * via the directStatus out param what went wrong.
416 */
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) {
583		CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
584	}
585
586	//
587	// install() is only (legitimately) called from securityd.
588	// Mark "server mode" so we don't end up waiting for ourselves when the databases open.
589	//
590	mModule.setServerMode();
591
592	try {
593		/* ensure MDS base directory exists with correct permissions */
594		if(createDir(MDS_BASE_DB_DIR, MDS_SYSTEM_UID, MDS_BASE_DB_DIR_MODE)) {
595			MSDebug("Error creating base MDS dir; aborting.");
596			CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
597		}
598
599		/* ensure the the system MDS DB directory exists with correct permissions */
600		if(createDir(MDS_SYSTEM_DB_DIR, MDS_SYSTEM_UID, MDS_SYSTEM_DB_DIR_MODE)) {
601			MSDebug("Error creating system MDS dir; aborting.");
602			CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED);
603		}
604
605        LockHelper lh;
606
607		if(!lh.obtainLock(MDS_INSTALL_LOCK_PATH, DB_LOCK_TIMEOUT)) {
608			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
609		}
610
611		/*
612		 * We own the whole MDS system. Clean everything out except for our lock
613		 * (and the directory it's in :-)
614		 */
615
616		const char *savedFile = MDS_INSTALL_LOCK_NAME;
617		if(cleanDir(MDS_SYSTEM_DB_DIR, &savedFile, 1)) {
618			/* this should never happen - we're root */
619			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
620		}
621
622		const char *savedFiles[] = {MDS_SYSTEM_DB_COMP, kExceptionDeletePath};
623		if(cleanDir(MDS_BASE_DB_DIR, savedFiles, 2)) {
624			/* this should never happen - we're root */
625			CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
626		}
627
628		/*
629		 * Do initial population of system DBs.
630		 */
631		createSystemDatabases(CSSM_FALSE, MDS_SYSTEM_DB_MODE);
632		DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
633		dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH);
634	}
635	catch(...) {
636		throw;
637	}
638}
639
640//
641// In this implementation, the uninstall() call is not supported since
642// we do not allow programmatic deletion of the MDS databases.
643//
644
645void
646MDSSession::uninstall ()
647{
648	CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED);
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) {
701		CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
702	}
703	const char *dbName;
704	if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) {
705		dbName = MDS_OBJECT_DB_NAME;
706	}
707	else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) {
708		dbName = MDS_DIRECT_DB_NAME;
709	}
710	else {
711		CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME);
712	}
713	char fullPath[MAXPATHLEN];
714	dbFullPath(dbName, fullPath);
715	MSIoDbg("open %s in dbOpen(name, loc, accessReq...)", dbName);
716	DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred,
717		OpenParameters, DbHandle);
718}
719
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");
753	CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
754}
755
756//
757// Attempt to obtain an exclusive lock over the the MDS databases. The
758// parameter is the maximum amount of time, in milliseconds, to spend
759// trying to obtain the lock. A value of zero means to return failure
760// right away if the lock cannot be obtained.
761//
762bool
763MDSSession::LockHelper::obtainLock(
764	const char *lockFile,	// e.g. MDS_INSTALL_LOCK_PATH
765	int timeout)			// default 0
766{
767	mFD = -1;
768	for(;;) {
769		secdebug("mdslock", "obtainLock: calling open(%s)", lockFile);
770		mFD = open(lockFile, O_EXLOCK | O_CREAT | O_RDWR, 0644);
771		if(mFD == -1) {
772			int err = errno;
773			secdebug("mdslock", "obtainLock: open error %d", errno);
774			if(err == EINTR) {
775				/* got a signal, go again */
776				continue;
777			}
778			else {
779				/* theoretically should never happen */
780				return false;
781			}
782		}
783		else {
784			secdebug("mdslock", "obtainLock: success");
785			return true;
786		}
787	}
788
789	/* not reached */
790	return false;
791}
792
793//
794// Release the exclusive lock over the MDS databases. If this session
795// does not hold the lock, this method does nothing.
796//
797
798MDSSession::LockHelper::~LockHelper()
799{
800	secdebug("mdslock", "releaseLock");
801    if (mFD == -1)
802    {
803        return;
804    }
805
806	flock(mFD, LOCK_UN);
807	close(mFD);
808	mFD = -1;
809}
810
811/* given DB file name, fill in fully specified path */
812void MDSSession::dbFullPath(
813	const char *dbName,
814	char fullPath[MAXPATHLEN+1])
815{
816	mModule.getDbPath(fullPath);
817	assert(fullPath[0] != '\0');
818	strcat(fullPath, "/");
819	strcat(fullPath, dbName);
820}
821
822/*
823 * See if any per-user bundles exist in specified directory. Returns true if so.
824 * First the check for one entry....
825 */
826static bool isBundle(
827	const struct dirent *dp)
828{
829	if(dp == NULL) {
830		return false;
831	}
832	/* NFS directories show up as DT_UNKNOWN */
833	switch(dp->d_type) {
834		case DT_UNKNOWN:
835		case DT_DIR:
836			break;
837		default:
838			return false;
839	}
840	int suffixLen = strlen(MDS_BUNDLE_EXTEN);
841	size_t len = strlen(dp->d_name);
842
843	return (len >= suffixLen) &&
844	       !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN);
845}
846
847/* now the full directory search */
848static bool checkUserBundles(
849	const char *bundlePath)
850{
851	MSDebug("searching for user bundles in %s", bundlePath);
852	DIR *dir = opendir(bundlePath);
853	if (dir == NULL) {
854		return false;
855	}
856	struct dirent *dp;
857	bool rtn = false;
858	while ((dp = readdir(dir)) != NULL) {
859		if(isBundle(dp)) {
860			/* any other checking to do? */
861			rtn = true;
862			break;
863		}
864	}
865	closedir(dir);
866	MSDebug("...%s bundle(s) found", rtn ? "" : "No");
867	return rtn;
868}
869
870#define COPY_BUF_SIZE	1024
871
872/*
873 * Single file copy with locking.
874 * Ensures that the source is a regular file with specified owner.
875 * Caller specifies mode of destination file.
876 * Throws a CssmError if the source file doesn't meet spec; throws a
877 *    UnixError on any other error (which is generally recoverable by
878 *    having the user MDS session use the system DB files).
879 */
880static void safeCopyFile(
881	const char *fromPath,
882	uid_t fromUid,
883	const char *toPath,
884	mode_t toMode)
885{
886	int error = 0;
887	bool haveLock = false;
888	int destFd = 0;
889	int srcFd = 0;
890	struct stat sb;
891	char tmpToPath[MAXPATHLEN+1];
892
893	MSIoDbg("open %s, %s in safeCopyFile", fromPath, toPath);
894
895	if(!doesFileExist(fromPath, fromUid, false, sb)) {
896		MSDebug("safeCopyFile: bad system DB file %s", fromPath);
897		CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
898	}
899
900	/* create temp destination */
901	snprintf(tmpToPath, sizeof(tmpToPath), "%s_", toPath);
902	destFd = open(tmpToPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, toMode);
903	if(destFd < 0) {
904		error = errno;
905		MSDebug("Error %d opening user DB file %s\n", error, tmpToPath);
906		UnixError::throwMe(error);
907	}
908
909	struct flock fl;
910	try {
911		/* don't get tripped up by umask */
912		if(fchmod(destFd, toMode)) {
913			error = errno;
914			MSDebug("Error %d chmoding user DB file %s\n", error, tmpToPath);
915			UnixError::throwMe(error);
916		}
917
918		/* open source for reading */
919		srcFd = open(fromPath, O_RDONLY, 0);
920		if(srcFd < 0) {
921			error = errno;
922			MSDebug("Error %d opening system DB file %s\n", error, fromPath);
923			UnixError::throwMe(error);
924		}
925
926		/* acquire the same kind of lock AtomicFile uses */
927		fl.l_start = 0;
928		fl.l_len = 1;
929		fl.l_pid = getpid();
930		fl.l_type = F_RDLCK;		// AtomicFile gets F_WRLCK
931		fl.l_whence = SEEK_SET;
932
933		// Keep trying to obtain the lock if we get interupted.
934		for (;;) {
935			if (::fcntl(srcFd, F_SETLKW, &fl) == -1) {
936				error = errno;
937				if (error == EINTR) {
938					error = 0;
939					continue;
940				}
941				MSDebug("Error %d locking system DB file %s\n", error, fromPath);
942				UnixError::throwMe(error);
943			}
944			else {
945				break;
946				haveLock = true;
947			}
948		}
949
950		/* copy */
951		char buf[COPY_BUF_SIZE];
952		while(1) {
953			ssize_t bytesRead = read(srcFd, buf, COPY_BUF_SIZE);
954			if(bytesRead == 0) {
955				break;
956			}
957			if(bytesRead < 0) {
958				error = errno;
959				MSDebug("Error %d reading system DB file %s\n", error, fromPath);
960				UnixError::throwMe(error);
961			}
962			ssize_t bytesWritten = write(destFd, buf, bytesRead);
963			if(bytesWritten < 0) {
964				error = errno;
965				MSDebug("Error %d writing user DB file %s\n", error, tmpToPath);
966				UnixError::throwMe(error);
967			}
968		}
969	}
970	catch(...) {
971		/* error is nonzero, we'll re-throw below...still have some cleanup */
972	}
973
974	/* unlock source and close both */
975	if(haveLock) {
976		fl.l_type = F_UNLCK;
977		if (::fcntl(srcFd, F_SETLK, &fl) == -1) {
978			MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath);
979		}
980	}
981	MSIoDbg("close %s, %s in safeCopyFile", fromPath, tmpToPath);
982	if(srcFd) {
983		close(srcFd);
984	}
985	if(destFd) {
986		close(destFd);
987	}
988	if(error == 0) {
989		/* commit temp file */
990		if(::rename(tmpToPath, toPath)) {
991			error = errno;
992			MSDebug("Error %d committing %s\n", error, toPath);
993		}
994	}
995	if(error) {
996		UnixError::throwMe(error);
997	}
998}
999
1000/*
1001 * Copy system DB files to specified user dir. Caller holds user DB lock.
1002 * Throws a UnixError on error.
1003 */
1004static void copySystemDbs(
1005	const char *userDbFileDir)
1006{
1007	char toPath[MAXPATHLEN+1];
1008
1009	snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME);
1010	safeCopyFile(MDS_OBJECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1011	snprintf(toPath, sizeof(toPath), "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME);
1012	safeCopyFile(MDS_DIRECT_DB_PATH, MDS_SYSTEM_UID, toPath, MDS_USER_DB_MODE);
1013}
1014
1015/*
1016 * Ensure current DB files exist and are up-to-date.
1017 * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any
1018 * other public functions which access a DB from scratch.
1019 */
1020void MDSSession::updateDataBases()
1021{
1022	RecursionBlock::Once once(mUpdating);
1023	if (once())
1024		return;	// already updating; don't recurse
1025
1026	uid_t ourUid = geteuid();
1027	bool isRoot = (ourUid == 0);
1028
1029	/* if we scanned recently, we're done */
1030	double delta = mModule.timeSinceLastScan();
1031	if(delta < (double)MDS_SCAN_INTERVAL) {
1032		return;
1033	}
1034
1035	/*
1036	 * If we're root, the first thing we do is to ensure that system DBs are present.
1037	 * Note that this is a necessary artifact of the problem behind Radar 3800811.
1038	 * When that is fixed, install() should ONLY be called from the public MDS_Install()
1039	 * routine.
1040	 * Anyway, if we *do* have to install here, we're done.
1041	 */
1042	if(isRoot && !systemDatabasesPresent(false)) {
1043		install();
1044		mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1045		mModule.lastScanIsNow();
1046		return;
1047	}
1048
1049	/*
1050	 * Obtain various per-user paths. Root is a special case but follows most
1051	 * of the same logic from here on.
1052	 */
1053	std::string userDBFileDir = GetMDSDBDir();
1054	std::string userObjDBFilePath = GetMDSObjectDBPath();
1055	std::string userDirectDBFilePath = GetMDSDirectDBPath();
1056	char userBundlePath[MAXPATHLEN+1];
1057	std::string userDbLockPath = GetMDSDBLockPath();
1058
1059	/* this means "no user bundles" */
1060	userBundlePath[0] = '\0';
1061	if(!isRoot) {
1062		char *userHome = getenv("HOME");
1063		if((userHome == NULL) ||
1064		   (strlen(userHome) + strlen(MDS_USER_BUNDLE) + 2) > sizeof(userBundlePath)) {
1065			/* Can't check for user bundles */
1066			MSDebug("missing or invalid HOME; skipping user bundle check");
1067		}
1068		/* TBD: any other checking of userHome? */
1069		else {
1070			snprintf(userBundlePath, sizeof(userBundlePath),
1071				"%s/%s", userHome, MDS_USER_BUNDLE);
1072		}
1073	}
1074
1075	/*
1076	 * Create the per-user directory...that's where the lock we'll be using lives.
1077	 */
1078	if(!isRoot) {
1079		if(createDir(userDBFileDir.c_str(), ourUid, MDS_USER_DB_DIR_MODE)) {
1080			/*
1081			 * We'll just have to limp along using the read-only system DBs.
1082			 * Note that this protects (somewhat) against the DoS attack in
1083			 * Radar 3801292. The only problem is that this user won't be able
1084			 * to use per-user bundles.
1085			 */
1086			MSDebug("Error creating user DBs; using system DBs");
1087			mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1088			return;
1089		}
1090	}
1091
1092	/* always release userLockFd no matter what happens */
1093    LockHelper lh;
1094
1095	if(!lh.obtainLock(userDbLockPath.c_str(), DB_LOCK_TIMEOUT)) {
1096		CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR);
1097	}
1098	try {
1099		if(!isRoot) {
1100			try {
1101				/*
1102				 * We copy the system DBs to the per-user DBs in two cases:
1103				 * -- user DBs don't exist, or
1104				 * -- system DBs have changed since the the last update to the user DBs.
1105				 *    This happens on smart card insertion and removal.
1106				 */
1107				bool doCopySystem = false;
1108				struct stat userObjStat, userDirectStat;
1109				if(!doFilesExist(userObjDBFilePath.c_str(), userDirectDBFilePath.c_str(), ourUid, true,
1110						userObjStat, userDirectStat)) {
1111					doCopySystem = true;
1112				}
1113				else {
1114					/* compare the two mdsDirectory.db files */
1115					MSIoDbg("stat %s, %s in updateDataBases",
1116						MDS_DIRECT_DB_PATH, userDirectDBFilePath.c_str());
1117					struct stat sysStat;
1118					if (!stat(MDS_DIRECT_DB_PATH, &sysStat)) {
1119						doCopySystem = (sysStat.st_mtimespec.tv_sec > userDirectStat.st_mtimespec.tv_sec) ||
1120							((sysStat.st_mtimespec.tv_sec == userDirectStat.st_mtimespec.tv_sec) &&
1121								(sysStat.st_mtimespec.tv_nsec > userDirectStat.st_mtimespec.tv_nsec));
1122						if(doCopySystem) {
1123							MSDebug("user DB files obsolete at %s", userDBFileDir.c_str());
1124						}
1125					}
1126				}
1127				if(doCopySystem) {
1128					/* copy system DBs to user DBs */
1129					MSDebug("copying system DBs to user at %s", userDBFileDir.c_str());
1130					copySystemDbs(userDBFileDir.c_str());
1131				}
1132				else {
1133					MSDebug("Using existing user DBs at %s", userDBFileDir.c_str());
1134				}
1135			}
1136			catch(const CssmError &cerror) {
1137				/*
1138				 * Bad system DB file detected. Fatal.
1139				 */
1140				throw;
1141			}
1142			catch(...) {
1143				/*
1144				 * Error on delete or create user DBs; fall back on system DBs.
1145				 */
1146				MSDebug("doFilesExist(purge) error; using system DBs");
1147				mModule.setDbPath(MDS_SYSTEM_DB_DIR);
1148				return;
1149			}
1150		}
1151		else {
1152			MSDebug("Using system DBs only");
1153		}
1154
1155		/*
1156		 * Update per-user DBs from both bundle sources (System bundles, user bundles)
1157		 * as appropriate.
1158		 */
1159		DbFilesInfo dbFiles(*this, userDBFileDir.c_str());
1160		dbFiles.removeOutdatedPlugins();
1161		dbFiles.updateSystemDbInfo(NULL, MDS_BUNDLE_PATH);
1162		if(userBundlePath[0]) {
1163			/* skip for invalid or missing $HOME... */
1164			if(checkUserBundles(userBundlePath)) {
1165				dbFiles.updateForBundleDir(userBundlePath);
1166			}
1167		}
1168		mModule.setDbPath(userDBFileDir.c_str());
1169	}	/* main block protected by mLockFd */
1170	catch(...) {
1171		throw;
1172	}
1173	mModule.lastScanIsNow();
1174}
1175
1176/*
1177 * Remove all records with specified guid (a.k.a. ModuleID) from specified DB.
1178 */
1179void MDSSession::removeRecordsForGuid(
1180	const char *guid,
1181	CSSM_DB_HANDLE dbHand)
1182{
1183	// tell the DB to flush its intermediate data to disk
1184	PassThrough(dbHand, CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1185	CssmClient::Query query = Attribute("ModuleID") == guid;
1186	clearRecords(dbHand, query.cssmQuery());
1187}
1188
1189
1190void MDSSession::clearRecords(CSSM_DB_HANDLE dbHand, const CssmQuery &query)
1191{
1192	CSSM_DB_UNIQUE_RECORD_PTR record = NULL;
1193	CSSM_HANDLE resultHand = DataGetFirst(dbHand,
1194		&query,
1195		NULL,
1196		NULL,			// No data
1197		record);
1198	if (resultHand == CSSM_INVALID_HANDLE)
1199		return; // no matches
1200	try {
1201		do {
1202			DataDelete(dbHand, *record);
1203			FreeUniqueRecord(dbHand, *record);
1204			record = NULL;
1205		} while (DataGetNext(dbHand,
1206			resultHand,
1207			NULL,
1208			NULL,
1209			record));
1210	} catch (...) {
1211		if (record)
1212			FreeUniqueRecord(dbHand, *record);
1213		DataAbortQuery(dbHand, resultHand);
1214	}
1215}
1216
1217
1218/*
1219 * Determine if system databases are present.
1220 * If the purge argument is true, we'll ensure that either both or neither
1221 * DB files exist on exit; in that case caller must be holding MDS_INSTALL_LOCK_PATH.
1222 */
1223bool MDSSession::systemDatabasesPresent(bool purge)
1224{
1225	bool rtn = false;
1226
1227	try {
1228		/*
1229		 * This can throw on a failed attempt to delete sole existing file....
1230		 * But if that happens while we're root, our goose is fully cooked.
1231		 */
1232		struct stat objDbSb, directDbSb;
1233		if(doFilesExist(MDS_OBJECT_DB_PATH, MDS_DIRECT_DB_PATH,
1234				MDS_SYSTEM_UID, purge, objDbSb, directDbSb)) {
1235			rtn = true;
1236		}
1237	}
1238	catch(...) {
1239
1240	}
1241	return rtn;
1242}
1243
1244/*
1245 * Given a DB name (which is used as an absolute path) and an array of
1246 * RelationInfos, create a DB.
1247 */
1248void
1249MDSSession::createSystemDatabase(
1250	const char *dbName,
1251	const RelationInfo *relationInfo,
1252	unsigned numRelations,
1253	CSSM_BOOL autoCommit,
1254	mode_t mode,
1255	CSSM_DB_HANDLE &dbHand)			// RETURNED
1256{
1257	CSSM_DBINFO dbInfo;
1258	CSSM_DBINFO_PTR dbInfoP = &dbInfo;
1259
1260	memset(dbInfoP, 0, sizeof(CSSM_DBINFO));
1261	dbInfoP->NumberOfRecordTypes = numRelations;
1262	dbInfoP->IsLocal = CSSM_TRUE;		// TBD - what does this mean?
1263	dbInfoP->AccessPath = NULL;		// TBD
1264
1265	/* alloc numRelations elements for parsingModule, recordAttr, and recordIndex
1266	 * info arrays */
1267	unsigned size = sizeof(CSSM_DB_PARSING_MODULE_INFO) * numRelations;
1268	dbInfoP->DefaultParsingModules = (CSSM_DB_PARSING_MODULE_INFO_PTR)malloc(size);
1269	memset(dbInfoP->DefaultParsingModules, 0, size);
1270	size = sizeof(CSSM_DB_RECORD_ATTRIBUTE_INFO) * numRelations;
1271	dbInfoP->RecordAttributeNames = (CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR)malloc(size);
1272	memset(dbInfoP->RecordAttributeNames, 0, size);
1273	size = sizeof(CSSM_DB_RECORD_INDEX_INFO) * numRelations;
1274	dbInfoP->RecordIndexes = (CSSM_DB_RECORD_INDEX_INFO_PTR)malloc(size);
1275	memset(dbInfoP->RecordIndexes, 0, size);
1276
1277	/* cook up attribute and index info for each relation */
1278	unsigned relation;
1279	for(relation=0; relation<numRelations; relation++) {
1280		const struct RelationInfo *relp = &relationInfo[relation];	// source
1281		CSSM_DB_RECORD_ATTRIBUTE_INFO_PTR attrInfo =
1282			&dbInfoP->RecordAttributeNames[relation];					// dest 1
1283		CSSM_DB_RECORD_INDEX_INFO_PTR indexInfo =
1284			&dbInfoP->RecordIndexes[relation];						// dest 2
1285
1286		attrInfo->DataRecordType = relp->DataRecordType;
1287		attrInfo->NumberOfAttributes = relp->NumberOfAttributes;
1288		attrInfo->AttributeInfo = (CSSM_DB_ATTRIBUTE_INFO_PTR)relp->AttributeInfo;
1289
1290		indexInfo->DataRecordType = relp->DataRecordType;
1291		indexInfo->NumberOfIndexes = relp->NumberOfIndexes;
1292		indexInfo->IndexInfo = (CSSM_DB_INDEX_INFO_PTR)relp->IndexInfo;
1293	}
1294
1295	/* set autocommit and mode */
1296	CSSM_APPLEDL_OPEN_PARAMETERS openParams;
1297	memset(&openParams, 0, sizeof(openParams));
1298	openParams.length = sizeof(openParams);
1299	openParams.version = CSSM_APPLEDL_OPEN_PARAMETERS_VERSION;
1300	openParams.autoCommit = autoCommit;
1301	openParams.mask = kCSSM_APPLEDL_MASK_MODE;
1302	openParams.mode = mode;
1303
1304	try {
1305		DbCreate(dbName,
1306			NULL,			// DbLocation
1307			*dbInfoP,
1308			CSSM_DB_ACCESS_READ | CSSM_DB_ACCESS_WRITE,
1309			NULL,			// CredAndAclEntry
1310			&openParams,
1311			dbHand);
1312	}
1313	catch(...) {
1314		MSDebug("Error on DbCreate");
1315		free(dbInfoP->DefaultParsingModules);
1316		free(dbInfoP->RecordAttributeNames);
1317		free(dbInfoP->RecordIndexes);
1318		throw;
1319	}
1320	free(dbInfoP->DefaultParsingModules);
1321	free(dbInfoP->RecordAttributeNames);
1322	free(dbInfoP->RecordIndexes);
1323
1324}
1325
1326/*
1327 * Create system databases from scratch if they do not already exist.
1328 * MDS_INSTALL_LOCK_PATH held on entry and exit. MDS_SYSTEM_DB_DIR assumed to
1329 * exist (that's our caller's job, before acquiring MDS_INSTALL_LOCK_PATH).
1330 * Returns true if we actually built the files, false if they already
1331 * existed.
1332 */
1333bool MDSSession::createSystemDatabases(
1334	CSSM_BOOL autoCommit,
1335	mode_t mode)
1336{
1337	CSSM_DB_HANDLE objectDbHand = 0;
1338	CSSM_DB_HANDLE directoryDbHand = 0;
1339
1340	assert(geteuid() == (uid_t)0);
1341	if(systemDatabasesPresent(true)) {
1342		/* both databases exist as regular files with correct owner - we're done */
1343		MSDebug("system DBs already exist");
1344		return false;
1345	}
1346
1347	/* create two DBs - any exception here results in deleting both of them */
1348	MSDebug("Creating MDS DBs");
1349	try {
1350		createSystemDatabase(MDS_OBJECT_DB_PATH, &kObjectRelation, 1,
1351			autoCommit, mode, objectDbHand);
1352		MSIoDbg("close objectDbHand in createSystemDatabases");
1353		DbClose(objectDbHand);
1354		objectDbHand = 0;
1355		createSystemDatabase(MDS_DIRECT_DB_PATH, kMDSRelationInfo, kNumMdsRelations,
1356			autoCommit, mode, directoryDbHand);
1357		MSIoDbg("close directoryDbHand in createSystemDatabases");
1358		DbClose(directoryDbHand);
1359		directoryDbHand = 0;
1360	}
1361	catch (...) {
1362		MSDebug("Error creating MDS DBs - deleting both DB files");
1363		unlink(MDS_OBJECT_DB_PATH);
1364		unlink(MDS_DIRECT_DB_PATH);
1365		throw;
1366	}
1367	return true;
1368}
1369
1370/*
1371 * DbFilesInfo helper class
1372 */
1373
1374/* Note both DB files MUST exist at construction time */
1375MDSSession::DbFilesInfo::DbFilesInfo(
1376	MDSSession &session,
1377	const char *dbPath) :
1378		mSession(session),
1379		mObjDbHand(0),
1380		mDirectDbHand(0),
1381		mLaterTimestamp(0)
1382{
1383	assert(strlen(dbPath) < MAXPATHLEN);
1384	strcpy(mDbPath, dbPath);
1385
1386	/* stat the two DB files, snag the later timestamp */
1387	char path[MAXPATHLEN];
1388	sprintf(path, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1389	struct stat sb;
1390	MSIoDbg("stat %s in DbFilesInfo()", path);
1391	int rtn = ::stat(path, &sb);
1392	if(rtn) {
1393		int error = errno;
1394		MSDebug("Error %d statting DB file %s", error, path);
1395		UnixError::throwMe(error);
1396	}
1397	mLaterTimestamp = sb.st_mtimespec.tv_sec;
1398	sprintf(path, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1399	MSIoDbg("stat %s in DbFilesInfo()", path);
1400	rtn = ::stat(path, &sb);
1401	if(rtn) {
1402		int error = errno;
1403		MSDebug("Error %d statting DB file %s", error, path);
1404		UnixError::throwMe(error);
1405	}
1406	if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1407		mLaterTimestamp = sb.st_mtimespec.tv_sec;
1408	}
1409}
1410
1411MDSSession::DbFilesInfo::~DbFilesInfo()
1412{
1413	if(mObjDbHand != 0) {
1414		/* autocommit on, henceforth */
1415		mSession.PassThrough(mObjDbHand,
1416			CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1417		MSIoDbg("close objectDbHand in ~DbFilesInfo()");
1418		mSession.DbClose(mObjDbHand);
1419		mObjDbHand = 0;
1420	}
1421	if(mDirectDbHand != 0) {
1422		mSession.PassThrough(mDirectDbHand,
1423			CSSM_APPLEFILEDL_COMMIT, NULL, NULL);
1424		MSIoDbg("close mDirectDbHand in ~DbFilesInfo()");
1425		mSession.DbClose(mDirectDbHand);
1426		mDirectDbHand = 0;
1427	}
1428}
1429
1430/* lazy evaluation of both DB handles�*/
1431CSSM_DB_HANDLE MDSSession::DbFilesInfo::objDbHand()
1432{
1433	if(mObjDbHand != 0) {
1434		return mObjDbHand;
1435	}
1436	char fullPath[MAXPATHLEN + 1];
1437	sprintf(fullPath, "%s/%s", mDbPath, MDS_OBJECT_DB_NAME);
1438	MSIoDbg("open %s in objDbHand()", fullPath);
1439	mObjDbHand = mSession.dbOpen(fullPath, true);	// batch mode
1440	return mObjDbHand;
1441}
1442
1443CSSM_DB_HANDLE MDSSession::DbFilesInfo::directDbHand()
1444{
1445	if(mDirectDbHand != 0) {
1446		return mDirectDbHand;
1447	}
1448	char fullPath[MAXPATHLEN + 1];
1449	sprintf(fullPath, "%s/%s", mDbPath, MDS_DIRECT_DB_NAME);
1450	MSIoDbg("open %s in directDbHand()", fullPath);
1451	mDirectDbHand = mSession.dbOpen(fullPath, true);	// batch mode
1452	return mDirectDbHand;
1453}
1454
1455/*
1456 * Update the info for Security.framework and the system bundles.
1457 */
1458void MDSSession::DbFilesInfo::updateSystemDbInfo(
1459	const char *systemPath,		// e.g., /System/Library/Frameworks
1460	const char *bundlePath)		// e.g., /System/Library/Security
1461{
1462	/*
1463	 * Security.framework - CSSM and built-in modules - only for initial population of
1464	 * system DB files.
1465	 */
1466	if (systemPath) {
1467		string path;
1468		if (CFRef<CFBundleRef> me = CFBundleGetBundleWithIdentifier(CFSTR("com.apple.security")))
1469			if (CFRef<CFURLRef> url = CFBundleCopyBundleURL(me))
1470				if (CFRef<CFStringRef> cfpath = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle))
1471					path = cfString(cfpath);	// path to my bundle
1472
1473		if (path.empty())   // use system default
1474			path = string(systemPath) + "/" MDS_SYSTEM_FRAME;
1475		updateForBundle(path.c_str());
1476	}
1477
1478	/* Standard loadable bundles */
1479	updateForBundleDir(bundlePath);
1480}
1481
1482
1483MDSSession::DbFilesInfo::TbdRecord::TbdRecord(
1484	const CSSM_DATA &guid)
1485{
1486	assert(guid.Length <= MAX_GUID_LEN);
1487	assert(guid.Length != 0);
1488	memmove(mGuid, guid.Data, guid.Length);
1489	if(mGuid[guid.Length - 1] != '\0') {
1490		mGuid[guid.Length] = '\0';
1491	}
1492}
1493
1494/*
1495 * Test if plugin specified by pluginPath needs to be deleted from DBs.
1496 * If so, add to tbdVector.
1497 */
1498void MDSSession::DbFilesInfo::checkOutdatedPlugin(
1499	const CSSM_DATA &pathValue,
1500	const CSSM_DATA &guidValue,
1501	TbdVector &tbdVector)
1502{
1503	/* stat the specified plugin */
1504	struct stat sb;
1505	bool obsolete = false;
1506	string path = CssmData::overlay(pathValue).toString();
1507	if (!path.empty() && path[0] == '*') {
1508		/* builtin pseudo-path; never obsolete this */
1509		return;
1510	}
1511	MSIoDbg("stat %s in checkOutdatedPlugin()", path.c_str());
1512	int rtn = ::stat(path.c_str(), &sb);
1513	if(rtn) {
1514		/* not there or inaccessible; delete */
1515		obsolete = true;
1516	}
1517	else if(sb.st_mtimespec.tv_sec > mLaterTimestamp) {
1518		/* timestamp of plugin's main directory later than that of DBs */
1519		obsolete = true;
1520	}
1521	if(obsolete) {
1522		TbdRecord *tbdRecord = new TbdRecord(guidValue);
1523		tbdVector.push_back(tbdRecord);
1524		MSDebug("checkOutdatedPlugin: flagging %s obsolete", path.c_str());
1525	}
1526}
1527
1528/*
1529 * Examine dbFiles.objDbHand; remove all fields associated with any bundle
1530 * i.e., with any path) which are either not present on disk, or which
1531 * have changed since dbFiles.laterTimestamp().
1532 */
1533void MDSSession::DbFilesInfo::removeOutdatedPlugins()
1534{
1535	CSSM_QUERY						query;
1536	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
1537	CSSM_HANDLE						resultHand;
1538	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
1539	CSSM_DB_ATTRIBUTE_DATA			theAttrs[2];
1540	CSSM_DB_ATTRIBUTE_INFO_PTR		attrInfo;
1541	TbdVector						tbdRecords;
1542
1543	/*
1544	 * First, scan object directory. All we need are the path and GUID attributes.
1545	 */
1546	recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1547	recordAttrs.SemanticInformation = 0;
1548	recordAttrs.NumberOfAttributes = 2;
1549	recordAttrs.AttributeData = theAttrs;
1550
1551	attrInfo = &theAttrs[0].Info;
1552	attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1553	attrInfo->Label.AttributeName = (char*) "ModuleID";
1554	attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1555	theAttrs[0].NumberOfValues = 0;
1556	theAttrs[0].Value = NULL;
1557	attrInfo = &theAttrs[1].Info;
1558	attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1559	attrInfo->Label.AttributeName = (char*) "Path";
1560	attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1561	theAttrs[1].NumberOfValues = 0;
1562	theAttrs[1].Value = NULL;
1563
1564	/* just search by recordType, no predicates */
1565	query.RecordType = MDS_OBJECT_RECORDTYPE;
1566	query.Conjunctive = CSSM_DB_NONE;
1567	query.NumSelectionPredicates = 0;
1568	query.SelectionPredicate = NULL;
1569	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
1570	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
1571	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?
1572
1573	CssmQuery perryQuery(query);
1574	try {
1575		resultHand = mSession.DataGetFirst(objDbHand(),
1576			&perryQuery,
1577			&recordAttrs,
1578			NULL,			// No data
1579			record);
1580	}
1581	catch(...) {
1582		MSDebug("removeOutdatedPlugins: DataGetFirst threw");
1583		return;		// ???
1584	}
1585	if(record) {
1586		mSession.FreeUniqueRecord(mObjDbHand, *record);
1587	}
1588	if(resultHand) {
1589		if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1590			checkOutdatedPlugin(*theAttrs[1].Value, *theAttrs[0].Value,
1591				tbdRecords);
1592		}
1593		else {
1594			MSDebug("removeOutdatedPlugins: incomplete record found (1)!");
1595		}
1596		for(unsigned dex=0; dex<2; dex++) {
1597			CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1598			for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1599				if(attr->Value[attrDex].Data) {
1600					mSession.free(attr->Value[attrDex].Data);
1601				}
1602			}
1603			if(attr->Value) {
1604				mSession.free(attr->Value);
1605			}
1606		}
1607	}
1608	else {
1609		/* empty Object DB - we're done */
1610		MSDebug("removeOutdatedPlugins: empty object DB");
1611		return;
1612	}
1613
1614	/* now the rest of the object DB records */
1615	for(;;) {
1616		bool brtn = mSession.DataGetNext(objDbHand(),
1617			resultHand,
1618			&recordAttrs,
1619			NULL,
1620			record);
1621		if(!brtn) {
1622			/* end of data */
1623			break;
1624		}
1625		if(record) {
1626			mSession.FreeUniqueRecord(mObjDbHand, *record);
1627		}
1628		if(theAttrs[0].NumberOfValues && theAttrs[1].NumberOfValues) {
1629			checkOutdatedPlugin(*theAttrs[1].Value,
1630				*theAttrs[0].Value,
1631				tbdRecords);
1632		}
1633		else {
1634			MSDebug("removeOutdatedPlugins: incomplete record found (2)!");
1635		}
1636		for(unsigned dex=0; dex<2; dex++) {
1637			CSSM_DB_ATTRIBUTE_DATA *attr = &theAttrs[dex];
1638			for (unsigned attrDex=0; attrDex<attr->NumberOfValues; attrDex++) {
1639				if(attr->Value[attrDex].Data) {
1640					mSession.free(attr->Value[attrDex].Data);
1641				}
1642			}
1643			if(attr->Value) {
1644				mSession.free(attr->Value);
1645			}
1646		}
1647	}
1648	/* no DataAbortQuery needed; we scanned until completion */
1649
1650	/*
1651	 * We have a vector of plugins to be deleted. Remove all records from both
1652	 * DBs associated with the plugins, as specified by guid.
1653	 */
1654	size_t numRecords = tbdRecords.size();
1655	for(size_t i=0; i<numRecords; i++) {
1656		TbdRecord *tbdRecord = tbdRecords[i];
1657		mSession.removeRecordsForGuid(tbdRecord->guid(), objDbHand());
1658		mSession.removeRecordsForGuid(tbdRecord->guid(), directDbHand());
1659	}
1660	for(size_t i=0; i<numRecords; i++) {
1661		delete tbdRecords[i];
1662	}
1663}
1664
1665
1666/*
1667 * Update DBs for all bundles in specified directory.
1668 */
1669void MDSSession::DbFilesInfo::updateForBundleDir(
1670	const char *bundleDirPath)
1671{
1672	/* do this with readdir(); CFBundleCreateBundlesFromDirectory is
1673	 * much too heavyweight */
1674	MSDebug("...updating DBs for dir %s", bundleDirPath);
1675	DIR *dir = opendir(bundleDirPath);
1676	if (dir == NULL) {
1677		MSDebug("updateForBundleDir: error %d opening %s", errno, bundleDirPath);
1678		return;
1679	}
1680	struct dirent *dp;
1681	char fullPath[MAXPATHLEN];
1682	while ((dp = readdir(dir)) != NULL) {
1683		if(isBundle(dp)) {
1684			sprintf(fullPath, "%s/%s", bundleDirPath, dp->d_name);
1685			updateForBundle(fullPath);
1686		}
1687	}
1688	closedir(dir);
1689}
1690
1691/*
1692 * lookup by path - just returns true if there is a record assoociated with the path
1693 * in mObjDbHand.
1694 */
1695bool MDSSession::DbFilesInfo::lookupForPath(
1696	const char *path)
1697{
1698	CSSM_QUERY						query;
1699	CSSM_DB_UNIQUE_RECORD_PTR		record = NULL;
1700	CSSM_HANDLE						resultHand = 0;
1701	CSSM_DB_RECORD_ATTRIBUTE_DATA	recordAttrs;
1702	CSSM_DB_ATTRIBUTE_DATA			theAttr;
1703	CSSM_DB_ATTRIBUTE_INFO_PTR		attrInfo = &theAttr.Info;
1704	CSSM_SELECTION_PREDICATE		predicate;
1705	CSSM_DATA						predData;
1706
1707	recordAttrs.DataRecordType = MDS_OBJECT_RECORDTYPE;
1708	recordAttrs.SemanticInformation = 0;
1709	recordAttrs.NumberOfAttributes = 1;
1710	recordAttrs.AttributeData = &theAttr;
1711
1712	attrInfo->AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1713	attrInfo->Label.AttributeName = (char*) "Path";
1714	attrInfo->AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1715
1716	theAttr.NumberOfValues = 0;
1717	theAttr.Value = NULL;
1718
1719	predicate.DbOperator = CSSM_DB_EQUAL;
1720	predicate.Attribute.Info.AttributeNameFormat = CSSM_DB_ATTRIBUTE_NAME_AS_STRING;
1721	predicate.Attribute.Info.Label.AttributeName = (char*) "Path";
1722	predicate.Attribute.Info.AttributeFormat = CSSM_DB_ATTRIBUTE_FORMAT_STRING;
1723	predData.Data = (uint8 *)path;
1724	predData.Length = strlen(path);
1725	predicate.Attribute.Value = &predData;
1726	predicate.Attribute.NumberOfValues = 1;
1727
1728	query.RecordType = MDS_OBJECT_RECORDTYPE;
1729	query.Conjunctive = CSSM_DB_NONE;
1730	query.NumSelectionPredicates = 1;
1731	query.SelectionPredicate = &predicate;
1732	query.QueryLimits.TimeLimit = 0;			// FIXME - meaningful?
1733	query.QueryLimits.SizeLimit = 1;			// FIXME - meaningful?
1734	query.QueryFlags = 0;		// CSSM_QUERY_RETURN_DATA...FIXME - used?
1735
1736	bool ourRtn = true;
1737	try {
1738		CssmQuery perryQuery(query);
1739		resultHand = mSession.DataGetFirst(objDbHand(),
1740			&perryQuery,
1741			&recordAttrs,
1742			NULL,			// No data
1743			record);
1744	}
1745	catch (...) {
1746		ourRtn = false;
1747	}
1748	if(record) {
1749		mSession.FreeUniqueRecord(mObjDbHand, *record);
1750	}
1751	else {
1752		ourRtn = false;
1753	}
1754	if(resultHand && ourRtn) {
1755		/* more resulting pending; terminate the search */
1756		try {
1757			mSession.DataAbortQuery(mObjDbHand, resultHand);
1758		}
1759		catch(...) {
1760			MSDebug("exception on DataAbortQuery in lookupForPath");
1761		}
1762	}
1763	for(unsigned dex=0; dex<theAttr.NumberOfValues; dex++) {
1764		if(theAttr.Value[dex].Data) {
1765			mSession.free(theAttr.Value[dex].Data);
1766		}
1767	}
1768	mSession.free(theAttr.Value);
1769	return ourRtn;
1770}
1771
1772/* update entry for one bundle, which is known to exist */
1773void MDSSession::DbFilesInfo::updateForBundle(
1774	const char *bundlePath)
1775{
1776	MSDebug("...updating DBs for bundle %s", bundlePath);
1777
1778	/* Quick lookup - do we have ANY entry for a bundle with this path? */
1779	if(lookupForPath(bundlePath)) {
1780		/* Yep, we're done */
1781		return;
1782	}
1783	MDSAttrParser parser(bundlePath,
1784		mSession,
1785		objDbHand(),
1786		directDbHand());
1787	try {
1788		parser.parseAttrs();
1789	}
1790	catch (const CssmError &err) {
1791		// a corrupt MDS info file invalidates the entire plugin
1792		const char *guid = parser.guid();
1793		if (guid) {
1794			mSession.removeRecordsForGuid(guid, objDbHand());
1795			mSession.removeRecordsForGuid(guid, directDbHand());
1796		}
1797	}
1798}
1799
1800
1801//
1802// Private API: add MDS records from contents of file
1803// These files are typically written by securityd and handed to us in this call.
1804//
1805void MDSSession::installFile(const MDS_InstallDefaults *defaults,
1806	const char *inBundlePath, const char *subdir, const char *file)
1807{
1808	string bundlePath = inBundlePath ? inBundlePath : cfString(CFBundleGetMainBundle());
1809	DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1810	MDSAttrParser parser(bundlePath.c_str(),
1811		*this,
1812		dbFiles.objDbHand(),
1813		dbFiles.directDbHand());
1814	parser.setDefaults(defaults);
1815
1816	try {
1817		if (file == NULL)	// parse a directory
1818			if (subdir)		// a particular directory
1819				parser.parseAttrs(CFTempString(subdir));
1820			else			// all resources in bundle
1821				parser.parseAttrs(NULL);
1822		else				// parse just one file
1823			parser.parseFile(CFRef<CFURLRef>(makeCFURL(file)), CFTempString(subdir));
1824	}
1825	catch (const CssmError &err) {
1826		const char *guid = parser.guid();
1827		if (guid) {
1828			removeRecordsForGuid(guid, dbFiles.objDbHand());
1829			removeRecordsForGuid(guid, dbFiles.directDbHand());
1830		}
1831	}
1832}
1833
1834
1835//
1836// Private API: Remove all records for a guid/subservice
1837//
1838// Note: Multicursors searching for SSID fail because not all records in the
1839// database have this attribute. So we have to explicitly run through all tables
1840// that do.
1841//
1842void MDSSession::removeSubservice(const char *guid, uint32 ssid)
1843{
1844	DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR);
1845
1846	CssmClient::Query query =
1847		Attribute("ModuleID") == guid &&
1848		Attribute("SSID") == ssid;
1849
1850	// only CSP and DL tables are cleared here
1851	// (this function is private to securityd, which only handles those types)
1852	clearRecords(dbFiles.directDbHand(),
1853		CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_PRIMARY_RECORDTYPE));
1854	clearRecords(dbFiles.directDbHand(),
1855		CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_CAPABILITY_RECORDTYPE));
1856	clearRecords(dbFiles.directDbHand(),
1857		CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_ENCAPSULATED_PRODUCT_RECORDTYPE));
1858	clearRecords(dbFiles.directDbHand(),
1859		CssmQuery(query.cssmQuery(), MDS_CDSADIR_CSP_SC_INFO_RECORDTYPE));
1860	clearRecords(dbFiles.directDbHand(),
1861		CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_PRIMARY_RECORDTYPE));
1862	clearRecords(dbFiles.directDbHand(),
1863		CssmQuery(query.cssmQuery(), MDS_CDSADIR_DL_ENCAPSULATED_PRODUCT_RECORDTYPE));
1864}
1865
1866
1867} // end namespace Security
1868