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