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