]> git.saurik.com Git - apple/security.git/blob - OSX/authd/authdb.c
Security-57740.1.18.tar.gz
[apple/security.git] / OSX / authd / authdb.c
1 /* Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. */
2
3 #include "authdb.h"
4 #include "mechanism.h"
5 #include "rule.h"
6 #include "debugging.h"
7 #include "authitems.h"
8 #include "server.h"
9
10 #include <sqlite3.h>
11 #include <sqlite3_private.h>
12 #include <CoreFoundation/CoreFoundation.h>
13 #include "rule.h"
14 #include "authutilities.h"
15 #include <libgen.h>
16 #include <sys/stat.h>
17
18 #define AUTHDB "/var/db/auth.db"
19 #define AUTHDB_DATA "/System/Library/Security/authorization.plist"
20
21 #define AUTH_STR(x) #x
22 #define AUTH_STRINGIFY(x) AUTH_STR(x)
23
24 #define AUTHDB_VERSION 1
25 #define AUTHDB_VERSION_STRING AUTH_STRINGIFY(AUTHDB_VERSION)
26
27 #define AUTHDB_BUSY_DELAY 1
28 #define AUTHDB_MAX_HANDLES 3
29
30 struct _authdb_connection_s {
31 __AUTH_BASE_STRUCT_HEADER__;
32
33 authdb_t db;
34 sqlite3 * handle;
35 };
36
37 struct _authdb_s {
38 __AUTH_BASE_STRUCT_HEADER__;
39
40 char * db_path;
41 dispatch_queue_t queue;
42 CFMutableArrayRef connections;
43 };
44
45 static const char * const authdb_upgrade_sql[] = {
46 /* 0 */
47 /* current scheme */
48 "CREATE TABLE delegates_map ("
49 "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,"
50 "d_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,"
51 "ord INTEGER NOT NULL"
52 ");"
53 "CREATE INDEX d_map_d_id ON delegates_map(d_id);"
54 "CREATE INDEX d_map_r_id ON delegates_map(r_id);"
55 "CREATE INDEX d_map_r_id_ord ON delegates_map (r_id, ord);"
56 "CREATE TABLE mechanisms ("
57 "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,"
58 "plugin TEXT NOT NULL,"
59 "param TEXT NOT NULL,"
60 "privileged INTEGER CHECK (privileged = 0 OR privileged = 1) NOT NULL DEFAULT (0)"
61 ");"
62 "CREATE UNIQUE INDEX mechanisms_lookup ON mechanisms (plugin,param,privileged);"
63 "CREATE TABLE mechanisms_map ("
64 "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,"
65 "m_id INTEGER NOT NULL REFERENCES mechanisms(id) ON DELETE CASCADE,"
66 "ord INTEGER NOT NULL"
67 ");"
68 "CREATE INDEX m_map_m_id ON mechanisms_map (m_id);"
69 "CREATE INDEX m_map_r_id ON mechanisms_map (r_id);"
70 "CREATE INDEX m_map_r_id_ord ON mechanisms_map (r_id, ord);"
71 "CREATE TABLE rules ("
72 "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,"
73 "name TEXT NOT NULL UNIQUE,"
74 "type INTEGER CHECK (type = 1 OR type = 2) NOT NULL,"
75 "class INTEGER CHECK (class > 0),"
76 "'group' TEXT,"
77 "kofn INTEGER,"
78 "timeout INTEGER,"
79 "flags INTEGER,"
80 "tries INTEGER,"
81 "version INTEGER NOT NULL DEFAULT (0),"
82 "created REAL NOT NULL DEFAULT (0),"
83 "modified REAL NOT NULL DEFAULT (0),"
84 "hash BLOB,"
85 "identifier TEXT,"
86 "requirement BLOB,"
87 "comment TEXT"
88 ");"
89 "CREATE INDEX a_type ON rules (type);"
90 "CREATE TABLE config ("
91 "'key' TEXT PRIMARY KEY NOT NULL UNIQUE,"
92 "value"
93 ");"
94 "CREATE TABLE prompts ("
95 "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,"
96 "lang TEXT NOT NULL,"
97 "value TEXT NOT NULL"
98 ");"
99 "CREATE INDEX p_r_id ON prompts(r_id);"
100 "CREATE TABLE buttons ("
101 "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE,"
102 "lang TEXT NOT NULL,"
103 "value TEXT NOT NULL"
104 ");"
105 "CREATE INDEX b_r_id ON buttons(r_id);"
106 "INSERT INTO config VALUES('version', "AUTHDB_VERSION_STRING");"
107 };
108
109 static sqlite3 * _create_handle(authdb_t db);
110
111 static int32_t
112 _sqlite3_exec(sqlite3 * handle, const char * query)
113 {
114 int32_t rc = SQLITE_ERROR;
115 require(query != NULL, done);
116
117 char * errmsg = NULL;
118 rc = sqlite3_exec(handle, query, NULL, NULL, &errmsg);
119 if (errmsg) {
120 LOGE("authdb: exec, (%i) %s", rc, errmsg);
121 sqlite3_free(errmsg);
122 }
123
124 done:
125 return rc;
126 }
127
128 struct _db_upgrade_stages {
129 int pre;
130 int main;
131 int post;
132 };
133
134 static struct _db_upgrade_stages auth_upgrade_script[] = {
135 { .pre = -1, .main = 0, .post = -1 } // Create version AUTHDB_VERSION databse.
136 };
137
138 static int32_t _db_run_script(authdb_connection_t dbconn, int number)
139 {
140 int32_t s3e;
141
142 /* Script -1 == skip this step. */
143 if (number < 0)
144 return SQLITE_OK;
145
146 /* If we are attempting to run a script we don't have, fail. */
147 if ((size_t)number >= sizeof(authdb_upgrade_sql) / sizeof(char*))
148 return SQLITE_CORRUPT;
149
150 s3e = _sqlite3_exec(dbconn->handle, authdb_upgrade_sql[number]);
151
152 return s3e;
153 }
154
155 static int32_t _db_upgrade_from_version(authdb_connection_t dbconn, int32_t version)
156 {
157 int32_t s3e;
158
159 /* If we are attempting to upgrade to a version greater than what we have
160 an upgrade script for, fail. */
161 if (version < 0 ||
162 (size_t)version >= sizeof(auth_upgrade_script) / sizeof(struct _db_upgrade_stages))
163 return SQLITE_CORRUPT;
164
165 struct _db_upgrade_stages *script = &auth_upgrade_script[version];
166 s3e = _db_run_script(dbconn, script->pre);
167 if (s3e == SQLITE_OK)
168 s3e = _db_run_script(dbconn, script->main);
169 if (s3e == SQLITE_OK)
170 s3e = _db_run_script(dbconn, script->post);
171
172 return s3e;
173 }
174
175 static void _printCFError(const char * errmsg, CFErrorRef err)
176 {
177 if (err) {
178 CFStringRef errString = NULL;
179 errString = CFErrorCopyDescription(err);
180 char *tmp = _copy_cf_string(errString, NULL);
181 LOGV("%s, %s", errmsg, tmp);
182 free_safe(tmp);
183 CFReleaseSafe(errString);
184 } else {
185 LOGV("%s", errmsg);
186 }
187 }
188
189 static void _db_load_data(authdb_connection_t dbconn, auth_items_t config)
190 {
191 CFURLRef authURL = NULL;
192 CFPropertyListRef plist = NULL;
193 CFDataRef data = NULL;
194 int32_t rc = 0;
195 CFErrorRef err = NULL;
196 CFTypeRef value = NULL;
197 CFAbsoluteTime ts = 0;
198 CFAbsoluteTime old_ts = 0;
199 Boolean ok;
200
201 authURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR(AUTHDB_DATA), kCFURLPOSIXPathStyle, false);
202 require_action(authURL != NULL, done, LOGE("authdb: file not found %s", AUTHDB_DATA));
203
204 ok = CFURLCopyResourcePropertyForKey(authURL, kCFURLContentModificationDateKey, &value, &err);
205 require_action(ok && value != NULL, done, _printCFError("authdb: failed to get modification date", err));
206
207 if (CFGetTypeID(value) == CFDateGetTypeID()) {
208 ts = CFDateGetAbsoluteTime(value);
209 }
210
211 old_ts = auth_items_get_double(config, "data_ts");
212
213 // <rdar://problem/17484375> SEED: BUG: Fast User Switching Not Working
214 // After Mavericks => Yosemite upgrade install, the new Yosemite rule "system.login.fus" was missing.
215 // Somehow (probably during install) ts < old_ts, even though that should never happen.
216 // Solution: always import plist and update db when time stamps don't match.
217 // After a successful import, old_ts = ts below.
218 if (ts != old_ts) {
219 LOGV("authdb: %s modified old=%f, new=%f", AUTHDB_DATA, old_ts, ts);
220 CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, authURL, &data, NULL, NULL, (SInt32*)&rc);
221 require_noerr_action(rc, done, LOGE("authdb: failed to load %s", AUTHDB_DATA));
222
223 plist = CFPropertyListCreateWithData(kCFAllocatorDefault, data, kCFPropertyListImmutable, NULL, &err);
224 require_action(err == NULL, done, _printCFError("authdb: failed to read plist", err));
225
226 if (authdb_import_plist(dbconn, plist, true)) {
227 LOGD("authdb: updating data_ts");
228 auth_items_t update = auth_items_create();
229 auth_items_set_double(update, "data_ts", ts);
230 authdb_set_key_value(dbconn, "config", update);
231 CFReleaseSafe(update);
232 }
233 }
234
235 done:
236 CFReleaseSafe(value);
237 CFReleaseSafe(authURL);
238 CFReleaseSafe(plist);
239 CFReleaseSafe(err);
240 CFReleaseSafe(data);
241 }
242
243 static bool _truncate_db(authdb_connection_t dbconn)
244 {
245 int32_t rc = SQLITE_ERROR;
246 int32_t flags = SQLITE_TRUNCATE_JOURNALMODE_WAL | SQLITE_TRUNCATE_AUTOVACUUM_FULL;
247 rc = sqlite3_file_control(dbconn->handle, NULL, SQLITE_TRUNCATE_DATABASE, &flags);
248 if (rc != SQLITE_OK) {
249 LOGV("Failed to delete db handle! SQLite error %i.\n", rc);
250 if (rc == SQLITE_IOERR) {
251 // Unable to recover successfully if we can't truncate
252 abort();
253 }
254 }
255
256 return rc == SQLITE_OK;
257 }
258
259 static void _handle_corrupt_db(authdb_connection_t dbconn)
260 {
261 int32_t rc = SQLITE_ERROR;
262 char buf[PATH_MAX+1];
263 sqlite3 *corrupt_db = NULL;
264
265 snprintf(buf, sizeof(buf), "%s-corrupt", dbconn->db->db_path);
266 if (sqlite3_open(buf, &corrupt_db) == SQLITE_OK) {
267
268 int on = 1;
269 sqlite3_file_control(corrupt_db, 0, SQLITE_FCNTL_PERSIST_WAL, &on);
270
271 rc = sqlite3_file_control(corrupt_db, NULL, SQLITE_REPLACE_DATABASE, (void *)dbconn->handle);
272 if (SQLITE_OK == rc) {
273 LOGE("Database at path %s is corrupt. Copying it to %s for further investigation.", dbconn->db->db_path, buf);
274 } else {
275 LOGE("Tried to copy corrupt database at path %s, but we failed with SQLite error %i.", dbconn->db->db_path, rc);
276 }
277 }
278
279 // SQLite documentation says:
280 // Whether or not an error occurs when it is opened, resources associated with the database connection handle should be released by passing it to sqlite3_close() when it is no longer required.
281 if (corrupt_db)
282 sqlite3_close(corrupt_db);
283
284 _truncate_db(dbconn);
285 }
286
287 static int32_t _db_maintenance(authdb_connection_t dbconn)
288 {
289 __block int32_t s3e = SQLITE_OK;
290 __block auth_items_t config = NULL;
291
292 authdb_transaction(dbconn, AuthDBTransactionNormal, ^bool(void) {
293
294 authdb_get_key_value(dbconn, "config", true, &config);
295
296 // We don't have a config table
297 if (NULL == config) {
298 LOGV("authdb: initializing database");
299 s3e = _db_upgrade_from_version(dbconn, 0);
300 require_noerr_action(s3e, done, LOGE("authdb: failed to initialize database %i", s3e));
301
302 s3e = authdb_get_key_value(dbconn, "config", true, &config);
303 require_noerr_action(s3e, done, LOGE("authdb: failed to get config %i", s3e));
304 }
305
306 int64_t currentVersion = auth_items_get_int64(config, "version");
307 LOGV("authdb: current db ver=%lli", currentVersion);
308 if (currentVersion < AUTHDB_VERSION) {
309 LOGV("authdb: upgrading schema");
310 s3e = _db_upgrade_from_version(dbconn, (int32_t)currentVersion);
311
312 auth_items_set_int64(config, "version", AUTHDB_VERSION);
313 authdb_set_key_value(dbconn, "config", config);
314 }
315
316 done:
317 return true;
318 });
319
320 CFReleaseSafe(config);
321 return s3e;
322 }
323
324 //static void unlock_notify_cb(void **apArg, int nArg AUTH_UNUSED){
325 // dispatch_semaphore_t semaphore = (dispatch_semaphore_t)apArg[0];
326 // dispatch_semaphore_signal(semaphore);
327 //}
328 //
329 //static int32_t _wait_for_unlock_notify(authdb_connection_t dbconn, sqlite3_stmt * stmt)
330 //{
331 // int32_t rc;
332 // dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
333 //
334 // rc = sqlite3_unlock_notify(dbconn->handle, unlock_notify_cb, semaphore);
335 // require(!rc, done);
336 //
337 // if (dispatch_semaphore_wait(semaphore, 5*NSEC_PER_SEC) != 0) {
338 // LOGV("authdb: timeout occurred!");
339 // sqlite3_unlock_notify(dbconn->handle, NULL, NULL);
340 // rc = SQLITE_LOCKED;
341 // } else if (stmt){
342 // sqlite3_reset(stmt);
343 // }
344 //
345 //done:
346 // dispatch_release(semaphore);
347 // return rc;
348 //}
349
350 static bool _is_busy(int32_t rc)
351 {
352 return SQLITE_BUSY == rc || SQLITE_LOCKED == rc;
353 }
354
355 static void _checkResult(authdb_connection_t dbconn, int32_t rc, const char * fn_name, sqlite3_stmt * stmt, const bool skip_maintenance)
356 {
357 bool isCorrupt = (SQLITE_CORRUPT == rc) || (SQLITE_NOTADB == rc) || (SQLITE_IOERR == rc);
358
359 if (isCorrupt) {
360 if (skip_maintenance) {
361 LOGV("authdb: corrupted db, skipping maintenance %s %s", fn_name, sqlite3_errmsg(dbconn->handle));
362 } else {
363 _handle_corrupt_db(dbconn);
364 authdb_maintenance(dbconn);
365 }
366 } else if (SQLITE_CONSTRAINT == rc || SQLITE_READONLY == rc) {
367 if (stmt) {
368 LOGV("authdb: %s %s for %s", fn_name, sqlite3_errmsg(dbconn->handle), sqlite3_sql(stmt));
369 } else {
370 LOGV("authdb: %s %s", fn_name, sqlite3_errmsg(dbconn->handle));
371 }
372 }
373 }
374
375 char * authdb_copy_sql_string(sqlite3_stmt * sql,int32_t col)
376 {
377 char * result = NULL;
378 const char * sql_str = (const char *)sqlite3_column_text(sql, col);
379 if (sql_str) {
380 size_t len = strlen(sql_str) + 1;
381 result = (char*)calloc(1u, len);
382 check(result != NULL);
383
384 strlcpy(result, sql_str, len);
385 }
386 return result;
387 }
388
389 #pragma mark -
390 #pragma mark authdb_t
391
392 static void
393 _authdb_finalize(CFTypeRef value)
394 {
395 authdb_t db = (authdb_t)value;
396
397 CFReleaseSafe(db->connections);
398 dispatch_release(db->queue);
399 free_safe(db->db_path);
400 }
401
402 AUTH_TYPE_INSTANCE(authdb,
403 .init = NULL,
404 .copy = NULL,
405 .finalize = _authdb_finalize,
406 .equal = NULL,
407 .hash = NULL,
408 .copyFormattingDesc = NULL,
409 .copyDebugDesc = NULL
410 );
411
412 static CFTypeID authdb_get_type_id() {
413 static CFTypeID type_id = _kCFRuntimeNotATypeID;
414 static dispatch_once_t onceToken;
415
416 dispatch_once(&onceToken, ^{
417 type_id = _CFRuntimeRegisterClass(&_auth_type_authdb);
418 });
419
420 return type_id;
421 }
422
423 authdb_t
424 authdb_create()
425 {
426 authdb_t db = NULL;
427
428 db = (authdb_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, authdb_get_type_id(), AUTH_CLASS_SIZE(authdb), NULL);
429 require(db != NULL, done);
430
431 db->queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL);
432 db->connections = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL);
433
434 if (getenv("__OSINSTALL_ENVIRONMENT") != NULL) {
435 LOGV("authdb: running from installer");
436 db->db_path = _copy_string("file::memory:?cache=shared");
437 } else {
438 db->db_path = _copy_string(AUTHDB);
439 }
440
441 done:
442 return db;
443 }
444
445 authdb_connection_t authdb_connection_acquire(authdb_t db)
446 {
447 __block authdb_connection_t dbconn = NULL;
448 #if DEBUG
449 static int32_t total = 0;
450 #endif
451 dispatch_sync(db->queue, ^{
452 CFIndex count = CFArrayGetCount(db->connections);
453 if (count) {
454 dbconn = (authdb_connection_t)CFArrayGetValueAtIndex(db->connections, 0);
455 CFArrayRemoveValueAtIndex(db->connections, 0);
456 } else {
457 dbconn = authdb_connection_create(db);
458 #if DEBUG
459 total++;
460 LOGV("authdb: no handles available total: %i", total);
461 #endif
462 }
463 });
464
465 return dbconn;
466 }
467
468 void authdb_connection_release(authdb_connection_t * dbconn)
469 {
470 if (!(*dbconn))
471 return;
472
473 authdb_connection_t tmp = *dbconn;
474 *dbconn = NULL;
475
476 dispatch_async(tmp->db->queue, ^{
477 CFIndex count = CFArrayGetCount(tmp->db->connections);
478 if (count <= AUTHDB_MAX_HANDLES) {
479 CFArrayAppendValue(tmp->db->connections, tmp);
480 } else {
481 LOGD("authdb: freeing extra connection");
482 CFRelease(tmp);
483 }
484 });
485 }
486
487 static bool _db_check_corrupted(authdb_connection_t dbconn)
488 {
489 bool isCorrupted = true;
490 sqlite3_stmt *stmt = NULL;
491 int32_t rc;
492
493 if (!dbconn->handle)
494 return true;
495
496 rc = sqlite3_prepare_v2(dbconn->handle, "PRAGMA integrity_check;", -1, &stmt, NULL);
497 if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) {
498 LOGV("authdb: warning error %i when running integrity check", rc);
499 isCorrupted = false;
500
501 } else if (rc == SQLITE_OK) {
502 rc = sqlite3_step(stmt);
503
504 if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) {
505 LOGV("authdb: warning error %i when running integrity check", rc);
506 isCorrupted = false;
507 } else if (rc == SQLITE_ROW) {
508 const char * result = (const char*)sqlite3_column_text(stmt, 0);
509
510 if (result && strncasecmp(result, "ok", 3) == 0) {
511 isCorrupted = false;
512 }
513 }
514 }
515
516 sqlite3_finalize(stmt);
517 return isCorrupted;
518 }
519
520 bool authdb_maintenance(authdb_connection_t dbconn)
521 {
522 LOGD("authdb: starting maintenance");
523 int32_t rc = SQLITE_ERROR;
524 auth_items_t config = NULL;
525
526 bool isCorrupted = _db_check_corrupted(dbconn);
527 LOGD("authdb: integrity check=%s", isCorrupted ? "fail" : "pass");
528
529 if (isCorrupted) {
530 _handle_corrupt_db(dbconn);
531 }
532
533 if (dbconn->handle == NULL) {
534 dbconn->handle = _create_handle(dbconn->db);
535 }
536
537 require_action(dbconn->handle, done, LOGE("authdb: maintenance cannot open database"));
538
539 _db_maintenance(dbconn);
540
541 rc = authdb_get_key_value(dbconn, "config", true, &config);
542 require_noerr_action(rc, done, LOGV("authdb: maintenance failed %i", rc));
543
544 _db_load_data(dbconn, config);
545
546 done:
547 CFReleaseSafe(config);
548 LOGD("authdb: finished maintenance");
549 return rc == SQLITE_OK;
550 }
551
552 int32_t
553 authdb_exec(authdb_connection_t dbconn, const char * query)
554 {
555 int32_t rc = SQLITE_ERROR;
556 require(query != NULL, done);
557
558 rc = _sqlite3_exec(dbconn->handle, query);
559 _checkResult(dbconn, rc, __FUNCTION__, NULL, false);
560
561 done:
562 return rc;
563 }
564
565 static int32_t _prepare(authdb_connection_t dbconn, const char * sql, const bool skip_maintenance, sqlite3_stmt ** out_stmt)
566 {
567 int32_t rc;
568 sqlite3_stmt * stmt = NULL;
569
570 require_action(sql != NULL, done, rc = SQLITE_ERROR);
571 require_action(out_stmt != NULL, done, rc = SQLITE_ERROR);
572
573 rc = sqlite3_prepare_v2(dbconn->handle, sql, -1, &stmt, NULL);
574 require_noerr_action(rc, done, LOGV("authdb: prepare (%i) %s", rc, sqlite3_errmsg(dbconn->handle)));
575
576 *out_stmt = stmt;
577
578 done:
579 _checkResult(dbconn, rc, __FUNCTION__, stmt, skip_maintenance);
580 return rc;
581 }
582
583 static void _parseItemsAtIndex(sqlite3_stmt * stmt, int32_t col, auth_items_t items, const char * key)
584 {
585 switch (sqlite3_column_type(stmt, col)) {
586 case SQLITE_FLOAT:
587 auth_items_set_double(items, key, sqlite3_column_double(stmt, col));
588 break;
589 case SQLITE_INTEGER:
590 auth_items_set_int64(items, key, sqlite3_column_int64(stmt, col));
591 break;
592 case SQLITE_BLOB:
593 auth_items_set_data(items,
594 key,
595 sqlite3_column_blob(stmt, col),
596 (size_t)sqlite3_column_bytes(stmt, col));
597 break;
598 case SQLITE_NULL:
599 break;
600 case SQLITE_TEXT:
601 default:
602 auth_items_set_string(items, key, (const char *)sqlite3_column_text(stmt, col));
603 break;
604 }
605
606 // LOGD("authdb: col=%s, val=%s, type=%i", sqlite3_column_name(stmt, col), sqlite3_column_text(stmt, col), sqlite3_column_type(stmt,col));
607 }
608
609 static int32_t _bindItemsAtIndex(sqlite3_stmt * stmt, int col, auth_items_t items, const char * key)
610 {
611 int32_t rc;
612 switch (auth_items_get_type(items, key)) {
613 case AI_TYPE_INT:
614 rc = sqlite3_bind_int64(stmt, col, auth_items_get_int(items, key));
615 break;
616 case AI_TYPE_UINT:
617 rc = sqlite3_bind_int64(stmt, col, auth_items_get_uint(items, key));
618 break;
619 case AI_TYPE_INT64:
620 rc = sqlite3_bind_int64(stmt, col, auth_items_get_int64(items, key));
621 break;
622 case AI_TYPE_UINT64:
623 rc = sqlite3_bind_int64(stmt, col, (int64_t)auth_items_get_uint64(items, key));
624 break;
625 case AI_TYPE_DOUBLE:
626 rc = sqlite3_bind_double(stmt, col, auth_items_get_double(items, key));
627 break;
628 case AI_TYPE_BOOL:
629 rc = sqlite3_bind_int64(stmt, col, auth_items_get_bool(items, key));
630 break;
631 case AI_TYPE_DATA:
632 {
633 size_t blobLen = 0;
634 const void * blob = auth_items_get_data(items, key, &blobLen);
635 rc = sqlite3_bind_blob(stmt, col, blob, (int32_t)blobLen, NULL);
636 }
637 break;
638 case AI_TYPE_STRING:
639 rc = sqlite3_bind_text(stmt, col, auth_items_get_string(items, key), -1, NULL);
640 break;
641 default:
642 rc = sqlite3_bind_null(stmt, col);
643 break;
644 }
645 if (rc != SQLITE_OK) {
646 LOGV("authdb: auth_items bind failed (%i)", rc);
647 }
648 return rc;
649 }
650
651 int32_t authdb_get_key_value(authdb_connection_t dbconn, const char * table, const bool skip_maintenance, auth_items_t * out_items)
652 {
653 int32_t rc = SQLITE_ERROR;
654 char * query = NULL;
655 sqlite3_stmt * stmt = NULL;
656 auth_items_t items = NULL;
657
658 require(table != NULL, done);
659 require(out_items != NULL, done);
660
661 asprintf(&query, "SELECT * FROM %s", table);
662
663 rc = _prepare(dbconn, query, skip_maintenance, &stmt);
664 require_noerr(rc, done);
665
666 items = auth_items_create();
667 while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
668 switch (rc) {
669 case SQLITE_ROW:
670 _parseItemsAtIndex(stmt, 1, items, (const char*)sqlite3_column_text(stmt, 0));
671 break;
672 default:
673 _checkResult(dbconn, rc, __FUNCTION__, stmt, skip_maintenance);
674 if (_is_busy(rc)) {
675 sleep(AUTHDB_BUSY_DELAY);
676 } else {
677 require_noerr_action(rc, done, LOGV("authdb: get_key_value (%i) %s", rc, sqlite3_errmsg(dbconn->handle)));
678 }
679 break;
680 }
681 }
682
683 rc = SQLITE_OK;
684 CFRetain(items);
685 *out_items = items;
686
687 done:
688 CFReleaseSafe(items);
689 free_safe(query);
690 sqlite3_finalize(stmt);
691 return rc;
692 }
693
694 int32_t authdb_set_key_value(authdb_connection_t dbconn, const char * table, auth_items_t items)
695 {
696 __block int32_t rc = SQLITE_ERROR;
697 char * query = NULL;
698 sqlite3_stmt * stmt = NULL;
699
700 require(table != NULL, done);
701 require(items != NULL, done);
702
703 asprintf(&query, "INSERT OR REPLACE INTO %s VALUES (?,?)", table);
704
705 rc = _prepare(dbconn, query, false, &stmt);
706 require_noerr(rc, done);
707
708 auth_items_iterate(items, ^bool(const char *key) {
709 sqlite3_reset(stmt);
710 _checkResult(dbconn, rc, __FUNCTION__, stmt, false);
711
712 sqlite3_bind_text(stmt, 1, key, -1, NULL);
713 _bindItemsAtIndex(stmt, 2, items, key);
714
715 rc = sqlite3_step(stmt);
716 if (rc != SQLITE_DONE) {
717 _checkResult(dbconn, rc, __FUNCTION__, stmt, false);
718 LOGV("authdb: set_key_value, step (%i) %s", rc, sqlite3_errmsg(dbconn->handle));
719 }
720
721 return true;
722 });
723
724 done:
725 free_safe(query);
726 sqlite3_finalize(stmt);
727 return rc;
728 }
729
730 static int32_t _begin_transaction_type(authdb_connection_t dbconn, AuthDBTransactionType type)
731 {
732 int32_t result = SQLITE_ERROR;
733
734 const char * query = NULL;
735 switch (type) {
736 case AuthDBTransactionImmediate:
737 query = "BEGIN IMMEDATE;";
738 break;
739 case AuthDBTransactionExclusive:
740 query = "BEGIN EXCLUSIVE;";
741 break;
742 case AuthDBTransactionNormal:
743 query = "BEGIN;";
744 break;
745 default:
746 break;
747 }
748
749 result = SQLITE_OK;
750
751 if (query != NULL && sqlite3_get_autocommit(dbconn->handle) != 0) {
752 result = _sqlite3_exec(dbconn->handle, query);
753 }
754
755 return result;
756 }
757
758 static int32_t _end_transaction(authdb_connection_t dbconn, bool commit)
759 {
760 if (commit) {
761 return _sqlite3_exec(dbconn->handle, "END;");
762 } else {
763 return _sqlite3_exec(dbconn->handle, "ROLLBACK;");
764 }
765 }
766
767 bool authdb_transaction(authdb_connection_t dbconn, AuthDBTransactionType type, bool (^t)(void))
768 {
769 int32_t result = SQLITE_ERROR;
770 bool commit = false;
771
772 result = _begin_transaction_type(dbconn, type);
773 require_action(result == SQLITE_OK, done, LOGV("authdb: transaction begin failed %i", result));
774
775 commit = t();
776
777 result = _end_transaction(dbconn, commit);
778 require_action(result == SQLITE_OK, done, commit = false; LOGV("authdb: transaction end failed %i", result));
779
780 done:
781 return commit;
782 }
783
784 bool authdb_step(authdb_connection_t dbconn, const char * sql, void (^bind_stmt)(sqlite3_stmt*), authdb_iterator_t iter)
785 {
786 bool result = false;
787 sqlite3_stmt * stmt = NULL;
788 int32_t rc = SQLITE_ERROR;
789
790 require_action(sql != NULL, done, rc = SQLITE_ERROR);
791
792 rc = _prepare(dbconn, sql, false, &stmt);
793 require_noerr(rc, done);
794
795 if (bind_stmt) {
796 bind_stmt(stmt);
797 }
798
799 int32_t count = sqlite3_column_count(stmt);
800
801 auth_items_t items = NULL;
802 while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) {
803 switch (rc) {
804 case SQLITE_ROW:
805 {
806 if (iter) {
807 items = auth_items_create();
808 for (int i = 0; i < count; i++) {
809 _parseItemsAtIndex(stmt, i, items, sqlite3_column_name(stmt, i));
810 }
811 result = iter(items);
812 CFReleaseNull(items);
813 if (!result) {
814 goto done;
815 }
816 }
817 }
818 break;
819 default:
820 if (_is_busy(rc)) {
821 LOGV("authdb: %s", sqlite3_errmsg(dbconn->handle));
822 sleep(AUTHDB_BUSY_DELAY);
823 sqlite3_reset(stmt);
824 } else {
825 require_noerr_action(rc, done, LOGV("authdb: step (%i) %s", rc, sqlite3_errmsg(dbconn->handle)));
826 }
827 break;
828 }
829 }
830
831 done:
832 _checkResult(dbconn, rc, __FUNCTION__, stmt, false);
833 sqlite3_finalize(stmt);
834 return rc == SQLITE_DONE;
835 }
836
837 void authdb_checkpoint(authdb_connection_t dbconn)
838 {
839 int32_t rc = sqlite3_wal_checkpoint(dbconn->handle, NULL);
840 if (rc != SQLITE_OK) {
841 LOGV("authdb: checkpoit failed %i", rc);
842 }
843 }
844
845 static CFMutableArrayRef
846 _copy_rules_dict(RuleType type, CFDictionaryRef plist, authdb_connection_t dbconn)
847 {
848 CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
849 require(result != NULL, done);
850
851 _cf_dictionary_iterate(plist, ^bool(CFTypeRef key, CFTypeRef value) {
852 if (CFGetTypeID(key) != CFStringGetTypeID()) {
853 return true;
854 }
855
856 if (CFGetTypeID(value) != CFDictionaryGetTypeID()) {
857 return true;
858 }
859
860 rule_t rule = rule_create_with_plist(type, key, value, dbconn);
861 if (rule) {
862 CFArrayAppendValue(result, rule);
863 CFReleaseSafe(rule);
864 }
865
866 return true;
867 });
868
869 done:
870 return result;
871 }
872
873 static void
874 _import_rules(authdb_connection_t dbconn, CFMutableArrayRef rules, bool version_check, CFAbsoluteTime now)
875 {
876 CFMutableArrayRef notcommited = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks);
877 CFIndex count = CFArrayGetCount(rules);
878
879 for (CFIndex i = 0; i < count; i++) {
880 rule_t rule = (rule_t)CFArrayGetValueAtIndex(rules, i);
881
882 bool update = false;
883 if (version_check) {
884 if (rule_get_id(rule) != 0) { // rule already exists see if we need to update
885 rule_t current = rule_create_with_string(rule_get_name(rule), dbconn);
886 if (rule_get_version(rule) > rule_get_version(current)) {
887 update = true;
888 }
889 CFReleaseSafe(current);
890
891 if (!update) {
892 continue;
893 }
894 }
895 }
896
897 __block bool delayCommit = false;
898
899 switch (rule_get_type(rule)) {
900 case RT_RULE:
901 rule_delegates_iterator(rule, ^bool(rule_t delegate) {
902 if (rule_get_id(delegate) == 0) {
903 // fetch the rule from the database if it was previously committed
904 rule_sql_fetch(delegate, dbconn);
905 }
906 if (rule_get_id(delegate) == 0) {
907 LOGD("authdb: delaying %s waiting for delegate %s", rule_get_name(rule), rule_get_name(delegate));
908 delayCommit = true;
909 return false;
910 }
911 return true;
912 });
913 break;
914 default:
915 break;
916 }
917
918 if (!delayCommit) {
919 bool success = rule_sql_commit(rule, dbconn, now, NULL);
920 LOGV("authdb: %s %s %s %s",
921 update ? "updating" : "importing",
922 rule_get_type(rule) == RT_RULE ? "rule" : "right",
923 rule_get_name(rule), success ? "success" : "FAIL");
924 if (!success) {
925 CFArrayAppendValue(notcommited, rule);
926 }
927 } else {
928 CFArrayAppendValue(notcommited, rule);
929 }
930 }
931 CFArrayRemoveAllValues(rules);
932 CFArrayAppendArray(rules, notcommited, CFRangeMake(0, CFArrayGetCount(notcommited)));
933 CFReleaseSafe(notcommited);
934 }
935
936 bool
937 authdb_import_plist(authdb_connection_t dbconn, CFDictionaryRef plist, bool version_check)
938 {
939 bool result = false;
940
941 LOGV("authdb: starting import");
942
943 CFAbsoluteTime now = CFAbsoluteTimeGetCurrent();
944 CFMutableArrayRef rights = NULL;
945 CFMutableArrayRef rules = NULL;
946 require(plist != NULL, done);
947
948 CFTypeRef rightsDict = CFDictionaryGetValue(plist, CFSTR("rights"));
949 if (rightsDict && CFGetTypeID(rightsDict) == CFDictionaryGetTypeID()) {
950 rights = _copy_rules_dict(RT_RIGHT, rightsDict, dbconn);
951 }
952
953 CFTypeRef rulesDict = CFDictionaryGetValue(plist, CFSTR("rules"));
954 if (rulesDict && CFGetTypeID(rulesDict) == CFDictionaryGetTypeID()) {
955 rules = _copy_rules_dict(RT_RULE, rulesDict, dbconn);
956 }
957
958 LOGV("authdb: rights = %li", CFArrayGetCount(rights));
959 LOGV("authdb: rules = %li", CFArrayGetCount(rules));
960
961 CFIndex count;
962 // first pass import base rules without delegations
963 // remaining import rules that delegate to other rules
964 // loop upto 3 times to commit dependent rules first
965 for (int32_t j = 0; j < 3; j++) {
966 count = CFArrayGetCount(rules);
967 if (!count)
968 break;
969
970 _import_rules(dbconn, rules, version_check, now);
971 }
972
973 _import_rules(dbconn, rights, version_check, now);
974
975 if (CFArrayGetCount(rights) == 0) {
976 result = true;
977 }
978
979 authdb_checkpoint(dbconn);
980
981 done:
982 CFReleaseSafe(rights);
983 CFReleaseSafe(rules);
984
985 LOGV("authdb: finished import, %s", result ? "succeeded" : "failed");
986
987 return result;
988 }
989
990 #pragma mark -
991 #pragma mark authdb_connection_t
992
993 static bool _sql_profile_enabled(void)
994 {
995 static bool profile_enabled = false;
996
997 #if DEBUG
998 static dispatch_once_t onceToken;
999
1000 //sudo defaults write /Library/Preferences/com.apple.security.auth profile -bool true
1001 dispatch_once(&onceToken, ^{
1002 CFTypeRef profile = (CFNumberRef)CFPreferencesCopyValue(CFSTR("profile"), CFSTR(SECURITY_AUTH_NAME), kCFPreferencesAnyUser, kCFPreferencesCurrentHost);
1003
1004 if (profile && CFGetTypeID(profile) == CFBooleanGetTypeID()) {
1005 profile_enabled = CFBooleanGetValue((CFBooleanRef)profile);
1006 }
1007
1008 LOGV("authdb: sql profile: %s", profile_enabled ? "enabled" : "disabled");
1009
1010 CFReleaseSafe(profile);
1011 });
1012 #endif
1013
1014 return profile_enabled;
1015 }
1016
1017 static void _profile(void *context AUTH_UNUSED, const char *sql, sqlite3_uint64 ns) {
1018 LOGV("==\nauthdb: %s\nTime: %llu ms\n", sql, ns >> 20);
1019 }
1020
1021 static sqlite3 * _create_handle(authdb_t db)
1022 {
1023 bool dbcreated = false;
1024 sqlite3 * handle = NULL;
1025 int32_t rc = sqlite3_open_v2(db->db_path, &handle, SQLITE_OPEN_READWRITE, NULL);
1026
1027 if (rc != SQLITE_OK) {
1028 LOGE("authdb: open %s (%i) %s", db->db_path, rc, handle ? sqlite3_errmsg(handle) : "no memory for handle");
1029 if (handle) {
1030 sqlite3_close(handle);
1031 }
1032 char * tmp = dirname(db->db_path);
1033 if (tmp) {
1034 mkpath_np(tmp, 0700);
1035 }
1036 rc = sqlite3_open_v2(db->db_path, &handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL);
1037 dbcreated = true;
1038
1039 if (rc != SQLITE_OK) {
1040 LOGE("authdb: create %s (%i) %s", db->db_path, rc, handle ? sqlite3_errmsg(handle) : "no memory for handle");
1041 if (handle) {
1042 sqlite3_close(handle);
1043 handle = NULL;
1044 }
1045 goto done;
1046 }
1047 }
1048
1049 if (_sql_profile_enabled()) {
1050 sqlite3_profile(handle, _profile, NULL);
1051 }
1052
1053 _sqlite3_exec(handle, "PRAGMA foreign_keys = ON");
1054 _sqlite3_exec(handle, "PRAGMA temp_store = MEMORY");
1055
1056 if (dbcreated) {
1057 _sqlite3_exec(handle, "PRAGMA auto_vacuum = FULL");
1058 _sqlite3_exec(handle, "PRAGMA journal_mode = WAL");
1059
1060 int on = 1;
1061 sqlite3_file_control(handle, 0, SQLITE_FCNTL_PERSIST_WAL, &on);
1062
1063 chmod(db->db_path, S_IRUSR | S_IWUSR);
1064 }
1065
1066 done:
1067 return handle;
1068 }
1069
1070 static void
1071 _authdb_connection_finalize(CFTypeRef value)
1072 {
1073 authdb_connection_t dbconn = (authdb_connection_t)value;
1074
1075 if (dbconn->handle) {
1076 sqlite3_close(dbconn->handle);
1077 }
1078 CFReleaseSafe(dbconn->db);
1079 }
1080
1081 AUTH_TYPE_INSTANCE(authdb_connection,
1082 .init = NULL,
1083 .copy = NULL,
1084 .finalize = _authdb_connection_finalize,
1085 .equal = NULL,
1086 .hash = NULL,
1087 .copyFormattingDesc = NULL,
1088 .copyDebugDesc = NULL
1089 );
1090
1091 static CFTypeID authdb_connection_get_type_id() {
1092 static CFTypeID type_id = _kCFRuntimeNotATypeID;
1093 static dispatch_once_t onceToken;
1094
1095 dispatch_once(&onceToken, ^{
1096 type_id = _CFRuntimeRegisterClass(&_auth_type_authdb_connection);
1097 });
1098
1099 return type_id;
1100 }
1101
1102 authdb_connection_t
1103 authdb_connection_create(authdb_t db)
1104 {
1105 authdb_connection_t dbconn = NULL;
1106
1107 dbconn = (authdb_connection_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, authdb_connection_get_type_id(), AUTH_CLASS_SIZE(authdb_connection), NULL);
1108 require(dbconn != NULL, done);
1109
1110 dbconn->db = (authdb_t)CFRetain(db);
1111 dbconn->handle = _create_handle(dbconn->db);
1112
1113 done:
1114 return dbconn;
1115 }