1/*
2 * Copyright (c) 2011-2014 Apple 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
39struct connection_info {
40    xpc_connection_t peer;
41    int processed;
42    int done;
43};
44
45typedef typeof(SecKeychainCopyDomainSearchList) SecKeychainCopyDomainSearchListType;
46SecKeychainCopyDomainSearchListType *SecKeychainCopyDomainSearchListFunctionPointer = NULL;
47typedef typeof(SecKeychainGetPath) SecKeychainGetPathType;
48SecKeychainGetPathType *SecKeychainGetPathFunctionPointer = NULL;
49
50// prior to 8723022 we have to do our own idle timeout work
51#ifndef XPC_HANDLES_IDLE_TIMEOUT
52int current_connections = 0;
53// Number of seconds to sit with no clients
54#define IDLE_WAIT_TIME 30
55#endif
56
57xpc_object_t keychain_prefs_path = NULL;
58xpc_object_t home = NULL;
59
60extern xpc_object_t
61xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
62
63static
64xpc_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);
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);
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);
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);
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
95static
96bool keychain_domain_needs_writes(const char *domain_name)
97{
98	return (0 == strcmp("kSecPreferencesDomainUser", domain_name) || 0 == strcmp("kSecPreferencesDomainDynamic", domain_name));
99}
100
101static
102void _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
115static
116xpc_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
129static
130xpc_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) {
169                ptrdiff_t i = strlen(buffer) - 1;
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);
199            CC_SHA1_Update(&sha1Context, base, (CC_LONG)strlen(base));
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
219static
220xpc_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
234static
235xpc_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
264static
265void 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(&current_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
333static
334void finalize_connection(void *not_used)
335{
336#ifdef XPC_HANDLES_IDLE_TIMEOUT
337	xpc_transaction_end();
338#endif
339}
340
341static
342void handle_connection_event(const xpc_connection_t peer)
343{
344#ifndef XPC_HANDLES_IDLE_TIMEOUT
345    __sync_add_and_fetch(&current_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
369static const char* g_path_to_plist = "/Library/Preferences/com.apple.security.plist";
370
371
372
373int 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
389    size_t home_dir_length = strlen(home_dir);
390    size_t path_to_plist_length = strlen(g_path_to_plist);
391
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
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}
423