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