2 * Copyright (c) 2011-2014 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
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
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.
21 * @APPLE_LICENSE_HEADER_END@
24 #include <xpc/private.h>
26 #include <sys/param.h>
30 #include <Security/Security.h>
34 #include <CommonCrypto/CommonDigest.h>
35 #include <CoreFoundation/CoreFoundation.h>
36 #include <sys/types.h>
39 struct connection_info
{
40 xpc_connection_t peer
;
45 typedef typeof(SecKeychainCopyDomainSearchList
) SecKeychainCopyDomainSearchListType
;
46 SecKeychainCopyDomainSearchListType
*SecKeychainCopyDomainSearchListFunctionPointer
= NULL
;
47 typedef typeof(SecKeychainGetPath
) SecKeychainGetPathType
;
48 SecKeychainGetPathType
*SecKeychainGetPathFunctionPointer
= NULL
;
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
57 xpc_object_t keychain_prefs_path
= NULL
;
58 xpc_object_t home
= NULL
;
61 xpc_create_reply_with_format(xpc_object_t original
, const char * format
, ...);
64 xpc_object_t
create_keychain_search_list_for(xpc_connection_t peer
, SecPreferencesDomain domain
)
66 CFArrayRef keychains
= NULL
;
67 pid_t peer_pid
= xpc_connection_get_pid(peer
);
68 OSStatus status
= SecKeychainCopyDomainSearchListFunctionPointer(domain
, &keychains
);
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
);
74 xpc_object_t paths
= xpc_array_create(NULL
, 0);
75 CFIndex n_keychains
= CFArrayGetCount(keychains
);
77 for(i
= 0; i
< n_keychains
; i
++) {
78 char path
[MAXPATHLEN
];
80 SecKeychainRef keychain
= (SecKeychainRef
)CFArrayGetValueAtIndex(keychains
, i
);
81 UInt32 length
= MAXPATHLEN
;
82 OSStatus status
= SecKeychainGetPathFunctionPointer(keychain
, &length
, path
);
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
);
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
);
96 bool keychain_domain_needs_writes(const char *domain_name
)
98 return (0 == strcmp("kSecPreferencesDomainUser", domain_name
) || 0 == strcmp("kSecPreferencesDomainDynamic", domain_name
));
102 void _set_keychain_search_lists_for_domain(xpc_connection_t peer
, xpc_object_t all_domains
, char *domain_name
, SecPreferencesDomain domain_enum
)
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
);
109 syslog(LOG_ERR
, "Can't discover keychain paths for domain %s on behalf of %d", domain_name
, xpc_connection_get_pid(peer
));
113 #define SET_KEYCHAIN_SEARCH_LISTS_FOR_DOMAIN(peer, all_domains, domain) _set_keychain_search_lists_for_domain(peer, all_domains, #domain, domain);
116 xpc_object_t
create_keychain_search_lists(xpc_connection_t peer
)
118 xpc_object_t all_domains
= xpc_dictionary_create(NULL
, NULL
, 0);
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
);
130 xpc_object_t
create_keychain_and_lock_paths(xpc_connection_t peer
, xpc_object_t keychain_path_dict
)
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
);
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
);
145 xpc_object_t return_paths_dict
= xpc_dictionary_create(NULL
, NULL
, 0);
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
);
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
160 // figure out the base and dir
161 const char* path
= xpc_array_get_string(keychain_path_array
, i
);
165 char buffer
[PATH_MAX
];
166 strcpy(buffer
, path
);
169 ptrdiff_t i
= strlen(buffer
) - 1;
170 while (i
>= 0 && buffer
[i
] != '/') {
175 // NULL terminate the dir
178 base
= buffer
+ i
+ 1;
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
));
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
);
195 xpc_release(path_as_xpc_string
);
197 CC_SHA1_CTX sha1Context
;
198 CC_SHA1_Init(&sha1Context
);
199 CC_SHA1_Update(&sha1Context
, base
, (CC_LONG
)strlen(base
));
201 unsigned char sha1_result_bytes
[CC_SHA1_DIGEST_LENGTH
];
203 CC_SHA1_Final(sha1_result_bytes
, &sha1Context
);
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
);
210 xpc_dictionary_set_value(return_paths_dict
, keychain_domain
, return_paths_array
);
211 xpc_release(return_paths_array
);
215 dispatch_release(assembly_queue
);
216 return return_paths_dict
;
220 xpc_object_t
create_one_sandbox_extension(xpc_object_t path
, bool read_only
)
222 const char * extension_class
= read_only
? APP_SANDBOX_READ
: APP_SANDBOX_READ_WRITE
;
223 char *sandbox_extension
= sandbox_extension_issue_file(extension_class
, xpc_string_get_string_ptr(path
), SANDBOX_EXTENSION_CANONICAL
);
224 if (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
;
229 syslog(LOG_ERR
, "Can't get sandbox fs extension for %s", xpc_string_get_string_ptr(path
));
235 xpc_object_t
create_all_sandbox_extensions(xpc_object_t path_dict
)
237 xpc_object_t extensions
= xpc_array_create(NULL
, 0);
239 xpc_object_t sandbox_extension
= create_one_sandbox_extension(keychain_prefs_path
, true);
240 if (sandbox_extension
) {
241 xpc_array_append_value(extensions
, sandbox_extension
);
242 xpc_release(sandbox_extension
);
245 xpc_dictionary_apply(path_dict
, ^(const char *keychain_domain
, xpc_object_t path_array
) {
246 bool read_only
= true;
247 if (keychain_domain_needs_writes(keychain_domain
)) {
250 xpc_array_apply(path_array
, ^(size_t index
, xpc_object_t path
) {
251 xpc_object_t sandbox_extension
= create_one_sandbox_extension(path
, read_only
);
252 if (sandbox_extension
) {
253 xpc_array_append_value(extensions
, sandbox_extension
);
254 xpc_release(sandbox_extension
);
265 void handle_request_event(struct connection_info
*info
, xpc_object_t event
)
267 xpc_connection_t peer
= xpc_dictionary_get_remote_connection(event
);
268 xpc_type_t xtype
= xpc_get_type(event
);
270 syslog(LOG_ERR
, "event %p while done", event
);
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.
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
) {
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.
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
;
306 sandbox_extensions
= create_all_sandbox_extensions(all_paths
);
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
);
313 if (keychain_paths
) {
314 xpc_release(keychain_paths
);
317 xpc_release(all_paths
);
319 if (sandbox_extensions
) {
320 xpc_release(sandbox_extensions
);
322 if (INT32_MAX
!= info
->processed
) {
326 syslog(LOG_ERR
, "Unknown op=%s request from pid %d", operation
, xpc_connection_get_pid(peer
));
329 syslog(LOG_ERR
, "Unhandled request event=%p type=%p", event
, xtype
);
334 void finalize_connection(void *not_used
)
336 #ifdef XPC_HANDLES_IDLE_TIMEOUT
337 xpc_transaction_end();
342 void handle_connection_event(const xpc_connection_t peer
)
344 #ifndef XPC_HANDLES_IDLE_TIMEOUT
345 __sync_add_and_fetch(¤t_connections
, 1);
347 __block
struct connection_info info
;
352 xpc_connection_set_event_handler(peer
, ^(xpc_object_t event
) {
353 handle_request_event(&info
, event
);
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();
363 // enable the peer connection to receive messages
364 xpc_connection_resume(peer
);
369 static const char* g_path_to_plist
= "/Library/Preferences/com.apple.security.plist";
373 int main(int argc
, const char *argv
[])
375 char *wait4debugger
= getenv("WAIT4DEBUGGER");
376 if (wait4debugger
&& !strcasecmp("YES", wait4debugger
)) {
377 syslog(LOG_ERR
, "Waiting for debugger");
378 kill(getpid(), SIGSTOP
);
381 // get the home directory
382 const char* home_dir
= getenv("HOME");
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...
389 size_t home_dir_length
= strlen(home_dir
);
390 size_t path_to_plist_length
= strlen(g_path_to_plist
);
392 size_t total_length
= home_dir_length
+ path_to_plist_length
+ 1; // compensate for terminating zero
393 if (total_length
> PATH_MAX
) {
394 // someone is spoofing us, just exit
398 // make storage for the real path
399 char buffer
[PATH_MAX
];
400 strlcpy(buffer
, home_dir
, sizeof(buffer
));
401 strlcat(buffer
, g_path_to_plist
, sizeof(buffer
));
402 keychain_prefs_path
= xpc_string_create(buffer
);
403 home
= xpc_string_create(home_dir
);
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());
412 SecKeychainGetPathFunctionPointer
= dlsym(security_framework
, "SecKeychainGetPath");
413 if (!SecKeychainGetPathFunctionPointer
) {
414 syslog(LOG_ERR
, "Can't lookup SecKeychainGetPath in %p: %s", security_framework
, dlerror());
418 syslog(LOG_ERR
, "Failed to open Security framework: %s", dlerror());
422 xpc_main(handle_connection_event
);