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