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