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