]> git.saurik.com Git - apple/security.git/blobdiff - sec/ipc/client.c
Security-55163.44.tar.gz
[apple/security.git] / sec / ipc / client.c
diff --git a/sec/ipc/client.c b/sec/ipc/client.c
new file mode 100644 (file)
index 0000000..241ee0c
--- /dev/null
@@ -0,0 +1,331 @@
+/*
+ * 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,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * 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 <CoreFoundation/CoreFoundation.h>
+#include <Security/SecItem.h>
+#include <Security/SecBasePriv.h>
+#include <Security/SecInternal.h>
+
+#include <security_utilities/debugging.h>
+#include "securityd_client.h"
+#include "securityd_req.h"
+
+#if NO_SERVER
+#define CHECK_ENTITLEMENTS 0
+#else
+#define CHECK_ENTITLEMENTS 1
+#endif
+
+struct securityd *gSecurityd;
+
+/* XXX Not thread safe right now. */
+static CFArrayRef gAccessGroups = NULL;
+
+CFArrayRef SecAccessGroupsGetCurrent(void) {
+#if !CHECK_ENTITLEMENTS
+    if (!gAccessGroups) {
+        /* Initialize gAccessGroups for tests. */
+        const void *agrps[] = {
+            CFSTR("test"),
+#if 1
+            CFSTR("apple"),
+            CFSTR("lockdown-identities"),
+#else
+            CFSTR("*"),
+#endif
+        };
+        gAccessGroups = CFArrayCreate(NULL, agrps,
+            sizeof(agrps) / sizeof(*agrps), &kCFTypeArrayCallBacks);
+    }
+#endif
+    return gAccessGroups;
+}
+
+void SecAccessGroupsSetCurrent(CFArrayRef accessGroups) {
+    if (accessGroups)
+        CFRetain(accessGroups);
+    CFReleaseSafe(gAccessGroups);
+    gAccessGroups = accessGroups;
+}
+
+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);
+}
+
+static void securityd_port_reset(void) 
+{
+    pthread_once_t temp = PTHREAD_ONCE_INIT;
+    init_once = temp;
+    securityd_port = MACH_PORT_NULL;
+}
+
+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));
+    }
+
+    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));
+    }
+}
+
+union max_msg_size_union {
+    union __RequestUnion__securityd_client_securityd_request_subsystem request;
+};
+
+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);
+}
+
+static int32_t send_receive(uint32_t msg_id, const void *data_in, size_t length_in,
+        void **data_out, size_t *length_out)
+{
+    pthread_once(&init_once, securityd_port_lookup);
+
+    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 " : "");
+            }
+        } else {
+            secdebug("client", "failed to send request to securityd (err=%d).", ret);
+        }
+
+    } 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;
+    }
+
+    return errSecNotAvailable;
+}
+
+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)
+{
+    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;
+        }
+    }
+    return 0;
+}
+
+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;
+
+    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;
+        }
+    }
+
+    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;
+
+    return status;     
+}
+
+/* vi:set ts=4 sw=4 et: */