]>
Commit | Line | Data |
---|---|---|
bac41a7b A |
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/DbContext.h> | |
22 | #include "MDSModule.h" | |
29654253 A |
23 | #include "MDSAttrParser.h" |
24 | #include "MDSAttrUtils.h" | |
bac41a7b A |
25 | |
26 | #include <memory> | |
27 | #include <Security/cssmerr.h> | |
28 | #include <Security/utilities.h> | |
29 | #include <Security/logging.h> | |
29654253 A |
30 | #include <Security/debugging.h> |
31 | #include <Security/mds_schema.h> | |
bac41a7b A |
32 | |
33 | #include <sys/types.h> | |
29654253 | 34 | #include <sys/param.h> |
bac41a7b A |
35 | #include <dirent.h> |
36 | #include <fcntl.h> | |
29654253 | 37 | #include <assert.h> |
bac41a7b A |
38 | #include <time.h> |
39 | ||
29654253 A |
40 | /* |
41 | * The layout of the various MDS DB files on disk is as follows: | |
42 | * | |
43 | * /var/tmp/mds -- owner = root, mode = 01777, world writable, sticky | |
44 | * mdsObject.db -- owner = root, mode = 0644, object DB | |
45 | * mdsDirectory.db -- owner = root, mode = 0644, MDS directory DB | |
46 | * mds.lock -- temporary, owner = root, protects creation of | |
47 | * previous two files | |
48 | * <uid>/ -- owner = <uid>, mode = 0644 | |
49 | * mdsObject.db -- owner = <uid>, mode = 0644, object DB | |
50 | * mdsDirectory.db -- owner = <uid>, mode = 0644, MDS directory DB | |
51 | * mds.lock -- temporary, owner = <uid>, protects creation of | |
52 | * previous two files | |
53 | * | |
54 | * The /var/tmp/mds directory and the two db files in it are created by root | |
55 | * via SS or an AEWP call. Each user except for root has their own private | |
56 | * directory with two DB files and a lock. The first time a user accesses MDS, | |
57 | * the per-user directory is created and the per-user DB files are created as | |
58 | * copies of the system DB files. Fcntl() with a F_RDLCK is used to lock the system | |
59 | * DB files when they are the source of these copies; this is the same mechanism | |
60 | * used by the underlying AtomincFile. | |
61 | * | |
62 | * The sticky bit in /var/tmp/mds ensures that users cannot delete, rename, and/or | |
63 | * replace the root-owned DB files in that directory, and that users can not | |
64 | * modify other user's private MDS directories. | |
65 | */ | |
66 | namespace Security | |
67 | { | |
bac41a7b | 68 | |
29654253 A |
69 | /* |
70 | * Nominal location of Security.framework. | |
71 | */ | |
72 | #define MDS_SYSTEM_PATH "/System/Library/Frameworks" | |
73 | #define MDS_SYSTEM_FRAME "Security.framework" | |
bac41a7b | 74 | |
29654253 A |
75 | /* |
76 | * Nominal location of standard plugins. | |
77 | */ | |
78 | #define MDS_BUNDLE_PATH "/System/Library/Security" | |
79 | #define MDS_BUNDLE_EXTEN ".bundle" | |
bac41a7b | 80 | |
bac41a7b | 81 | |
29654253 A |
82 | /* |
83 | * Location of system MDS database and lock files. | |
84 | */ | |
85 | #define MDS_SYSTEM_DB_DIR "/private/var/tmp/mds" | |
86 | #define MDS_LOCK_FILE_NAME "mds.lock" | |
87 | #define MDS_OBJECT_DB_NAME "mdsObject.db" | |
88 | #define MDS_DIRECT_DB_NAME "mdsDirectory.db" | |
89 | #define MDS_LOCK_FILE_PATH MDS_SYSTEM_DB_DIR "/" MDS_LOCK_FILE_NAME | |
90 | #define MDS_OBJECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_OBJECT_DB_NAME | |
91 | #define MDS_DIRECT_DB_PATH MDS_SYSTEM_DB_DIR "/" MDS_DIRECT_DB_NAME | |
bac41a7b | 92 | |
29654253 A |
93 | /* |
94 | * Location of per-user bundles, relative to home directory. | |
95 | * PEr-user DB files are in MDS_SYSTEM_DB_DIR/<uid>/. | |
96 | */ | |
97 | #define MDS_USER_DB_DIR "Library/Security" | |
98 | #define MDS_USER_BUNDLE "Library/Security" | |
99 | ||
100 | /* time to wait in ms trying to acquire lock */ | |
101 | #define DB_LOCK_TIMEOUT (2 * 1000) | |
102 | ||
103 | /* Minimum interval, in seconds, between rescans for plugin changes */ | |
104 | #define MDS_SCAN_INTERVAL 10 | |
105 | ||
106 | /* initial debug - start from scratch each time */ | |
107 | #define START_FROM_SCRATCH 0 | |
108 | ||
109 | /* debug - skip file-level locking */ | |
110 | #define SKIP_FILE_LOCKING 0 | |
111 | ||
df0e469f A |
112 | #ifndef NDEBUG |
113 | ||
29654253 A |
114 | /* Only allow root to create and update system DB files - in the final config this |
115 | * will be true */ | |
116 | #define SYSTEM_MDS_ROOT_ONLY 0 | |
117 | ||
118 | /* | |
119 | * Early development; no Security Server/root involvement with system DB creation. | |
120 | * If this is true, SYSTEM_MDS_ROOT_ONLY must be false (though both can be | |
121 | * false for intermediate testing). | |
122 | */ | |
123 | #define SYSTEM_DBS_VIA_USER 1 | |
bac41a7b | 124 | |
df0e469f A |
125 | #else /* NDEBUG */ |
126 | /* normal deployment case */ | |
127 | #define SYSTEM_MDS_ROOT_ONLY 1 | |
128 | #define SYSTEM_DBS_VIA_USER 0 | |
129 | #endif /* NDEBUG */ | |
bac41a7b | 130 | |
29654253 A |
131 | /* |
132 | * Determine if both of the specified DB files exist as | |
133 | * accessible regular files. Returns true if they do. If the purge argument | |
134 | * is true, we'll ensure that either both or neither of the files exist on | |
135 | * exit. | |
136 | */ | |
137 | static bool doFilesExist( | |
138 | const char *objDbFile, | |
139 | const char *directDbFile, | |
140 | bool purge) // false means "passive" check | |
bac41a7b | 141 | { |
29654253 A |
142 | struct stat sb; |
143 | bool objectExist = false; | |
144 | bool directExist = false; | |
145 | ||
146 | if (stat(objDbFile, &sb) == 0) { | |
147 | /* Object DB exists */ | |
148 | if(!(sb.st_mode & S_IFREG)) { | |
149 | MSDebug("deleting non-regular file %s", objDbFile); | |
150 | if(purge && unlink(objDbFile)) { | |
151 | MSDebug("unlink(%s) returned %d", objDbFile, errno); | |
152 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
153 | } | |
154 | } | |
155 | else { | |
156 | objectExist = true; | |
157 | } | |
158 | } | |
159 | if (stat(directDbFile, &sb) == 0) { | |
160 | /* directory DB exists */ | |
161 | if(!(sb.st_mode & S_IFREG)) { | |
162 | MSDebug("deleting non-regular file %s", directDbFile); | |
163 | if(purge & unlink(directDbFile)) { | |
164 | MSDebug("unlink(%s) returned %d", directDbFile, errno); | |
165 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
166 | } | |
167 | } | |
168 | directExist = true; | |
169 | } | |
170 | if(objectExist && directExist) { | |
171 | /* both databases exist as regular files */ | |
172 | return true; | |
173 | } | |
174 | else if(!purge) { | |
175 | return false; | |
176 | } | |
177 | ||
178 | /* at least one does not exist - ensure neither of them do */ | |
179 | if(objectExist) { | |
180 | if(unlink(objDbFile)) { | |
181 | MSDebug("unlink(%s) returned %d", objDbFile, errno); | |
182 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
183 | } | |
184 | } | |
185 | if(directExist) { | |
186 | if(unlink(directDbFile)) { | |
187 | MSDebug("unlink(%s) returned %d", directDbFile, errno); | |
188 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
189 | } | |
190 | } | |
191 | return false; | |
bac41a7b A |
192 | } |
193 | ||
29654253 A |
194 | /* |
195 | * Determine if specified directory exists. | |
196 | */ | |
197 | static bool doesDirectExist( | |
198 | const char *dirPath) | |
199 | { | |
200 | struct stat sb; | |
201 | ||
202 | if (stat(dirPath, &sb)) { | |
203 | return false; | |
204 | } | |
205 | if(!(sb.st_mode & S_IFDIR)) { | |
206 | return false; | |
207 | } | |
208 | return true; | |
209 | } | |
bac41a7b | 210 | |
29654253 A |
211 | /* |
212 | * Create specified directory if it doesn't already exist. | |
213 | * Zero for mode means "use the default provided by 0755 modified by umask". | |
214 | */ | |
215 | static int createDir( | |
216 | const char *dirPath, | |
217 | mode_t dirMode = 0) | |
218 | { | |
219 | if(doesDirectExist(dirPath)) { | |
220 | return 0; | |
221 | } | |
222 | int rtn = mkdir(dirPath, 0755); | |
223 | if(rtn) { | |
224 | if(errno == EEXIST) { | |
225 | /* this one's OK */ | |
226 | rtn = 0; | |
227 | } | |
228 | else { | |
229 | rtn = errno; | |
230 | MSDebug("mkdir(%s) returned %d", dirPath, errno); | |
231 | } | |
232 | } | |
233 | if((rtn == 0) && (dirMode != 0)) { | |
234 | rtn = chmod(dirPath, dirMode); | |
235 | if(rtn) { | |
236 | MSDebug("chmod(%s) returned %d", dirPath, errno); | |
237 | } | |
238 | } | |
239 | return rtn; | |
240 | } | |
241 | ||
242 | /* | |
243 | * Create an MDS session. | |
244 | */ | |
bac41a7b A |
245 | MDSSession::MDSSession (const Guid *inCallerGuid, |
246 | const CSSM_MEMORY_FUNCS &inMemoryFunctions) : | |
29654253 A |
247 | DatabaseSession(MDSModule::get().databaseManager()), |
248 | mCssmMemoryFunctions (inMemoryFunctions), | |
249 | mModule(MDSModule::get()), | |
bac41a7b A |
250 | mLockFd(-1) |
251 | { | |
29654253 | 252 | MSDebug("MDSSession::MDSSession"); |
bac41a7b | 253 | |
29654253 A |
254 | #if START_FROM_SCRATCH |
255 | unlink(MDS_LOCK_FILE_PATH); | |
256 | unlink(MDS_OBJECT_DB_PATH); | |
257 | unlink(MDS_DIRECT_DB_PATH); | |
258 | #endif | |
259 | ||
bac41a7b A |
260 | mCallerGuidPresent = inCallerGuid != nil; |
261 | if (mCallerGuidPresent) | |
262 | mCallerGuid = *inCallerGuid; | |
bac41a7b | 263 | |
29654253 A |
264 | /* |
265 | * Create DB files if necessary; make sure they are up-to-date | |
266 | */ | |
267 | // no! done in either install or open! updateDataBases(); | |
bac41a7b A |
268 | } |
269 | ||
270 | MDSSession::~MDSSession () | |
271 | { | |
29654253 A |
272 | MSDebug("MDSSession::~MDSSession"); |
273 | releaseLock(mLockFd); | |
bac41a7b A |
274 | } |
275 | ||
276 | void | |
277 | MDSSession::terminate () | |
278 | { | |
29654253 A |
279 | MSDebug("MDSSession::terminate"); |
280 | releaseLock(mLockFd); | |
bac41a7b A |
281 | closeAll(); |
282 | } | |
283 | ||
29654253 A |
284 | /* |
285 | * Called by security server or AEWP-executed privileged tool. | |
286 | */ | |
bac41a7b A |
287 | void |
288 | MDSSession::install () | |
289 | { | |
29654253 A |
290 | if((getuid() != (uid_t)0) && SYSTEM_MDS_ROOT_ONLY) { |
291 | CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); | |
292 | } | |
293 | ||
294 | int sysFdLock = -1; | |
295 | try { | |
296 | /* before we obtain the lock, ensure the the system MDS DB directory exists */ | |
297 | if(createDir(MDS_SYSTEM_DB_DIR, 01777)) { | |
298 | MSDebug("Error creating system MDS dir; aborting."); | |
299 | CssmError::throwMe(CSSMERR_DL_OS_ACCESS_DENIED); | |
300 | } | |
301 | ||
302 | if(!obtainLock(MDS_LOCK_FILE_PATH, sysFdLock, DB_LOCK_TIMEOUT)) { | |
303 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
304 | } | |
305 | if(!systemDatabasesPresent(true)) { | |
df0e469f A |
306 | /* |
307 | * Root umask is 0 when this runs, so specify readable (only) | |
308 | * by world. Also, turn off autocommit during initial | |
309 | * system DB population. | |
310 | */ | |
311 | bool created = createSystemDatabases(CSSM_FALSE, 0644); | |
29654253 A |
312 | if(created) { |
313 | /* | |
314 | * Skip possible race condition in which this is called twice, | |
315 | * both via SS by user procs who say "no system DBs present" | |
316 | * in their updateDataBases() method. | |
317 | * | |
318 | * Do initial population of system DBs. | |
319 | */ | |
320 | DbFilesInfo dbFiles(*this, MDS_SYSTEM_DB_DIR); | |
29654253 | 321 | dbFiles.autoCommit(CSSM_FALSE); |
29654253 A |
322 | dbFiles.updateSystemDbInfo(MDS_SYSTEM_PATH, MDS_BUNDLE_PATH); |
323 | } | |
324 | } | |
325 | } | |
326 | catch(...) { | |
327 | if(sysFdLock != -1) { | |
328 | releaseLock(sysFdLock); | |
329 | } | |
330 | throw; | |
331 | } | |
332 | releaseLock(sysFdLock); | |
bac41a7b A |
333 | } |
334 | ||
335 | // | |
336 | // In this implementation, the uninstall() call is not supported since | |
337 | // we do not allow programmatic deletion of the MDS databases. | |
338 | // | |
339 | ||
340 | void | |
341 | MDSSession::uninstall () | |
342 | { | |
343 | CssmError::throwMe(CSSM_ERRCODE_FUNCTION_NOT_IMPLEMENTED); | |
344 | } | |
345 | ||
29654253 A |
346 | /* |
347 | * Common private open routine given a full specified path. | |
348 | * | |
349 | * FIXME: both of these dbOpen routines leak like crazy even though | |
350 | * we know we close properly. | |
351 | * Typical stack trace (from MallocDebug) of a leak is | |
352 | * | |
353 | * DatabaseSession::DbOpen(char const *, cssm_net_address const...) | |
354 | * DatabaseManager::dbOpen(Security::DatabaseSession &, ...) | |
355 | * Database::_dbOpen(Security::DatabaseSession &, unsigned long, ...) | |
356 | * AppleDatabase::dbOpen(Security::DbContext &) | |
357 | * DbModifier::openDatabase(void) | |
358 | * DbModifier::getDbVersion(void) | |
359 | * DbVersion::DbVersion(Security::AtomicFile &, ...) | |
360 | * DbVersion::open(void) | |
361 | * MetaRecord::unpackRecord(Security::ReadSection const &, ...) | |
362 | * MetaRecord::unpackAttribute(Security::ReadSection const &, ...) | |
363 | * MetaAttribute::unpackAttribute(Security::ReadSection const &, ..) | |
364 | * TypedMetaAttribute<Security::StringValue>::unpackValue(...) | |
365 | * TrackingAllocator::malloc(unsigned long) | |
366 | */ | |
367 | CSSM_DB_HANDLE MDSSession::dbOpen( | |
368 | const char *dbName) | |
bac41a7b | 369 | { |
29654253 A |
370 | MSDebug("Opening %s", dbName); |
371 | CSSM_DB_HANDLE dbHand; | |
372 | DatabaseSession::DbOpen(dbName, | |
373 | NULL, // DbLocation | |
374 | CSSM_DB_ACCESS_READ, | |
375 | NULL, // AccessCred - hopefully optional | |
376 | NULL, // OpenParameters | |
377 | dbHand); | |
378 | return dbHand; | |
bac41a7b A |
379 | } |
380 | ||
29654253 A |
381 | |
382 | /* DatabaseSession routines we need to override */ | |
383 | void MDSSession::DbOpen(const char *DbName, | |
384 | const CSSM_NET_ADDRESS *DbLocation, | |
385 | CSSM_DB_ACCESS_TYPE AccessRequest, | |
386 | const AccessCredentials *AccessCred, | |
387 | const void *OpenParameters, | |
388 | CSSM_DB_HANDLE &DbHandle) | |
bac41a7b | 389 | { |
29654253 A |
390 | /* make sure DBs are up-to-date */ |
391 | updateDataBases(); | |
392 | ||
393 | /* | |
394 | * Only task here is map incoming DbName - specified in the CDSA | |
395 | * spec - to a filename we actually use (which is a path to either | |
396 | * a system MDS DB file or a per-user MDS DB file). | |
397 | */ | |
398 | if(DbName == NULL) { | |
399 | CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME); | |
400 | } | |
401 | const char *dbName; | |
402 | if(!strcmp(DbName, MDS_OBJECT_DIRECTORY_NAME)) { | |
403 | dbName = MDS_OBJECT_DB_NAME; | |
404 | } | |
405 | else if(!strcmp(DbName, MDS_CDSA_DIRECTORY_NAME)) { | |
406 | dbName = MDS_DIRECT_DB_NAME; | |
407 | } | |
408 | else { | |
409 | CssmError::throwMe(CSSMERR_DL_INVALID_DB_NAME); | |
410 | } | |
411 | char fullPath[MAXPATHLEN]; | |
412 | dbFullPath(dbName, fullPath); | |
413 | DatabaseSession::DbOpen(fullPath, DbLocation, AccessRequest, AccessCred, | |
414 | OpenParameters, DbHandle); | |
bac41a7b A |
415 | } |
416 | ||
29654253 A |
417 | void |
418 | MDSSession::GetDbNames(CSSM_NAME_LIST_PTR &outNameList) | |
bac41a7b | 419 | { |
29654253 A |
420 | outNameList = new CSSM_NAME_LIST[1]; |
421 | outNameList->NumStrings = 2; | |
422 | outNameList->String = new (char *)[2]; | |
423 | outNameList->String[0] = MDSCopyCstring(MDS_OBJECT_DIRECTORY_NAME); | |
424 | outNameList->String[1] = MDSCopyCstring(MDS_CDSA_DIRECTORY_NAME); | |
bac41a7b A |
425 | } |
426 | ||
29654253 A |
427 | void |
428 | MDSSession::FreeNameList(CSSM_NAME_LIST &inNameList) | |
bac41a7b | 429 | { |
29654253 A |
430 | delete [] inNameList.String[0]; |
431 | delete [] inNameList.String[1]; | |
432 | delete [] inNameList.String; | |
bac41a7b A |
433 | } |
434 | ||
29654253 A |
435 | void MDSSession::GetDbNameFromHandle(CSSM_DB_HANDLE DBHandle, |
436 | char **DbName) | |
bac41a7b | 437 | { |
29654253 A |
438 | printf("GetDbNameFromHandle: code on demand\n"); |
439 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
440 | } | |
bac41a7b A |
441 | |
442 | // | |
443 | // Attempt to obtain an exclusive lock over the the MDS databases. The | |
444 | // parameter is the maximum amount of time, in milliseconds, to spend | |
445 | // trying to obtain the lock. A value of zero means to return failure | |
446 | // right away if the lock cannot be obtained. | |
447 | // | |
bac41a7b | 448 | bool |
29654253 A |
449 | MDSSession::obtainLock( |
450 | const char *lockFile, // e.g. MDS_LOCK_FILE_PATH | |
451 | int &fd, // IN/OUT | |
452 | int timeout) // default 0 | |
bac41a7b | 453 | { |
29654253 A |
454 | #if SKIP_FILE_LOCKING |
455 | return true; | |
456 | #else | |
457 | ||
bac41a7b A |
458 | static const int kRetryDelay = 250; // ms |
459 | ||
29654253 A |
460 | fd = open(MDS_LOCK_FILE_PATH, O_CREAT | O_EXCL, 0544); |
461 | while (fd == -1 && timeout >= kRetryDelay) { | |
bac41a7b A |
462 | timeout -= kRetryDelay; |
463 | usleep(1000 * kRetryDelay); | |
29654253 | 464 | mLockFd = open(MDS_LOCK_FILE_PATH, O_CREAT | O_EXCL, 0544); |
bac41a7b A |
465 | } |
466 | ||
29654253 A |
467 | return (fd != -1); |
468 | #endif /* SKIP_FILE_LOCKING */ | |
bac41a7b A |
469 | } |
470 | ||
471 | // | |
472 | // Release the exclusive lock over the MDS databases. If this session | |
473 | // does not hold the lock, this method does nothing. | |
474 | // | |
475 | ||
476 | void | |
29654253 | 477 | MDSSession::releaseLock(int &fd) |
bac41a7b | 478 | { |
29654253 A |
479 | #if !SKIP_FILE_LOCKING |
480 | if (fd != -1) { | |
481 | close(fd); | |
482 | unlink(MDS_LOCK_FILE_PATH); | |
483 | fd = -1; | |
bac41a7b | 484 | } |
29654253 | 485 | #endif |
bac41a7b A |
486 | } |
487 | ||
29654253 A |
488 | /* given DB file name, fill in fully specified path */ |
489 | void MDSSession::dbFullPath( | |
490 | const char *dbName, | |
491 | char fullPath[MAXPATHLEN+1]) | |
492 | { | |
493 | mModule.getDbPath(fullPath); | |
494 | assert(fullPath[0] != '\0'); | |
495 | strcat(fullPath, "/"); | |
496 | strcat(fullPath, dbName); | |
497 | } | |
bac41a7b | 498 | |
29654253 A |
499 | /* |
500 | * See if any per-user bundles exist in specified directory. Returns true if so. | |
501 | * First the check for one entry.... | |
502 | */ | |
503 | static bool isBundle( | |
504 | const struct dirent *dp) | |
505 | { | |
506 | if(dp == NULL) { | |
507 | return false; | |
508 | } | |
509 | /* NFS directories show up as DT_UNKNOWN */ | |
510 | switch(dp->d_type) { | |
511 | case DT_UNKNOWN: | |
512 | case DT_DIR: | |
513 | break; | |
514 | default: | |
515 | return false; | |
516 | } | |
517 | int suffixLen = strlen(MDS_BUNDLE_EXTEN); | |
518 | int len = strlen(dp->d_name); | |
519 | ||
520 | return (len >= suffixLen) && | |
521 | !strcmp(dp->d_name + len - suffixLen, MDS_BUNDLE_EXTEN); | |
522 | } | |
523 | ||
524 | /* now the full directory search */ | |
525 | static bool checkUserBundles( | |
526 | const char *bundlePath) | |
bac41a7b | 527 | { |
29654253 A |
528 | MSDebug("searching for user bundles in %s", bundlePath); |
529 | DIR *dir = opendir(bundlePath); | |
530 | if (dir == NULL) { | |
531 | return false; | |
532 | } | |
533 | struct dirent *dp; | |
534 | bool rtn = false; | |
535 | while ((dp = readdir(dir)) != NULL) { | |
536 | if(isBundle(dp)) { | |
537 | /* any other checking to do? */ | |
538 | rtn = true; | |
539 | break; | |
540 | } | |
541 | } | |
542 | closedir(dir); | |
543 | MSDebug("...%s bundle(s) found", rtn ? "" : "No"); | |
544 | return rtn; | |
545 | } | |
546 | ||
547 | #define COPY_BUF_SIZE 1024 | |
548 | ||
549 | /* Single file copy with locking */ | |
550 | static void safeCopyFile( | |
551 | const char *fromPath, | |
552 | const char *toPath) | |
553 | { | |
554 | /* open source for reading */ | |
555 | int srcFd = open(fromPath, O_RDONLY, 0); | |
556 | if(srcFd < 0) { | |
557 | /* FIXME - what error would we see if the file is locked for writing | |
558 | * by someone else? We definitely have to handle that. */ | |
559 | int error = errno; | |
560 | MSDebug("Error %d opening system DB file %s\n", error, fromPath); | |
561 | UnixError::throwMe(error); | |
562 | } | |
bac41a7b | 563 | |
29654253 A |
564 | /* acquire the same kind of lock AtomicFile uses */ |
565 | struct flock fl; | |
566 | fl.l_start = 0; | |
567 | fl.l_len = 1; | |
568 | fl.l_pid = getpid(); | |
569 | fl.l_type = F_RDLCK; // AtomicFile gets F_WRLCK | |
570 | fl.l_whence = SEEK_SET; | |
571 | ||
572 | // Keep trying to obtain the lock if we get interupted. | |
573 | for (;;) { | |
574 | if (::fcntl(srcFd, F_SETLKW, reinterpret_cast<int>(&fl)) == -1) { | |
575 | int error = errno; | |
576 | if (error == EINTR) { | |
577 | continue; | |
578 | } | |
579 | MSDebug("Error %d locking system DB file %s\n", error, fromPath); | |
580 | UnixError::throwMe(error); | |
581 | } | |
582 | else { | |
583 | break; | |
584 | } | |
585 | } | |
586 | ||
587 | /* create destination */ | |
588 | int destFd = open(toPath, O_WRONLY | O_APPEND | O_CREAT | O_TRUNC | O_EXCL, 0644); | |
589 | if(destFd < 0) { | |
590 | int error = errno; | |
591 | MSDebug("Error %d opening user DB file %s\n", error, toPath); | |
592 | UnixError::throwMe(error); | |
593 | } | |
bac41a7b | 594 | |
29654253 A |
595 | /* copy */ |
596 | char buf[COPY_BUF_SIZE]; | |
597 | while(1) { | |
598 | int bytesRead = read(srcFd, buf, COPY_BUF_SIZE); | |
599 | if(bytesRead == 0) { | |
600 | break; | |
601 | } | |
602 | if(bytesRead < 0) { | |
603 | int error = errno; | |
604 | MSDebug("Error %d reading system DB file %s\n", error, fromPath); | |
605 | UnixError::throwMe(error); | |
606 | } | |
607 | int bytesWritten = write(destFd, buf, bytesRead); | |
608 | if(bytesWritten < 0) { | |
609 | int error = errno; | |
610 | MSDebug("Error %d writing user DB file %s\n", error, toPath); | |
611 | UnixError::throwMe(error); | |
612 | } | |
613 | } | |
bac41a7b | 614 | |
29654253 A |
615 | /* unlock source and close both */ |
616 | fl.l_type = F_UNLCK; | |
617 | if (::fcntl(srcFd, F_SETLK, reinterpret_cast<int>(&fl)) == -1) { | |
618 | MSDebug("Error %d unlocking system DB file %s\n", errno, fromPath); | |
619 | } | |
620 | close(srcFd); | |
621 | close(destFd); | |
622 | } | |
623 | ||
624 | /* Copy system DB files to specified user dir. */ | |
625 | static void copySystemDbs( | |
626 | const char *userDbFileDir) | |
627 | { | |
628 | char toPath[MAXPATHLEN+1]; | |
bac41a7b | 629 | |
29654253 A |
630 | sprintf(toPath, "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); |
631 | safeCopyFile(MDS_OBJECT_DB_PATH, toPath); | |
632 | sprintf(toPath, "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); | |
633 | safeCopyFile(MDS_DIRECT_DB_PATH, toPath); | |
634 | } | |
635 | ||
636 | /* | |
637 | * Ensure current DB files exist and are up-to-date. | |
638 | * Called from MDSSession constructor and from DataGetFirst, DbOpen, and any | |
639 | * other public functions which access a DB from scratch. | |
640 | */ | |
641 | void MDSSession::updateDataBases() | |
642 | { | |
643 | bool isRoot = (getuid() == (uid_t)0); | |
644 | bool createdSystemDb = false; | |
bac41a7b | 645 | |
29654253 A |
646 | /* |
647 | * The first thing we do is to ensure that system DBs are present. | |
648 | * This call right here is the reason for the purge argument in | |
649 | * systemDatabasesPresent(); if we're a user proc, we can't grab the system | |
650 | * MDS lock. | |
651 | */ | |
652 | if(!systemDatabasesPresent(false)) { | |
653 | if(isRoot || SYSTEM_DBS_VIA_USER) { | |
654 | /* Either doing actual MDS op as root, or development case: | |
655 | * install as current user */ | |
656 | install(); | |
657 | } | |
658 | else { | |
659 | /* This path TBD; it involves either a SecurityServer RPC or | |
660 | * a privileged tool exec'd via AEWP. */ | |
661 | assert(0); | |
662 | } | |
663 | /* remember this - we have to delete possible existing user DBs */ | |
664 | createdSystemDb = true; | |
665 | } | |
bac41a7b | 666 | |
29654253 A |
667 | /* if we scanned recently, we're done */ |
668 | double delta = mModule.timeSinceLastScan(); | |
669 | if(delta < (double)MDS_SCAN_INTERVAL) { | |
670 | return; | |
bac41a7b | 671 | } |
29654253 A |
672 | |
673 | /* | |
674 | * Obtain various per-user paths. Root is a special case but follows most | |
675 | * of the same logic from here on. | |
676 | */ | |
677 | char userDbFileDir[MAXPATHLEN+1]; | |
678 | char userObjDbFilePath[MAXPATHLEN+1]; | |
679 | char userDirectDbFilePath[MAXPATHLEN+1]; | |
680 | char userBundlePath[MAXPATHLEN+1]; | |
681 | char userDbLockPath[MAXPATHLEN+1]; | |
682 | ||
683 | if(isRoot) { | |
684 | strcat(userDbFileDir, MDS_SYSTEM_DB_DIR); | |
685 | /* no userBundlePath */ | |
686 | } | |
687 | else { | |
688 | char *userHome = getenv("HOME"); | |
689 | if(userHome == NULL) { | |
690 | /* FIXME - what now, batman? */ | |
691 | MSDebug("updateDataBases: no HOME"); | |
692 | userHome = "/"; | |
693 | } | |
694 | sprintf(userBundlePath, "%s/%s", userHome, MDS_USER_BUNDLE); | |
695 | ||
696 | /* DBs go in a per-UID directory in the system MDS DB directory */ | |
697 | sprintf(userDbFileDir, "%s/%d", MDS_SYSTEM_DB_DIR, (int)(getuid())); | |
698 | } | |
699 | sprintf(userObjDbFilePath, "%s/%s", userDbFileDir, MDS_OBJECT_DB_NAME); | |
700 | sprintf(userDirectDbFilePath, "%s/%s", userDbFileDir, MDS_DIRECT_DB_NAME); | |
701 | sprintf(userDbLockPath, "%s/%s", userDbFileDir, MDS_LOCK_FILE_NAME); | |
702 | ||
703 | /* | |
704 | * Create the per-user directory first...that's where the lock we'll be using | |
705 | * lives. Our createDir() is tolerant of EEXIST errors. | |
706 | */ | |
707 | if(!isRoot) { | |
708 | if(createDir(userDbFileDir)) { | |
709 | /* We'll just have to limp along using the read-only system DBs */ | |
710 | Syslog::alert("Error creating %s", userDbFileDir); | |
711 | MSDebug("Error creating user DBs; using system DBs"); | |
712 | mModule.setDbPath(MDS_SYSTEM_DB_DIR); | |
713 | return; | |
714 | } | |
715 | } | |
716 | ||
717 | /* always release mLockFd no matter what happens */ | |
718 | if(!obtainLock(userDbLockPath, mLockFd, DB_LOCK_TIMEOUT)) { | |
719 | CssmError::throwMe(CSSM_ERRCODE_MDS_ERROR); | |
720 | } | |
721 | try { | |
722 | if(!isRoot) { | |
723 | if(createdSystemDb) { | |
724 | /* initial creation of system DBs by user - start from scratch */ | |
725 | unlink(userObjDbFilePath); | |
726 | unlink(userDirectDbFilePath); | |
727 | } | |
728 | ||
729 | /* | |
730 | * System DBs exist and are as up-to-date as we are allowed to make them. | |
731 | * Create per-user DBs if they don't exist. | |
732 | */ | |
733 |