]>
Commit | Line | Data |
---|---|---|
b1ab9ed8 A |
1 | /* |
2 | * Copyright (c) 2011 Apple Computer, Inc. All Rights Reserved. | |
3 | * | |
4 | * @APPLE_LICENSE_HEADER_START@ | |
5 | * | |
6 | * This file contains Original Code and/or Modifications of Original Code | |
7 | * as defined in and that are subject to the Apple Public Source License | |
8 | * Version 2.0 (the 'License'). You may not use this file except in | |
9 | * compliance with the License. Please obtain a copy of the License at | |
10 | * http://www.opensource.apple.com/apsl/ and read it before using this | |
11 | * file. | |
12 | * | |
13 | * The Original Code and all software distributed under the License are | |
14 | * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER | |
15 | * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, | |
16 | * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. | |
18 | * Please see the License for the specific language governing rights and | |
19 | * limitations under the License. | |
20 | * | |
21 | * @APPLE_LICENSE_HEADER_END@ | |
22 | */ | |
23 | ||
24 | #include <xpc/private.h> | |
25 | #include <syslog.h> | |
26 | #include <sys/param.h> | |
27 | #include <sandbox.h> | |
28 | #include <dlfcn.h> | |
29 | #include <sysexits.h> | |
30 | #include <Security/Security.h> | |
31 | #include <stdlib.h> | |
32 | #include <libgen.h> | |
33 | #include <string.h> | |
34 | #include <CommonCrypto/CommonDigest.h> | |
35 | #include <CoreFoundation/CoreFoundation.h> | |
36 | #include <sys/types.h> | |
37 | #include <pwd.h> | |
38 | ||
39 | struct connection_info { | |
40 | xpc_connection_t peer; | |
41 | int processed; | |
42 | int done; | |
43 | }; | |
44 | ||
45 | typedef typeof(SecKeychainCopyDomainSearchList) SecKeychainCopyDomainSearchListType; | |
46 | SecKeychainCopyDomainSearchListType *SecKeychainCopyDomainSearchListFunctionPointer = NULL; | |
47 | typedef typeof(SecKeychainGetPath) SecKeychainGetPathType; | |
48 | SecKeychainGetPathType *SecKeychainGetPathFunctionPointer = NULL; | |
49 | ||
50 | // prior to 8723022 we have to do our own idle timeout work | |
51 | #ifndef XPC_HANDLES_IDLE_TIMEOUT | |
52 | int current_connections = 0; | |
53 | // Number of seconds to sit with no clients | |
54 | #define IDLE_WAIT_TIME 30 | |
55 | #endif | |
56 | ||
57 | xpc_object_t keychain_prefs_path = NULL; | |
58 | xpc_object_t home = NULL; | |
59 | ||
60 | extern xpc_object_t | |
61 | xpc_create_reply_with_format(xpc_object_t original, const char * format, ...); | |
62 | ||
427c49bc | 63 | static |
b1ab9ed8 A |
64 | xpc_object_t create_keychain_search_list_for(xpc_connection_t peer, SecPreferencesDomain domain) |
65 | { | |
66 | CFArrayRef keychains = NULL; | |
67 | pid_t peer_pid = xpc_connection_get_pid(peer); | |
68 | OSStatus status = SecKeychainCopyDomainSearchListFunctionPointer(domain, &keychains); | |
427c49bc A |
69 | if (errSecSuccess != status) { |
70 | syslog(LOG_ERR, "Unable to get keychain search list (domain=%d) on behalf of %d, status=0x%lx", domain, peer_pid, (unsigned long)status); | |
b1ab9ed8 A |
71 | return NULL; |
72 | } | |
73 | ||
74 | xpc_object_t paths = xpc_array_create(NULL, 0); | |
75 | CFIndex n_keychains = CFArrayGetCount(keychains); | |
76 | CFIndex i; | |
77 | for(i = 0; i < n_keychains; i++) { | |
78 | char path[MAXPATHLEN]; | |
79 | ||
80 | SecKeychainRef keychain = (SecKeychainRef)CFArrayGetValueAtIndex(keychains, i); | |
81 | UInt32 length = MAXPATHLEN; | |
82 | OSStatus status = SecKeychainGetPathFunctionPointer(keychain, &length, path); | |
427c49bc A |
83 | if (errSecSuccess != status) { |
84 | syslog(LOG_ERR, "Unable to get path for keychain#%ld of %ld on behalf of %d, status=0x%lx", i, n_keychains, peer_pid, (unsigned long)status); | |
b1ab9ed8 A |
85 | continue; |
86 | } | |
87 | xpc_object_t path_as_xpc_string = xpc_string_create(path); | |
88 | xpc_array_append_value(paths, path_as_xpc_string); | |
89 | xpc_release(path_as_xpc_string); | |
90 | } | |
91 | CFRelease(keychains); | |
92 | return paths; | |
93 | } | |
94 | ||
427c49bc | 95 | static |
b1ab9ed8 A |
96 | bool keychain_domain_needs_writes(const char *domain_name) |
97 | { | |
98 | return (0 == strcmp("kSecPreferencesDomainUser", domain_name) || 0 == strcmp("kSecPreferencesDomainDynamic", domain_name)); | |
99 | } | |
100 | ||
427c49bc | 101 | static |
b1ab9ed8 A |
102 | void _set_keychain_search_lists_for_domain(xpc_connection_t peer, xpc_object_t all_domains, char *domain_name, SecPreferencesDomain domain_enum) |
103 | { | |
104 | xpc_object_t keychains_for_domain = create_keychain_search_list_for(peer, domain_enum); | |
105 | if (keychains_for_domain) { | |
106 | xpc_dictionary_set_value(all_domains, domain_name, keychains_for_domain); | |
107 | xpc_release(keychains_for_domain); | |
108 | } else { | |
109 | syslog(LOG_ERR, "Can't discover keychain paths for domain %s on behalf of %d", domain_name, xpc_connection_get_pid(peer)); | |
110 | } | |
111 | } | |
112 | ||
113 | #define SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, domain) _set_keychain_search_lists_for_domain(peer, all_domains, #domain, domain); | |
114 | ||
427c49bc | 115 | static |
b1ab9ed8 A |
116 | xpc_object_t create_keychain_search_lists(xpc_connection_t peer) |
117 | { | |
118 | xpc_object_t all_domains = xpc_dictionary_create(NULL, NULL, 0); | |
119 | ||
120 | SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainUser); | |
121 | SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainSystem); | |
122 | SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainCommon); | |
123 | SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, kSecPreferencesDomainDynamic); | |
124 | ||
125 | return all_domains; | |
126 | } | |
127 | ||
128 | ||
427c49bc | 129 | static |
b1ab9ed8 A |
130 | xpc_object_t create_keychain_and_lock_paths(xpc_connection_t peer, xpc_object_t keychain_path_dict) |
131 | { | |
132 | pid_t peer_pid = xpc_connection_get_pid(peer); | |
133 | char *assembly_queue_label = NULL; | |
134 | asprintf(&assembly_queue_label, "assembly-for-%d", peer_pid); | |
135 | if (!assembly_queue_label) { | |
136 | syslog(LOG_ERR, "Unable to create assembly queue label for %d", peer_pid); | |
137 | return NULL; | |
138 | } | |
139 | dispatch_queue_t assembly_queue = dispatch_queue_create(assembly_queue_label, 0); | |
140 | free(assembly_queue_label); | |
141 | if (!assembly_queue) { | |
142 | syslog(LOG_ERR, "Unable to create assembly queue for %d", peer_pid); | |
143 | return NULL; | |
144 | } | |
145 | xpc_object_t return_paths_dict = xpc_dictionary_create(NULL, NULL, 0); | |
146 | ||
147 | xpc_dictionary_apply(keychain_path_dict, ^(const char *keychain_domain, xpc_object_t keychain_path_array) { | |
148 | xpc_object_t return_paths_array = xpc_array_create(NULL, 0); | |
149 | dispatch_apply(xpc_array_get_count(keychain_path_array), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^(size_t i) { | |
150 | xpc_object_t path_as_xpc_string = xpc_array_get_value(keychain_path_array, i); | |
151 | dispatch_sync(assembly_queue, ^{ | |
152 | xpc_array_append_value(return_paths_array, path_as_xpc_string); | |
153 | }); | |
154 | ||
155 | if (!keychain_domain_needs_writes(keychain_domain)) { | |
156 | // lock files are only to prevent write-write errors, readers don't hold locks, so they don't need the lock files | |
157 | return; | |
158 | } | |
159 | ||
160 | // figure out the base and dir | |
161 | const char* path = xpc_array_get_string(keychain_path_array, i); | |
162 | char* dir; | |
163 | char* base; | |
164 | ||
165 | char buffer[PATH_MAX]; | |
166 | strcpy(buffer, path); | |
167 | ||
168 | if (path != NULL) { | |
427c49bc | 169 | ptrdiff_t i = strlen(buffer) - 1; |
b1ab9ed8 A |
170 | while (i >= 0 && buffer[i] != '/') { |
171 | i -= 1; | |
172 | } | |
173 | ||
174 | if (i >= 0) { | |
175 | // NULL terminate the dir | |
176 | buffer[i] = 0; | |
177 | dir = buffer; | |
178 | base = buffer + i + 1; | |
179 | } else { | |
180 | dir = NULL; | |
181 | base = buffer; | |
182 | } | |
183 | } | |
184 | ||
185 | if (!(path && dir && base)) { | |
186 | syslog(LOG_ERR, "Can't get dir or base (likely out of memory) for %s", xpc_array_get_string(keychain_path_array, i)); | |
187 | return; | |
188 | } | |
189 | ||
190 | // "network style" lock files | |
191 | path_as_xpc_string = xpc_string_create_with_format("%s/lck~%s", dir, base); | |
192 | dispatch_sync(assembly_queue, ^{ | |
193 | xpc_array_append_value(return_paths_array, path_as_xpc_string); | |
194 | }); | |
195 | xpc_release(path_as_xpc_string); | |
196 | ||
197 | CC_SHA1_CTX sha1Context; | |
198 | CC_SHA1_Init(&sha1Context); | |
427c49bc | 199 | CC_SHA1_Update(&sha1Context, base, (CC_LONG)strlen(base)); |
b1ab9ed8 A |
200 | |
201 | unsigned char sha1_result_bytes[CC_SHA1_DIGEST_LENGTH]; | |
202 | ||
203 | CC_SHA1_Final(sha1_result_bytes, &sha1Context); | |
204 | ||
205 | path_as_xpc_string = xpc_string_create_with_format("%s/.fl%02X%02X%02X%02X", dir, sha1_result_bytes[0], sha1_result_bytes[1], sha1_result_bytes[2], sha1_result_bytes[3]); | |
206 | dispatch_sync(assembly_queue, ^{ | |
207 | xpc_array_append_value(return_paths_array, path_as_xpc_string); | |
208 | }); | |
209 | }); | |
210 | xpc_dictionary_set_value(return_paths_dict, keychain_domain, return_paths_array); | |
211 | xpc_release(return_paths_array); | |
212 | return (bool)true; | |
213 | }); | |
214 | ||
215 | dispatch_release(assembly_queue); | |
216 | return return_paths_dict; | |
217 | } | |
218 | ||
427c49bc | 219 | static |
b1ab9ed8 A |
220 | xpc_object_t create_one_sandbox_extension(xpc_object_t path, uint64_t extension_flags) |
221 | { | |
222 | char *sandbox_extension = NULL; | |
223 | int status = sandbox_issue_fs_extension(xpc_string_get_string_ptr(path), extension_flags, &sandbox_extension); | |
224 | if (0 == status && sandbox_extension) { | |
225 | xpc_object_t sandbox_extension_as_xpc_string = xpc_string_create(sandbox_extension); | |
226 | free(sandbox_extension); | |
227 | return sandbox_extension_as_xpc_string; | |
228 | } else { | |
229 | syslog(LOG_ERR, "Can't get sandbox fs extension for %s, status=%d errno=%m ext=%s", xpc_string_get_string_ptr(path), status, sandbox_extension); | |
230 | } | |
231 | return NULL; | |
232 | } | |
233 | ||
427c49bc | 234 | static |
b1ab9ed8 A |
235 | xpc_object_t create_all_sandbox_extensions(xpc_object_t path_dict) |
236 | { | |
237 | xpc_object_t extensions = xpc_array_create(NULL, 0); | |
238 | ||
239 | xpc_object_t sandbox_extension = create_one_sandbox_extension(keychain_prefs_path, FS_EXT_FOR_PATH|FS_EXT_READ); | |
240 | if (sandbox_extension) { | |
241 | xpc_array_append_value(extensions, sandbox_extension); | |
242 | xpc_release(sandbox_extension); | |
243 | } | |
244 | ||
245 | xpc_dictionary_apply(path_dict, ^(const char *keychain_domain, xpc_object_t path_array) { | |
246 | uint64_t extension_flags = FS_EXT_FOR_PATH|FS_EXT_READ; | |
247 | if (keychain_domain_needs_writes(keychain_domain)) { | |
248 | extension_flags = FS_EXT_FOR_PATH|FS_EXT_READ|FS_EXT_WRITE; | |
249 | } | |
250 | xpc_array_apply(path_array, ^(size_t index, xpc_object_t path) { | |
251 | xpc_object_t sandbox_extension = create_one_sandbox_extension(path, extension_flags); | |
252 | if (sandbox_extension) { | |
253 | xpc_array_append_value(extensions, sandbox_extension); | |
254 | xpc_release(sandbox_extension); | |
255 | } | |
256 | return (bool)true; | |
257 | }); | |
258 | return (bool)true; | |
259 | }); | |
260 | ||
261 | return extensions; | |
262 | } | |
263 | ||
427c49bc | 264 | static |
b1ab9ed8 A |
265 | void handle_request_event(struct connection_info *info, xpc_object_t event) |
266 | { | |
267 | xpc_connection_t peer = xpc_dictionary_get_connection(event); | |
268 | xpc_type_t xtype = xpc_get_type(event); | |
269 | if (info->done) { | |
270 | syslog(LOG_ERR, "event %p while done", event); | |
271 | return; | |
272 | } | |
273 | if (xtype == XPC_TYPE_ERROR) { | |
274 | if (event == XPC_ERROR_TERMINATION_IMMINENT) { | |
275 | // launchd would like us to die, but we have open transactions. When we finish with them xpc_service_main | |
276 | // will exit for us, so there is nothing for us to do here. | |
277 | return; | |
278 | } | |
279 | ||
280 | if (!info->done) { | |
281 | info->done = true; | |
282 | xpc_release(info->peer); | |
283 | #ifndef XPC_HANDLES_IDLE_TIMEOUT | |
284 | if (0 == __sync_add_and_fetch(¤t_connections, -1)) { | |
285 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * IDLE_WAIT_TIME), dispatch_get_main_queue(), ^(void) { | |
286 | if (0 == current_connections) { | |
287 | exit(0); | |
288 | } | |
289 | }); | |
290 | } | |
291 | #endif | |
292 | } | |
293 | if (peer == NULL && XPC_ERROR_CONNECTION_INVALID == event && 0 != info->processed) { | |
294 | // this is a normal shutdown on a connection that has processed at least | |
295 | // one request. Nothing intresting to log. | |
296 | return; | |
297 | } | |
298 | syslog(LOG_ERR, "listener event error (connection %p): %s", peer, xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION)); | |
299 | } else if (xtype == XPC_TYPE_DICTIONARY) { | |
300 | const char *operation = xpc_dictionary_get_string(event, "op"); | |
301 | if (operation && !strcmp(operation, "GrantKeychainPaths")) { | |
302 | xpc_object_t keychain_paths = create_keychain_search_lists(peer); | |
303 | xpc_object_t all_paths = create_keychain_and_lock_paths(peer, keychain_paths); | |
304 | xpc_object_t sandbox_extensions = NULL; | |
305 | if (all_paths) { | |
306 | sandbox_extensions = create_all_sandbox_extensions(all_paths); | |
307 | } | |
308 | ||
309 | xpc_object_t reply = xpc_create_reply_with_format(event, "{keychain-paths: %value, all-paths: %value, extensions: %value, keychain-home: %value}", | |
310 | keychain_paths, all_paths, sandbox_extensions, home); | |
311 | xpc_connection_send_message(peer, reply); | |
312 | xpc_release(reply); | |
313 | if (keychain_paths) { | |
314 | xpc_release(keychain_paths); | |
315 | } | |
316 | if (all_paths) { | |
317 | xpc_release(all_paths); | |
318 | } | |
319 | if (sandbox_extensions) { | |
320 | xpc_release(sandbox_extensions); | |
321 | } | |
322 | if (INT32_MAX != info->processed) { | |
323 | info->processed++; | |
324 | } | |
325 | } else { | |
326 | syslog(LOG_ERR, "Unknown op=%s request from pid %d", operation, xpc_connection_get_pid(peer)); | |
327 | } | |
328 | } else { | |
329 | syslog(LOG_ERR, "Unhandled request event=%p type=%p", event, xtype); | |
330 | } | |
331 | } | |
332 | ||
427c49bc | 333 | static |
b1ab9ed8 A |
334 | void finalize_connection(void *not_used) |
335 | { | |
336 | #ifdef XPC_HANDLES_IDLE_TIMEOUT | |
337 | xpc_transaction_end(); | |
338 | #endif | |
339 | } | |
340 | ||
427c49bc A |
341 | static |
342 | void handle_connection_event(const xpc_connection_t peer) | |
b1ab9ed8 A |
343 | { |
344 | #ifndef XPC_HANDLES_IDLE_TIMEOUT | |
345 | __sync_add_and_fetch(¤t_connections, 1); | |
346 | #endif | |
347 | __block struct connection_info info; | |
348 | info.peer = peer; | |
349 | info.processed = 0; | |
350 | info.done = false; | |
351 | ||
352 | xpc_connection_set_event_handler(peer, ^(xpc_object_t event) { | |
353 | handle_request_event(&info, event); | |
354 | }); | |
355 | ||
356 | // unlike dispatch objects xpc objects don't need a context set in order to run a finalizer. (we use our finalizer to | |
357 | // end the transaction we are about to begin...this keeps xpc from idle exiting us while we have a live connection) | |
358 | xpc_connection_set_finalizer_f(peer, finalize_connection); | |
359 | #ifdef XPC_HANDLES_IDLE_TIMEOUT | |
360 | xpc_transaction_begin(); | |
361 | #endif | |
362 | ||
363 | // enable the peer connection to receive messages | |
364 | xpc_connection_resume(peer); | |
365 | xpc_retain(peer); | |
366 | } | |
367 | ||
368 | ||
369 | static const char* g_path_to_plist = "/Library/Preferences/com.apple.security.plist"; | |
370 | ||
371 | ||
372 | ||
373 | int main(int argc, const char *argv[]) | |
374 | { | |
375 | char *wait4debugger = getenv("WAIT4DEBUGGER"); | |
376 | if (wait4debugger && !strcasecmp("YES", wait4debugger)) { | |
377 | syslog(LOG_ERR, "Waiting for debugger"); | |
378 | kill(getpid(), SIGSTOP); | |
379 | } | |
380 | ||
381 | // get the home directory | |
382 | const char* home_dir = getenv("HOME"); | |
383 | ||
384 | if (home_dir == NULL || strlen(home_dir) == 0) { | |
385 | struct passwd* pwd = getpwuid(getuid()); | |
386 | home_dir = pwd->pw_dir; // look it up in directory services, sort of... | |
387 | } | |
388 | ||
427c49bc A |
389 | size_t home_dir_length = strlen(home_dir); |
390 | size_t path_to_plist_length = strlen(g_path_to_plist); | |
b1ab9ed8 | 391 | |
427c49bc | 392 | size_t total_length = home_dir_length + path_to_plist_length + 1; // compensate for terminating zero |
b1ab9ed8 A |
393 | if (total_length > PATH_MAX) { |
394 | // someone is spoofing us, just exit | |
395 | return -1; | |
396 | } | |
397 | ||
398 | // make storage for the real path | |
399 | char buffer[total_length]; | |
400 | strlcpy(buffer, home_dir, total_length); | |
401 | strlcat(buffer, g_path_to_plist, total_length); | |
402 | keychain_prefs_path = xpc_string_create(buffer); | |
403 | home = xpc_string_create(home_dir); | |
404 | ||
405 | void *security_framework = dlopen("/System/Library/Frameworks/Security.framework/Security", RTLD_LAZY); | |
406 | if (security_framework) { | |
407 | SecKeychainCopyDomainSearchListFunctionPointer = dlsym(security_framework, "SecKeychainCopyDomainSearchList"); | |
408 | if (!SecKeychainCopyDomainSearchListFunctionPointer) { | |
409 | syslog(LOG_ERR, "Can't lookup SecKeychainCopyDomainSearchList in %p: %s", security_framework, dlerror()); | |
410 | return EX_OSERR; | |
411 | } | |
412 | SecKeychainGetPathFunctionPointer = dlsym(security_framework, "SecKeychainGetPath"); | |
413 | if (!SecKeychainGetPathFunctionPointer) { | |
414 | syslog(LOG_ERR, "Can't lookup SecKeychainGetPath in %p: %s", security_framework, dlerror()); | |
415 | return EX_OSERR; | |
416 | } | |
417 | } | |
418 | ||
419 | xpc_main(handle_connection_event); | |
420 | ||
421 | return EX_OSERR; | |
422 | } |