]> git.saurik.com Git - apple/security.git/blobdiff - Security/authd/authdb.c
Security-57031.1.35.tar.gz
[apple/security.git] / Security / authd / authdb.c
diff --git a/Security/authd/authdb.c b/Security/authd/authdb.c
new file mode 100644 (file)
index 0000000..c52e61b
--- /dev/null
@@ -0,0 +1,1074 @@
+/* Copyright (c) 2012-2014 Apple Inc. All Rights Reserved. */
+
+#include "authdb.h"
+#include "mechanism.h"
+#include "rule.h"
+#include "debugging.h"
+#include "authitems.h"
+#include "server.h"
+
+#include <sqlite3.h>
+#include <sqlite3_private.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include "rule.h"
+#include "authutilities.h"
+#include <libgen.h>
+#include <sys/stat.h>
+
+#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;
+}