X-Git-Url: https://git.saurik.com/apple/security.git/blobdiff_plain/80e2389990082500d76eb566d4946be3e786c3ef..d8f41ccd20de16f8ebe2ccc84d47bf1cb2b26bbb:/authd/authdb.c?ds=inline diff --git a/authd/authdb.c b/authd/authdb.c deleted file mode 100644 index d6952d01..00000000 --- a/authd/authdb.c +++ /dev/null @@ -1,1073 +0,0 @@ -/* Copyright (c) 2012 Apple Inc. All rights reserved. */ - -#include "authdb.h" -#include "mechanism.h" -#include "rule.h" -#include "debugging.h" -#include "authitems.h" -#include "server.h" - -#include -#include -#include -#include "rule.h" -#include "authutilities.h" -#include -#include - -#define AUTHDB "/var/db/auth.db" -#define AUTHDB_DATA "/System/Library/Security/authorization.plist" - -#define AUTH_STR(x) #x -#define AUTH_STRINGIFY(x) AUTH_STR(x) - -#define AUTHDB_VERSION 1 -#define AUTHDB_VERSION_STRING AUTH_STRINGIFY(AUTHDB_VERSION) - -#define AUTHDB_BUSY_DELAY 1 -#define AUTHDB_MAX_HANDLES 3 - -struct _authdb_connection_s { - __AUTH_BASE_STRUCT_HEADER__; - - authdb_t db; - sqlite3 * handle; -}; - -struct _authdb_s { - __AUTH_BASE_STRUCT_HEADER__; - - char * db_path; - dispatch_queue_t queue; - CFMutableArrayRef connections; -}; - -static const char * const authdb_upgrade_sql[] = { - /* 0 */ - /* current scheme */ - "CREATE TABLE delegates_map (" - "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE," - "d_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE," - "ord INTEGER NOT NULL" - ");" - "CREATE INDEX d_map_d_id ON delegates_map(d_id);" - "CREATE INDEX d_map_r_id ON delegates_map(r_id);" - "CREATE INDEX d_map_r_id_ord ON delegates_map (r_id, ord);" - "CREATE TABLE mechanisms (" - "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE," - "plugin TEXT NOT NULL," - "param TEXT NOT NULL," - "privileged INTEGER CHECK (privileged = 0 OR privileged = 1) NOT NULL DEFAULT (0)" - ");" - "CREATE UNIQUE INDEX mechanisms_lookup ON mechanisms (plugin,param,privileged);" - "CREATE TABLE mechanisms_map (" - "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE," - "m_id INTEGER NOT NULL REFERENCES mechanisms(id) ON DELETE CASCADE," - "ord INTEGER NOT NULL" - ");" - "CREATE INDEX m_map_m_id ON mechanisms_map (m_id);" - "CREATE INDEX m_map_r_id ON mechanisms_map (r_id);" - "CREATE INDEX m_map_r_id_ord ON mechanisms_map (r_id, ord);" - "CREATE TABLE rules (" - "id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE," - "name TEXT NOT NULL UNIQUE," - "type INTEGER CHECK (type = 1 OR type = 2) NOT NULL," - "class INTEGER CHECK (class > 0)," - "'group' TEXT," - "kofn INTEGER," - "timeout INTEGER," - "flags INTEGER," - "tries INTEGER," - "version INTEGER NOT NULL DEFAULT (0)," - "created REAL NOT NULL DEFAULT (0)," - "modified REAL NOT NULL DEFAULT (0)," - "hash BLOB," - "identifier TEXT," - "requirement BLOB," - "comment TEXT" - ");" - "CREATE INDEX a_type ON rules (type);" - "CREATE TABLE config (" - "'key' TEXT PRIMARY KEY NOT NULL UNIQUE," - "value" - ");" - "CREATE TABLE prompts (" - "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE," - "lang TEXT NOT NULL," - "value TEXT NOT NULL" - ");" - "CREATE INDEX p_r_id ON prompts(r_id);" - "CREATE TABLE buttons (" - "r_id INTEGER NOT NULL REFERENCES rules(id) ON DELETE CASCADE," - "lang TEXT NOT NULL," - "value TEXT NOT NULL" - ");" - "CREATE INDEX b_r_id ON buttons(r_id);" - "INSERT INTO config VALUES('version', "AUTHDB_VERSION_STRING");" -}; - -static int32_t -_sqlite3_exec(sqlite3 * handle, const char * query) -{ - int32_t rc = SQLITE_ERROR; - require(query != NULL, done); - - char * errmsg = NULL; - rc = sqlite3_exec(handle, query, NULL, NULL, &errmsg); - if (errmsg) { - LOGE("authdb: exec, (%i) %s", rc, errmsg); - sqlite3_free(errmsg); - } - -done: - return rc; -} - -struct _db_upgrade_stages { - int pre; - int main; - int post; -}; - -static struct _db_upgrade_stages auth_upgrade_script[] = { - { .pre = -1, .main = 0, .post = -1 } // Create version AUTHDB_VERSION databse. -}; - -static int32_t _db_run_script(authdb_connection_t dbconn, int number) -{ - int32_t s3e; - - /* Script -1 == skip this step. */ - if (number < 0) - return SQLITE_OK; - - /* If we are attempting to run a script we don't have, fail. */ - if ((size_t)number >= sizeof(authdb_upgrade_sql) / sizeof(char*)) - return SQLITE_CORRUPT; - - s3e = _sqlite3_exec(dbconn->handle, authdb_upgrade_sql[number]); - - return s3e; -} - -static int32_t _db_upgrade_from_version(authdb_connection_t dbconn, int32_t version) -{ - int32_t s3e; - - /* If we are attempting to upgrade to a version greater than what we have - an upgrade script for, fail. */ - if (version < 0 || - (size_t)version >= sizeof(auth_upgrade_script) / sizeof(struct _db_upgrade_stages)) - return SQLITE_CORRUPT; - - struct _db_upgrade_stages *script = &auth_upgrade_script[version]; - s3e = _db_run_script(dbconn, script->pre); - if (s3e == SQLITE_OK) - s3e = _db_run_script(dbconn, script->main); - if (s3e == SQLITE_OK) - s3e = _db_run_script(dbconn, script->post); - - return s3e; -} - -static void _printCFError(const char * errmsg, CFErrorRef err) -{ - CFStringRef errString = NULL; - errString = CFErrorCopyDescription(err); - char * tmp = _copy_cf_string(errString, NULL); - LOGV("%s, %s", errmsg, tmp); - free_safe(tmp); - CFReleaseSafe(errString); -} - -static void _db_load_data(authdb_connection_t dbconn, auth_items_t config) -{ - CFURLRef authURL = NULL; - CFPropertyListRef plist = NULL; - CFDataRef data = NULL; - int32_t rc = 0; - CFErrorRef err = NULL; - CFTypeRef value = NULL; - CFAbsoluteTime ts = 0; - CFAbsoluteTime old_ts = 0; - - authURL = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, CFSTR(AUTHDB_DATA), kCFURLPOSIXPathStyle, false); - require_action(authURL != NULL, done, LOGE("authdb: file not found %s", AUTHDB_DATA)); - - CFURLCopyResourcePropertyForKey(authURL, kCFURLContentModificationDateKey, &value, &err); - require_action(err == NULL, done, _printCFError("authdb: failed to get modification date", err)); - - if (CFGetTypeID(value) == CFDateGetTypeID()) { - ts = CFDateGetAbsoluteTime(value); - } - - old_ts = auth_items_get_double(config, "data_ts"); - if (ts > old_ts) { - LOGV("authdb: %s modified old=%f, new=%f", AUTHDB_DATA, old_ts, ts); - CFURLCreateDataAndPropertiesFromResource(kCFAllocatorDefault, authURL, &data, NULL, NULL, (SInt32*)&rc); - require_noerr_action(rc, done, LOGE("authdb: failed to load %s", AUTHDB_DATA)); - - plist = CFPropertyListCreateWithData(kCFAllocatorDefault, data, kCFPropertyListImmutable, NULL, &err); - require_action(err == NULL, done, _printCFError("authdb: failed to read plist", err)); - - if (authdb_import_plist(dbconn, plist, true)) { - LOGD("authdb: updating data_ts"); - auth_items_t update = auth_items_create(); - auth_items_set_double(update, "data_ts", ts); - authdb_set_key_value(dbconn, "config", update); - CFReleaseSafe(update); - } - } - -done: - CFReleaseSafe(value); - CFReleaseSafe(authURL); - CFReleaseSafe(plist); - CFReleaseSafe(err); - CFReleaseSafe(data); -} - -static bool _truncate_db(authdb_connection_t dbconn) -{ - int32_t rc = SQLITE_ERROR; - int32_t flags = SQLITE_TRUNCATE_JOURNALMODE_WAL | SQLITE_TRUNCATE_AUTOVACUUM_FULL; - rc = sqlite3_file_control(dbconn->handle, NULL, SQLITE_TRUNCATE_DATABASE, &flags); - if (rc != SQLITE_OK) { - LOGV("Failed to delete db handle! SQLite error %i.\n", rc); - if (rc == SQLITE_IOERR) { - // Unable to recover successfully if we can't truncate - abort(); - } - } - - return rc == SQLITE_OK; -} - -static void _handle_corrupt_db(authdb_connection_t dbconn) -{ - int32_t rc = SQLITE_ERROR; - char buf[PATH_MAX+1]; - sqlite3 *corrupt_db = NULL; - - snprintf(buf, sizeof(buf), "%s-corrupt", dbconn->db->db_path); - if (sqlite3_open(buf, &corrupt_db) == SQLITE_OK) { - - int on = 1; - sqlite3_file_control(corrupt_db, 0, SQLITE_FCNTL_PERSIST_WAL, &on); - - rc = sqlite3_file_control(corrupt_db, NULL, SQLITE_REPLACE_DATABASE, (void *)dbconn->handle); - if (SQLITE_OK == rc) { - LOGE("Database at path %s is corrupt. Copying it to %s for further investigation.", dbconn->db->db_path, buf); - } else { - LOGE("Tried to copy corrupt database at path %s, but we failed with SQLite error %i.", dbconn->db->db_path, rc); - } - sqlite3_close(corrupt_db); - } - - _truncate_db(dbconn); -} - -static int32_t _db_maintenance(authdb_connection_t dbconn) -{ - __block int32_t s3e = SQLITE_OK; - __block auth_items_t config = NULL; - - authdb_transaction(dbconn, AuthDBTransactionNormal, ^bool(void) { - - authdb_get_key_value(dbconn, "config", &config); - - // We don't have a config table - if (NULL == config) { - LOGV("authdb: initializing database"); - s3e = _db_upgrade_from_version(dbconn, 0); - require_noerr_action(s3e, done, LOGE("authdb: failed to initialize database %i", s3e)); - - s3e = authdb_get_key_value(dbconn, "config", &config); - require_noerr_action(s3e, done, LOGE("authdb: failed to get config %i", s3e)); - } - - int64_t currentVersion = auth_items_get_int64(config, "version"); - LOGV("authdb: current db ver=%lli", currentVersion); - if (currentVersion < AUTHDB_VERSION) { - LOGV("authdb: upgrading schema"); - s3e = _db_upgrade_from_version(dbconn, (int32_t)currentVersion); - - auth_items_set_int64(config, "version", AUTHDB_VERSION); - authdb_set_key_value(dbconn, "config", config); - } - - done: - return true; - }); - - CFReleaseSafe(config); - return s3e; -} - -//static void unlock_notify_cb(void **apArg, int nArg AUTH_UNUSED){ -// dispatch_semaphore_t semaphore = (dispatch_semaphore_t)apArg[0]; -// dispatch_semaphore_signal(semaphore); -//} -// -//static int32_t _wait_for_unlock_notify(authdb_connection_t dbconn, sqlite3_stmt * stmt) -//{ -// int32_t rc; -// dispatch_semaphore_t semaphore = dispatch_semaphore_create(0); -// -// rc = sqlite3_unlock_notify(dbconn->handle, unlock_notify_cb, semaphore); -// require(!rc, done); -// -// if (dispatch_semaphore_wait(semaphore, 5*NSEC_PER_SEC) != 0) { -// LOGV("authdb: timeout occurred!"); -// sqlite3_unlock_notify(dbconn->handle, NULL, NULL); -// rc = SQLITE_LOCKED; -// } else if (stmt){ -// sqlite3_reset(stmt); -// } -// -//done: -// dispatch_release(semaphore); -// return rc; -//} - -static bool _is_busy(int32_t rc) -{ - return SQLITE_BUSY == rc || SQLITE_LOCKED == rc; -} - -static void _checkResult(authdb_connection_t dbconn, int32_t rc, const char * fn_name, sqlite3_stmt * stmt) -{ - bool isCorrupt = (SQLITE_CORRUPT == rc) || (SQLITE_NOTADB == rc) || (SQLITE_IOERR == rc); - - if (isCorrupt) { - _handle_corrupt_db(dbconn); - authdb_maintenance(dbconn); - } else if (SQLITE_CONSTRAINT == rc || SQLITE_READONLY == rc) { - if (stmt) { - LOGV("authdb: %s %s for %s", fn_name, sqlite3_errmsg(dbconn->handle), sqlite3_sql(stmt)); - } else { - LOGV("authdb: %s %s", fn_name, sqlite3_errmsg(dbconn->handle)); - } - } -} - -char * authdb_copy_sql_string(sqlite3_stmt * sql,int32_t col) -{ - char * result = NULL; - const char * sql_str = (const char *)sqlite3_column_text(sql, col); - if (sql_str) { - size_t len = strlen(sql_str) + 1; - result = (char*)calloc(1u, len); - check(result != NULL); - - strlcpy(result, sql_str, len); - } - return result; -} - -#pragma mark - -#pragma mark authdb_t - -static void -_authdb_finalize(CFTypeRef value) -{ - authdb_t db = (authdb_t)value; - - CFReleaseSafe(db->connections); - dispatch_release(db->queue); - free_safe(db->db_path); -} - -AUTH_TYPE_INSTANCE(authdb, - .init = NULL, - .copy = NULL, - .finalize = _authdb_finalize, - .equal = NULL, - .hash = NULL, - .copyFormattingDesc = NULL, - .copyDebugDesc = NULL - ); - -static CFTypeID authdb_get_type_id() { - static CFTypeID type_id = _kCFRuntimeNotATypeID; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - type_id = _CFRuntimeRegisterClass(&_auth_type_authdb); - }); - - return type_id; -} - -authdb_t -authdb_create() -{ - authdb_t db = NULL; - - db = (authdb_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, authdb_get_type_id(), AUTH_CLASS_SIZE(authdb), NULL); - require(db != NULL, done); - - db->queue = dispatch_queue_create(NULL, DISPATCH_QUEUE_SERIAL); - db->connections = CFArrayCreateMutable(kCFAllocatorDefault, 0, NULL); - - if (getenv("__OSINSTALL_ENVIRONMENT") != NULL) { - LOGV("authdb: running from installer"); - db->db_path = _copy_string("file::memory:?cache=shared"); - } else { - db->db_path = _copy_string(AUTHDB); - } - -done: - return db; -} - -authdb_connection_t authdb_connection_acquire(authdb_t db) -{ - __block authdb_connection_t dbconn = NULL; -#if DEBUG - static int32_t total = 0; -#endif - dispatch_sync(db->queue, ^{ - CFIndex count = CFArrayGetCount(db->connections); - if (count) { - dbconn = (authdb_connection_t)CFArrayGetValueAtIndex(db->connections, 0); - CFArrayRemoveValueAtIndex(db->connections, 0); - } else { - dbconn = authdb_connection_create(db); -#if DEBUG - total++; - LOGV("authdb: no handles available total: %i", total); -#endif - } - }); - - return dbconn; -} - -void authdb_connection_release(authdb_connection_t * dbconn) -{ - if (!dbconn || !(*dbconn)) - return; - - authdb_connection_t tmp = *dbconn; - *dbconn = NULL; - - dispatch_async(tmp->db->queue, ^{ - CFIndex count = CFArrayGetCount(tmp->db->connections); - if (count <= AUTHDB_MAX_HANDLES) { - CFArrayAppendValue(tmp->db->connections, tmp); - } else { - LOGD("authdb: freeing extra connection"); - CFRelease(tmp); - } - }); -} - -static bool _db_check_corrupted(authdb_connection_t dbconn) -{ - bool isCorrupted = true; - sqlite3_stmt *stmt = NULL; - int32_t rc; - - rc = sqlite3_prepare_v2(dbconn->handle, "PRAGMA integrity_check;", -1, &stmt, NULL); - if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { - LOGV("authdb: warning error %i when running integrity check", rc); - isCorrupted = false; - - } else if (rc == SQLITE_OK) { - rc = sqlite3_step(stmt); - - if (rc == SQLITE_LOCKED || rc == SQLITE_BUSY) { - LOGV("authdb: warning error %i when running integrity check", rc); - isCorrupted = false; - } else if (rc == SQLITE_ROW) { - const char * result = (const char*)sqlite3_column_text(stmt, 0); - - if (result && strncasecmp(result, "ok", 3) == 0) { - isCorrupted = false; - } - } - } - - sqlite3_finalize(stmt); - return isCorrupted; -} - -bool authdb_maintenance(authdb_connection_t dbconn) -{ - LOGD("authdb: starting maintenance"); - int32_t rc = SQLITE_ERROR; - auth_items_t config = NULL; - - bool isCorrupted = _db_check_corrupted(dbconn); - LOGD("authdb: integrity check=%s", isCorrupted ? "fail" : "pass"); - - if (isCorrupted) { - _handle_corrupt_db(dbconn); - } - - _db_maintenance(dbconn); - - rc = authdb_get_key_value(dbconn, "config", &config); - require_noerr_action(rc, done, LOGV("authdb: maintenance failed %i", rc)); - - _db_load_data(dbconn, config); - -done: - CFReleaseSafe(config); - LOGD("authdb: finished maintenance"); - return rc == SQLITE_OK; -} - -int32_t -authdb_exec(authdb_connection_t dbconn, const char * query) -{ - int32_t rc = SQLITE_ERROR; - require(query != NULL, done); - - rc = _sqlite3_exec(dbconn->handle, query); - _checkResult(dbconn, rc, __FUNCTION__, NULL); - -done: - return rc; -} - -static int32_t _prepare(authdb_connection_t dbconn, const char * sql, sqlite3_stmt ** out_stmt) -{ - int32_t rc; - sqlite3_stmt * stmt = NULL; - - require_action(sql != NULL, done, rc = SQLITE_ERROR); - require_action(out_stmt != NULL, done, rc = SQLITE_ERROR); - - rc = sqlite3_prepare_v2(dbconn->handle, sql, -1, &stmt, NULL); - require_noerr_action(rc, done, LOGV("authdb: prepare (%i) %s", rc, sqlite3_errmsg(dbconn->handle))); - - *out_stmt = stmt; - -done: - _checkResult(dbconn, rc, __FUNCTION__, stmt); - return rc; -} - -static void _parseItemsAtIndex(sqlite3_stmt * stmt, int32_t col, auth_items_t items, const char * key) -{ - switch (sqlite3_column_type(stmt, col)) { - case SQLITE_FLOAT: - auth_items_set_double(items, key, sqlite3_column_double(stmt, col)); - break; - case SQLITE_INTEGER: - auth_items_set_int64(items, key, sqlite3_column_int64(stmt, col)); - break; - case SQLITE_BLOB: - auth_items_set_data(items, - key, - sqlite3_column_blob(stmt, col), - (size_t)sqlite3_column_bytes(stmt, col)); - break; - case SQLITE_NULL: - break; - case SQLITE_TEXT: - default: - auth_items_set_string(items, key, (const char *)sqlite3_column_text(stmt, col)); - break; - } - -// LOGD("authdb: col=%s, val=%s, type=%i", sqlite3_column_name(stmt, col), sqlite3_column_text(stmt, col), sqlite3_column_type(stmt,col)); -} - -static int32_t _bindItemsAtIndex(sqlite3_stmt * stmt, int col, auth_items_t items, const char * key) -{ - int32_t rc; - switch (auth_items_get_type(items, key)) { - case AI_TYPE_INT: - rc = sqlite3_bind_int64(stmt, col, auth_items_get_int(items, key)); - break; - case AI_TYPE_UINT: - rc = sqlite3_bind_int64(stmt, col, auth_items_get_uint(items, key)); - break; - case AI_TYPE_INT64: - rc = sqlite3_bind_int64(stmt, col, auth_items_get_int64(items, key)); - break; - case AI_TYPE_UINT64: - rc = sqlite3_bind_int64(stmt, col, (int64_t)auth_items_get_uint64(items, key)); - break; - case AI_TYPE_DOUBLE: - rc = sqlite3_bind_double(stmt, col, auth_items_get_double(items, key)); - break; - case AI_TYPE_BOOL: - rc = sqlite3_bind_int64(stmt, col, auth_items_get_bool(items, key)); - break; - case AI_TYPE_DATA: - { - size_t blobLen = 0; - const void * blob = auth_items_get_data(items, key, &blobLen); - rc = sqlite3_bind_blob(stmt, col, blob, (int32_t)blobLen, NULL); - } - break; - case AI_TYPE_STRING: - rc = sqlite3_bind_text(stmt, col, auth_items_get_string(items, key), -1, NULL); - break; - default: - rc = sqlite3_bind_null(stmt, col); - break; - } - if (rc != SQLITE_OK) { - LOGV("authdb: auth_items bind failed (%i)", rc); - } - return rc; -} - -int32_t authdb_get_key_value(authdb_connection_t dbconn, const char * table, auth_items_t * out_items) -{ - int32_t rc = SQLITE_ERROR; - char * query = NULL; - sqlite3_stmt * stmt = NULL; - auth_items_t items = NULL; - - require(table != NULL, done); - require(out_items != NULL, done); - - asprintf(&query, "SELECT * FROM %s", table); - - rc = _prepare(dbconn, query, &stmt); - require_noerr(rc, done); - - items = auth_items_create(); - while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) { - switch (rc) { - case SQLITE_ROW: - _parseItemsAtIndex(stmt, 1, items, (const char*)sqlite3_column_text(stmt, 0)); - break; - default: - _checkResult(dbconn, rc, __FUNCTION__, stmt); - if (_is_busy(rc)) { - sleep(AUTHDB_BUSY_DELAY); - } else { - require_noerr_action(rc, done, LOGV("authdb: get_key_value (%i) %s", rc, sqlite3_errmsg(dbconn->handle))); - } - break; - } - } - - rc = SQLITE_OK; - CFRetain(items); - *out_items = items; - -done: - CFReleaseSafe(items); - free_safe(query); - sqlite3_finalize(stmt); - return rc; -} - -int32_t authdb_set_key_value(authdb_connection_t dbconn, const char * table, auth_items_t items) -{ - __block int32_t rc = SQLITE_ERROR; - char * query = NULL; - sqlite3_stmt * stmt = NULL; - - require(table != NULL, done); - require(items != NULL, done); - - asprintf(&query, "INSERT OR REPLACE INTO %s VALUES (?,?)", table); - - rc = _prepare(dbconn, query, &stmt); - require_noerr(rc, done); - - auth_items_iterate(items, ^bool(const char *key) { - sqlite3_reset(stmt); - _checkResult(dbconn, rc, __FUNCTION__, stmt); - - sqlite3_bind_text(stmt, 1, key, -1, NULL); - _bindItemsAtIndex(stmt, 2, items, key); - - rc = sqlite3_step(stmt); - if (rc != SQLITE_DONE) { - _checkResult(dbconn, rc, __FUNCTION__, stmt); - LOGV("authdb: set_key_value, step (%i) %s", rc, sqlite3_errmsg(dbconn->handle)); - } - - return true; - }); - -done: - free_safe(query); - sqlite3_finalize(stmt); - return rc; -} - -static int32_t _begin_transaction_type(authdb_connection_t dbconn, AuthDBTransactionType type) -{ - int32_t result = SQLITE_ERROR; - - const char * query = NULL; - switch (type) { - case AuthDBTransactionImmediate: - query = "BEGIN IMMEDATE;"; - break; - case AuthDBTransactionExclusive: - query = "BEGIN EXCLUSIVE;"; - break; - case AuthDBTransactionNormal: - query = "BEGIN;"; - break; - default: - break; - } - - result = SQLITE_OK; - - if (query != NULL && sqlite3_get_autocommit(dbconn->handle) != 0) { - result = _sqlite3_exec(dbconn->handle, query); - } - - return result; -} - -static int32_t _end_transaction(authdb_connection_t dbconn, bool commit) -{ - if (commit) { - return _sqlite3_exec(dbconn->handle, "END;"); - } else { - return _sqlite3_exec(dbconn->handle, "ROLLBACK;"); - } -} - -bool authdb_transaction(authdb_connection_t dbconn, AuthDBTransactionType type, bool (^t)(void)) -{ - int32_t result = SQLITE_ERROR; - bool commit = false; - - result = _begin_transaction_type(dbconn, type); - require_action(result == SQLITE_OK, done, LOGV("authdb: transaction begin failed %i", result)); - - commit = t(); - - result = _end_transaction(dbconn, commit); - require_action(result == SQLITE_OK, done, commit = false; LOGV("authdb: transaction end failed %i", result)); - -done: - return commit; -} - -bool authdb_step(authdb_connection_t dbconn, const char * sql, void (^bind_stmt)(sqlite3_stmt*), authdb_iterator_t iter) -{ - bool result = false; - sqlite3_stmt * stmt = NULL; - int32_t rc = SQLITE_ERROR; - - require_action(sql != NULL, done, rc = SQLITE_ERROR); - - rc = _prepare(dbconn, sql, &stmt); - require_noerr(rc, done); - - if (bind_stmt) { - bind_stmt(stmt); - } - - int32_t count = sqlite3_column_count(stmt); - - auth_items_t items = NULL; - while ((rc = sqlite3_step(stmt)) != SQLITE_DONE) { - switch (rc) { - case SQLITE_ROW: - { - if (iter) { - items = auth_items_create(); - for (int i = 0; i < count; i++) { - _parseItemsAtIndex(stmt, i, items, sqlite3_column_name(stmt, i)); - } - result = iter(items); - CFReleaseNull(items); - if (!result) { - goto done; - } - } - } - break; - default: - if (_is_busy(rc)) { - LOGV("authdb: %s", sqlite3_errmsg(dbconn->handle)); - sleep(AUTHDB_BUSY_DELAY); - sqlite3_reset(stmt); - } else { - require_noerr_action(rc, done, LOGV("authdb: step (%i) %s", rc, sqlite3_errmsg(dbconn->handle))); - } - break; - } - } - -done: - _checkResult(dbconn, rc, __FUNCTION__, stmt); - sqlite3_finalize(stmt); - return rc == SQLITE_DONE; -} - -void authdb_checkpoint(authdb_connection_t dbconn) -{ - int32_t rc = sqlite3_wal_checkpoint(dbconn->handle, NULL); - if (rc != SQLITE_OK) { - LOGV("authdb: checkpoit failed %i", rc); - } -} - -static CFMutableArrayRef -_copy_rules_dict(RuleType type, CFDictionaryRef plist, authdb_connection_t dbconn) -{ - CFMutableArrayRef result = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - require(result != NULL, done); - - _cf_dictionary_iterate(plist, ^bool(CFTypeRef key, CFTypeRef value) { - if (CFGetTypeID(key) != CFStringGetTypeID()) { - return true; - } - - if (CFGetTypeID(value) != CFDictionaryGetTypeID()) { - return true; - } - - rule_t rule = rule_create_with_plist(type, key, value, dbconn); - if (rule) { - CFArrayAppendValue(result, rule); - CFReleaseSafe(rule); - } - - return true; - }); - -done: - return result; -} - -static void -_import_rules(authdb_connection_t dbconn, CFMutableArrayRef rules, bool version_check, CFAbsoluteTime now) -{ - CFMutableArrayRef notcommited = CFArrayCreateMutable(kCFAllocatorDefault, 0, &kCFTypeArrayCallBacks); - CFIndex count = CFArrayGetCount(rules); - - for (CFIndex i = 0; i < count; i++) { - rule_t rule = (rule_t)CFArrayGetValueAtIndex(rules, i); - - bool update = false; - if (version_check) { - if (rule_get_id(rule) != 0) { // rule already exists see if we need to update - rule_t current = rule_create_with_string(rule_get_name(rule), dbconn); - if (rule_get_version(rule) > rule_get_version(current)) { - update = true; - } - CFReleaseSafe(current); - - if (!update) { - continue; - } - } - } - - __block bool delayCommit = false; - - switch (rule_get_type(rule)) { - case RT_RULE: - rule_delegates_iterator(rule, ^bool(rule_t delegate) { - if (rule_get_id(delegate) == 0) { - // fetch the rule from the database if it was previously committed - rule_sql_fetch(delegate, dbconn); - } - if (rule_get_id(delegate) == 0) { - LOGD("authdb: delaying %s waiting for delegate %s", rule_get_name(rule), rule_get_name(delegate)); - delayCommit = true; - return false; - } - return true; - }); - break; - default: - break; - } - - if (!delayCommit) { - bool success = rule_sql_commit(rule, dbconn, now, NULL); - LOGV("authdb: %s %s %s %s", - update ? "updating" : "importing", - rule_get_type(rule) == RT_RULE ? "rule" : "right", - rule_get_name(rule), success ? "success" : "FAIL"); - if (!success) { - CFArrayAppendValue(notcommited, rule); - } - } else { - CFArrayAppendValue(notcommited, rule); - } - } - CFArrayRemoveAllValues(rules); - CFArrayAppendArray(rules, notcommited, CFRangeMake(0, CFArrayGetCount(notcommited))); - CFReleaseSafe(notcommited); -} - -bool -authdb_import_plist(authdb_connection_t dbconn, CFDictionaryRef plist, bool version_check) -{ - bool result = false; - - LOGV("authdb: starting import"); - - CFAbsoluteTime now = CFAbsoluteTimeGetCurrent(); - CFMutableArrayRef rights = NULL; - CFMutableArrayRef rules = NULL; - require(plist != NULL, done); - - CFTypeRef rightsDict = CFDictionaryGetValue(plist, CFSTR("rights")); - if (rightsDict && CFGetTypeID(rightsDict) == CFDictionaryGetTypeID()) { - rights = _copy_rules_dict(RT_RIGHT, rightsDict, dbconn); - } - - CFTypeRef rulesDict = CFDictionaryGetValue(plist, CFSTR("rules")); - if (rulesDict && CFGetTypeID(rulesDict) == CFDictionaryGetTypeID()) { - rules = _copy_rules_dict(RT_RULE, rulesDict, dbconn); - } - - LOGV("authdb: rights = %li", CFArrayGetCount(rights)); - LOGV("authdb: rules = %li", CFArrayGetCount(rules)); - - CFIndex count; - // first pass import base rules without delegations - // remaining import rules that delegate to other rules - // loop upto 3 times to commit dependent rules first - for (int32_t j = 0; j < 3; j++) { - count = CFArrayGetCount(rules); - if (!count) - break; - - _import_rules(dbconn, rules, version_check, now); - } - - _import_rules(dbconn, rights, version_check, now); - - if (CFArrayGetCount(rights) == 0) { - result = true; - } - - authdb_checkpoint(dbconn); - -done: - CFReleaseSafe(rights); - CFReleaseSafe(rules); - - LOGV("authdb: finished import, %s", result ? "succeeded" : "failed"); - - return result; -} - -#pragma mark - -#pragma mark authdb_connection_t - -static bool _sql_profile_enabled(void) -{ - static bool profile_enabled = false; - -#if DEBUG - static dispatch_once_t onceToken; - - //sudo defaults write /Library/Preferences/com.apple.security.auth profile -bool true - dispatch_once(&onceToken, ^{ - CFTypeRef profile = (CFNumberRef)CFPreferencesCopyValue(CFSTR("profile"), CFSTR(SECURITY_AUTH_NAME), kCFPreferencesAnyUser, kCFPreferencesCurrentHost); - - if (profile && CFGetTypeID(profile) == CFBooleanGetTypeID()) { - profile_enabled = CFBooleanGetValue((CFBooleanRef)profile); - } - - LOGV("authdb: sql profile: %s", profile_enabled ? "enabled" : "disabled"); - - CFReleaseSafe(profile); - }); -#endif - - return profile_enabled; -} - -static void _profile(void *context AUTH_UNUSED, const char *sql, sqlite3_uint64 ns) { - LOGV("==\nauthdb: %s\nTime: %llu ms\n", sql, ns >> 20); -} - -static sqlite3 * _create_handle(authdb_t db) -{ - bool dbcreated = false; - sqlite3 * handle = NULL; - int32_t rc = sqlite3_open_v2(db->db_path, &handle, SQLITE_OPEN_READWRITE, NULL); - - if (rc != SQLITE_OK) { - char * tmp = dirname(db->db_path); - if (tmp) { - mkpath_np(tmp, 0700); - } - rc = sqlite3_open_v2(db->db_path, &handle, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, NULL); - dbcreated = true; - } - require_noerr_action(rc, done, LOGE("authdb: open %s (%i) %s", db->db_path, rc, sqlite3_errmsg(handle))); - - if (_sql_profile_enabled()) { - sqlite3_profile(handle, _profile, NULL); - } - - _sqlite3_exec(handle, "PRAGMA foreign_keys = ON"); - _sqlite3_exec(handle, "PRAGMA temp_store = MEMORY"); - - if (dbcreated) { - _sqlite3_exec(handle, "PRAGMA auto_vacuum = FULL"); - _sqlite3_exec(handle, "PRAGMA journal_mode = WAL"); - - int on = 1; - sqlite3_file_control(handle, 0, SQLITE_FCNTL_PERSIST_WAL, &on); - - chmod(db->db_path, S_IRUSR | S_IWUSR); - } - -done: - return handle; -} - -static void -_authdb_connection_finalize(CFTypeRef value) -{ - authdb_connection_t dbconn = (authdb_connection_t)value; - - if (dbconn->handle) { - sqlite3_close(dbconn->handle); - } - CFReleaseSafe(dbconn->db); -} - -AUTH_TYPE_INSTANCE(authdb_connection, - .init = NULL, - .copy = NULL, - .finalize = _authdb_connection_finalize, - .equal = NULL, - .hash = NULL, - .copyFormattingDesc = NULL, - .copyDebugDesc = NULL - ); - -static CFTypeID authdb_connection_get_type_id() { - static CFTypeID type_id = _kCFRuntimeNotATypeID; - static dispatch_once_t onceToken; - - dispatch_once(&onceToken, ^{ - type_id = _CFRuntimeRegisterClass(&_auth_type_authdb_connection); - }); - - return type_id; -} - -authdb_connection_t -authdb_connection_create(authdb_t db) -{ - authdb_connection_t dbconn = NULL; - - dbconn = (authdb_connection_t)_CFRuntimeCreateInstance(kCFAllocatorDefault, authdb_connection_get_type_id(), AUTH_CLASS_SIZE(authdb_connection), NULL); - require(dbconn != NULL, done); - - dbconn->db = (authdb_t)CFRetain(db); - dbconn->handle = _create_handle(dbconn->db); - -done: - return dbconn; -}