client.c   [plain text]


/*
 * 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: */