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