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