#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;
static CFArrayRef gAccessGroups = NULL;
CFArrayRef SecAccessGroupsGetCurrent(void) {
#if !CHECK_ENTITLEMENTS
if (!gAccessGroups) {
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;
} *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;
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;
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:
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);
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 {
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) {
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 (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;
}