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