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