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