/*
* Copyright (c) 2007-2009 Apple Inc. All Rights Reserved.
- *
+ *
* @APPLE_LICENSE_HEADER_START@
- *
+ *
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
- *
+ *
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
- *
+ *
* @APPLE_LICENSE_HEADER_END@
*/
-#include <mach/mach.h>
-#include <mach/message.h>
-#include <mach/mach_error.h>
-#include <servers/bootstrap.h>
-
-#include <pthread.h>
#include <stdbool.h>
#include <sys/queue.h>
+#include <syslog.h>
#include <CoreFoundation/CoreFoundation.h>
#include <Security/SecItem.h>
#include <Security/SecBasePriv.h>
#include <Security/SecInternal.h>
-#include <security_utilities/debugging.h>
+#include <utilities/debugging.h>
+#include <utilities/SecCFError.h>
+#include <utilities/SecXPCError.h>
+#include <utilities/SecCFWrappers.h>
+#include <utilities/SecDispatchRelease.h>
+#include <utilities/SecDb.h> // TODO Fixme this gets us SecError().
#include "securityd_client.h"
-#include "securityd_req.h"
-
-#if NO_SERVER
-#define CHECK_ENTITLEMENTS 0
-#else
-#define CHECK_ENTITLEMENTS 1
-#endif
+#include "SecuritydXPC.h"
struct securityd *gSecurityd;
-/* XXX Not thread safe right now. */
-static CFArrayRef gAccessGroups = NULL;
+//
+// MARK: XPC IPC.
+//
-CFArrayRef SecAccessGroupsGetCurrent(void) {
-#if !CHECK_ENTITLEMENTS
- if (!gAccessGroups) {
- /* Initialize gAccessGroups for tests. */
- const void *agrps[] = {
- CFSTR("test"),
-#if 1
- CFSTR("apple"),
- CFSTR("lockdown-identities"),
+/* Hardcoded Access Groups for the server itself */
+static CFArrayRef SecServerCopyAccessGroups(void) {
+ return CFArrayCreateForCFTypes(kCFAllocatorDefault,
+#if NO_SERVER
+ CFSTR("test"),
+ CFSTR("apple"),
+ CFSTR("lockdown-identities"),
#else
- CFSTR("*"),
-#endif
- };
- gAccessGroups = CFArrayCreate(NULL, agrps,
- sizeof(agrps) / sizeof(*agrps), &kCFTypeArrayCallBacks);
- }
+ CFSTR("sync"),
#endif
- return gAccessGroups;
-}
-
-void SecAccessGroupsSetCurrent(CFArrayRef accessGroups) {
- if (accessGroups)
- CFRetain(accessGroups);
- CFReleaseSafe(gAccessGroups);
- gAccessGroups = accessGroups;
+ CFSTR("com.apple.security.sos"),
+ NULL);
}
-static mach_port_t securityd_port = MACH_PORT_NULL;
-static pthread_once_t init_once = PTHREAD_ONCE_INIT;
-static pthread_key_t port_key;
-
-static CFStringRef kSecRunLoopModeRef = CFSTR("com.apple.securityd.runloop");
-
-struct request {
- uint32_t seq_no;
- uint32_t msgid;
- SLIST_ENTRY(request) next;
- int32_t rcode;
- void *data;
- size_t length;
- volatile bool done;
-};
-
-typedef struct per_thread {
- SLIST_HEAD(request_head, request) head;;
- CFMachPortRef port;
- uint32_t seq_no;
- mach_msg_id_t notification; /* XXX for stacked requests, notifications
- need to be matched up with seq_nos such
- that only failed requests error out/retry */
-} *per_thread_t;
-
-
-static void destroy_per_thread(void *ex)
-{
- per_thread_t pt = (per_thread_t)ex;
- CFMachPortInvalidate(pt->port);
- CFRelease(pt->port);
- free(pt);
+CFArrayRef SecAccessGroupsGetCurrent(void) {
+ static CFArrayRef gSecServerAccessGroups;
+ static dispatch_once_t only_do_this_once;
+ dispatch_once(&only_do_this_once, ^{
+ gSecServerAccessGroups = SecServerCopyAccessGroups();
+ assert(gSecServerAccessGroups);
+ });
+ return gSecServerAccessGroups;
}
-static void securityd_port_reset(void)
-{
- pthread_once_t temp = PTHREAD_ONCE_INIT;
- init_once = temp;
- securityd_port = MACH_PORT_NULL;
+static xpc_connection_t securityd_create_connection(const char *name) {
+ if (!name)
+ name = kSecuritydXPCServiceName;
+ xpc_connection_t connection;
+ connection = xpc_connection_create_mach_service(name, NULL, 0);
+ xpc_connection_set_event_handler(connection, ^(xpc_object_t event) {
+ const char *description = xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION);
+ secnotice("xpc", "got event: %s", description);
+ });
+ xpc_connection_resume(connection);
+ return connection;
}
-static void securityd_port_lookup(void)
-{
- kern_return_t ret;
- mach_port_t bootstrap_lookup_port = MACH_PORT_NULL;
-
- /* bootstrap_port is not initialized on embedded */
- ret = task_get_bootstrap_port(mach_task_self(), &bootstrap_lookup_port);
- if (ret != KERN_SUCCESS) {
- secdebug("client", "task_get_bootstrap_port(): 0x%x: %s\n", ret, mach_error_string(ret));
- }
-
- ret = bootstrap_look_up(bootstrap_lookup_port,
- SECURITYSERVER_BOOTSTRAP_NAME,
- &securityd_port);
- if (ret != KERN_SUCCESS) {
- secdebug("client", "bootstrap_look_up(): 0x%x: %s\n", ret, mach_error_string(ret));
- }
+static xpc_connection_t sSecuritydConnection;
- pthread_key_create(&port_key, destroy_per_thread);
-
- int err = pthread_atfork(NULL, NULL, securityd_port_reset);
- if (err) {
- secdebug("client", "pthread_atfork(): %d: %s\n", errno, strerror(errno));
- }
+static xpc_connection_t securityd_connection(void) {
+ static dispatch_once_t once;
+ dispatch_once(&once, ^{
+ sSecuritydConnection = securityd_create_connection(NULL);
+ });
+ return sSecuritydConnection;
}
-union max_msg_size_union {
- union __RequestUnion__securityd_client_securityd_request_subsystem request;
-};
+// NOTE: This is not thread safe, but this SPI is for testing only.
+void SecServerSetMachServiceName(const char *name) {
+ // Make sure sSecXPCServer.queue exists.
+ securityd_connection();
-static uint8_t reply_buffer[sizeof(union max_msg_size_union) + MAX_TRAILER_SIZE];
-
-static boolean_t maybe_notification(mach_msg_header_t *request)
-{
- mach_no_senders_notification_t * notify = (mach_no_senders_notification_t *)request;
- if ((notify->not_header.msgh_id > MACH_NOTIFY_LAST) ||
- (notify->not_header.msgh_id < MACH_NOTIFY_FIRST))
- return false; /* if this is not a notification message */
-
- per_thread_t pt = (per_thread_t)pthread_getspecific(port_key);
- assert(pt);
- mach_msg_id_t notification_id = notify->not_header.msgh_id;
-
- switch(notification_id) {
- case MACH_NOTIFY_SEND_ONCE:
- /* our send-once right for a reply died in the hands of another */
- pt->notification = notification_id;
- CFRunLoopStop(CFRunLoopGetCurrent());
- break;
- default:
- secdebug("client", "unexpected notification %d", pt->notification);
- break;
- }
- return true;
-}
-
-extern boolean_t securityd_reply_server
-(mach_msg_header_t *InHeadP, mach_msg_header_t *OutHeadP);
-
-static void cfmachport_callback(CFMachPortRef port, void *msg, CFIndex size, void *info)
-{
- if (!maybe_notification((mach_msg_header_t *)msg))
- securityd_reply_server(msg, (mach_msg_header_t *)reply_buffer);
+ xpc_connection_t oldConection = sSecuritydConnection;
+ sSecuritydConnection = securityd_create_connection(name);
+ if (oldConection)
+ xpc_release(oldConection);
}
-static int32_t send_receive(uint32_t msg_id, const void *data_in, size_t length_in,
- void **data_out, size_t *length_out)
+xpc_object_t
+securityd_message_with_reply_sync(xpc_object_t message, CFErrorRef *error)
{
- pthread_once(&init_once, securityd_port_lookup);
+ xpc_object_t reply = NULL;
+ xpc_connection_t connection = securityd_connection();
- CFRunLoopRef rl = CFRunLoopGetCurrent();
-
- per_thread_t pt = (per_thread_t)pthread_getspecific(port_key);
- if (!pt) {
- pt = calloc(1, sizeof(*pt));
- SLIST_INIT(&pt->head);
- pt->port = CFMachPortCreate (NULL, cfmachport_callback, NULL, false);
- CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource(NULL, pt->port, 0/*order*/);
- CFRunLoopAddSource(rl, source, kSecRunLoopModeRef);
- CFRelease(source);
- pthread_setspecific(port_key, pt);
- }
-
- struct request req = { pt->seq_no++, msg_id, };
- SLIST_INSERT_HEAD(&pt->head, &req, next);
-
- int retries = 1;
- kern_return_t ret;
- do {
- /* 64 bits cast: worst case here is the client send a truncated query, which the server will reject */
- /* Debug check: The size of the request is of type natural_t.
- The following is correct as long as natural_t is unsigned int */
- assert(length_in<=UINT_MAX);
- ret = securityd_client_request(securityd_port,
- CFMachPortGetPort(pt->port), req.seq_no, msg_id,
- (void *)data_in, (unsigned int) length_in);
- secdebug("client", "securityd_client_request %d sent, retry %d, result %d\n",
- req.seq_no, retries, ret);
-
- if (!ret) {
- pt->notification = 0;
- while (!pt->notification && !req.done) {
- CFRunLoopRunInMode(kSecRunLoopModeRef, 10000, true);
- secdebug("client", "return from runloop, notification %d, request %d %sdone\n",
- pt->notification, req.seq_no, !req.done ? "not " : "");
- }
+ for (int try = 1; try <= 2; ++try) {
+ reply = xpc_connection_send_message_with_reply_sync(connection, message);
+ if (try == 1 && reply == XPC_ERROR_CONNECTION_INTERRUPTED) {
+ xpc_release(reply);
} else {
- secdebug("client", "failed to send request to securityd (err=%d).", ret);
+ break;
}
-
- } while ((ret || pt->notification) && retries--);
-
- SLIST_REMOVE_HEAD(&pt->head, next);
-
- struct request *next_head = SLIST_FIRST(&pt->head);
- if (next_head) {
- /* stop runloop if "new head" is also already done */
- if (pt->notification || next_head->done)
- CFRunLoopStop(rl);
}
- if (req.done) {
- if (data_out)
- *data_out = req.data;
- if (length_out)
- *length_out = req.length;
- return req.rcode;
+ if (xpc_get_type(reply) == XPC_TYPE_ERROR) {
+ CFIndex code = 0;
+ if (reply == XPC_ERROR_CONNECTION_INTERRUPTED || reply == XPC_ERROR_CONNECTION_INVALID)
+ code = kSecXPCErrorConnectionFailed;
+ else if (reply == XPC_ERROR_TERMINATION_IMMINENT)
+ code = kSecXPCErrorUnknown;
+ else
+ code = kSecXPCErrorUnknown;
+
+ char *conn_desc = xpc_copy_description(connection);
+ const char *description = xpc_dictionary_get_string(reply, XPC_ERROR_KEY_DESCRIPTION);
+ SecCFCreateErrorWithFormat(code, sSecXPCErrorDomain, NULL, error, NULL, CFSTR("%s: %s"), conn_desc, description);
+ free(conn_desc);
+ xpc_release(reply);
+ reply = NULL;
}
- return errSecNotAvailable;
+ return reply;
}
-kern_return_t securityd_server_reply(mach_port_t receiver,
- uint32_t seq_no, int32_t rcode,
- uint8_t *msg_data, mach_msg_type_number_t msg_length);
-
-kern_return_t securityd_server_reply(mach_port_t receiver,
- uint32_t seq_no, int32_t rcode,
- uint8_t *msg_data, mach_msg_type_number_t msg_length)
+xpc_object_t securityd_create_message(enum SecXPCOperation op, CFErrorRef* error)
{
- secdebug("client", "reply from port %d request_id %d data(%d,%p)\n",
- receiver, seq_no, msg_length, msg_data);
- per_thread_t pt = (per_thread_t)pthread_getspecific(port_key);
- assert(pt);
- struct request *req;
- SLIST_FOREACH(req, &pt->head, next) {
- if (req->seq_no == seq_no) {
- req->rcode = rcode;
- if (msg_length && msg_data) {
- req->data = malloc(msg_length);
- if (req->data) {
- req->length = msg_length;
- memcpy(req->data, msg_data, msg_length);
- } else
- req->length = 0;
- }
- req->done = true;
- /* if multiple requests were queued during nested invocations
- we wait until the last one inserted which is the deepest
- nested one is done */
- if (req == SLIST_FIRST(&pt->head))
- CFRunLoopStop(CFRunLoopGetCurrent());
- break;
- }
+ xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
+ if (message) {
+ xpc_dictionary_set_uint64(message, kSecXPCKeyOperation, op);
+ } else {
+ SecCFCreateError(kSecXPCErrorConnectionFailed, sSecXPCErrorDomain,
+ CFSTR("xpc_dictionary_create returned NULL"), NULL, error);
}
- return 0;
+ return message;
}
-OSStatus ServerCommandSendReceive(uint32_t id, CFTypeRef in, CFTypeRef *out)
-{
- CFDataRef data_in = NULL, data_out = NULL;
- void *bytes_out = NULL; size_t length_out = 0;
+// Return true if there is no error in message, return false and set *error if there is.
+bool securityd_message_no_error(xpc_object_t message, CFErrorRef *error) {
+ xpc_object_t xpc_error = xpc_dictionary_get_value(message, kSecXPCKeyError);
+ if (xpc_error == NULL)
+ return true;
- if (in) {
-#ifndef NDEBUG
- CFDataRef query_debug = CFPropertyListCreateXMLData(kCFAllocatorDefault, in);
- if (query_debug) {
- secdebug("client", "securityd query: %.*s\n",
- CFDataGetLength(query_debug), CFDataGetBytePtr(query_debug));
- CFReleaseSafe(query_debug);
- }
-#endif
- CFErrorRef error = NULL;
- data_in = CFPropertyListCreateData(kCFAllocatorDefault, in,
- kCFPropertyListBinaryFormat_v1_0,
- 0, &error);
- if (!data_in) {
- secdebug("client", "failed to encode query: %@", error);
- CFReleaseSafe(error);
- return errSecItemIllegalQuery;
- }
+ if (error) {
+ *error = SecCreateCFErrorWithXPCObject(xpc_error);
}
+ return false;
+}
- OSStatus status = send_receive(id, data_in ? CFDataGetBytePtr(data_in) : NULL,
- data_in ? CFDataGetLength(data_in) : 0, &bytes_out, &length_out);
- if (data_in) CFRelease(data_in);
-
- if (bytes_out && length_out) {
- data_out = CFDataCreateWithBytesNoCopy(kCFAllocatorDefault, bytes_out, length_out, kCFAllocatorMalloc);
- if (out)
- *out = CFPropertyListCreateFromXMLData(kCFAllocatorDefault, data_out, kCFPropertyListImmutable, NULL);
- CFRelease(data_out);
- }
- else
- if (!status && out)
- *out = NULL;
+bool securityd_send_sync_and_do(enum SecXPCOperation op, CFErrorRef *error,
+ bool (^add_to_message)(xpc_object_t message, CFErrorRef* error),
+ bool (^handle_response)(xpc_object_t response, CFErrorRef* error)) {
+ xpc_object_t message = securityd_create_message(op, error);
+ bool ok = false;
+ if (message) {
+ if (!add_to_message || add_to_message(message, error)) {
+ xpc_object_t response = securityd_message_with_reply_sync(message, error);
+ if (response) {
+ if (securityd_message_no_error(response, error)) {
+ ok = (!handle_response || handle_response(response, error));
+ }
+ xpc_release(response);
+ }
+ }
+ xpc_release(message);
+ }
- return status;
+ return ok;
}
+
/* vi:set ts=4 sw=4 et: */