+++ /dev/null
-/* 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 <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;
-}