#import <CoreFoundation/CFRuntime.h>
#import <CoreFoundation/CFXPCBridge.h>
#import <xpc/xpc.h>
#import "heimcred.h"
#import "common.h"
#import "heimbase.h"
static void HeimItemNotify(xpc_object_t object);
static void HeimWakeupVersion(void);
#define HC_INIT() do { _HeimCredInit(); } while(0)
#define HC_INIT_ERROR(_error) \
do { \
if (_error) { \
*(_error) = NULL; \
} \
HC_INIT(); \
} while(0)
static void
_HeimCredInit(void)
{
static dispatch_once_t once;
dispatch_once(&once, ^{
_HeimCredInitCommon();
HeimCredCTX.conn = xpc_connection_create("com.apple.GSSCred", HeimCredCTX.queue);
xpc_connection_set_event_handler(HeimCredCTX.conn, ^(xpc_object_t object){ HeimItemNotify(object); });
xpc_connection_resume(HeimCredCTX.conn);
HeimWakeupVersion();
});
}
static void
HeimWakeupVersion(void)
{
if (HeimCredCTX.conn == NULL) abort();
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(request, "command", "wakeup");
xpc_dictionary_set_int64(request, "version", 0);
xpc_connection_send_message(HeimCredCTX.conn, request);
xpc_release(request);
}
#if 0
static NSDictionary *
heim_send_message_with_reply_sync(NSDictionary *message)
{
NSDictionary *reply = nil;
NSMutableData *request;
size_t length;
void *ptr;
xpc_object_t xrequest;
request = [NSMutableData data];
[HeimCredDecoder archiveRootObject:message toData:request];
xrequest = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_data(xrequest, "data", [request mutableBytes], [request length]);
xpc_object_t xreply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, xrequest);
ptr = xpc_dictionary_get_value(xreply, "data", &length);
NSData *data = [NSData dataWithBytes:ptr length:length];
reply = [HeimCredDecoder copyUnarchiveObjectWithData:data];
return reply;
}
#endif
static void
HeimItemNotify(xpc_object_t object)
{
CFUUIDRef uuid;
if (object == XPC_TYPE_ERROR)
return;
uuid = HeimCredCopyUUID(object, "uuid");
if (uuid)
CFDictionaryRemoveValue(HeimCredCTX.items, uuid);
CFRELEASE_NULL(uuid);
}
static HeimCredRef
HeimCredAddItem(xpc_object_t object)
{
CFDictionaryRef attributes = HeimCredMessageCopyAttributes(object, "attributes");
if (attributes == NULL)
return NULL;
CFUUIDRef uuid = CFDictionaryGetValue(attributes, kHEIMAttrUUID);
if (uuid == NULL) {
if (attributes)
CFRelease(attributes);
return NULL;
}
HeimCredRef cred = HeimCredCreateItem(uuid);
if (cred == NULL) {
if (attributes)
CFRelease(attributes);
return NULL;
}
cred->attributes = attributes;
dispatch_sync(HeimCredCTX.queue, ^{
CFDictionarySetValue(HeimCredCTX.items, cred->uuid, cred);
});
return cred;
}
HeimCredRef
HeimCredCreate(CFDictionaryRef attributes, CFErrorRef *error)
{
__block HeimCredRef cred;
HC_INIT_ERROR(error);
xpc_object_t xpcattrs = _CFXPCCreateXPCObjectFromCFObject(attributes);
if (xpcattrs == NULL)
return NULL;
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
heim_assert(request != NULL, "xpc_dictionary_create");
xpc_dictionary_set_string(request, "command", "create");
xpc_dictionary_set_value(request, "attributes", xpcattrs);
xpc_release(xpcattrs);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, request);
xpc_release(request);
if (reply == NULL)
return NULL;
heim_assert(reply != XPC_ERROR_CONNECTION_INTERRUPTED, "got XPC_ERROR_CONNECTION_INTERRUPTED");
heim_assert(reply != XPC_ERROR_CONNECTION_INVALID, "got XPC_ERROR_CONNECTION_INVALID");
cred = HeimCredAddItem(reply);
xpc_release(reply);
return cred;
}
CFUUIDRef
HeimCredGetUUID(HeimCredRef cred)
{
return cred->uuid;
}
HeimCredRef
HeimCredCopyFromUUID(CFUUIDRef uuid)
{
__block HeimCredRef cred;
HC_INIT();
dispatch_sync(HeimCredCTX.queue, ^{
cred = (HeimCredRef)CFDictionaryGetValue(HeimCredCTX.items, uuid);
if (cred == NULL) {
cred = HeimCredCreateItem(uuid);
CFDictionarySetValue(HeimCredCTX.items, uuid, cred);
} else {
CFRetain(cred);
}
});
return cred;
}
bool
HeimCredSetAttribute(HeimCredRef cred, CFTypeRef key, CFTypeID value, CFErrorRef *error)
{
const void *keys[1] = { (void *)key };
const void *values[1] = { (void *)value };
CFDictionaryRef attrs = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (attrs == NULL)
return false;
bool ret = HeimCredSetAttributes(cred, attrs, error);
CFRelease(attrs);
return ret;
}
bool
HeimCredSetAttributes(HeimCredRef cred, CFDictionaryRef attributes, CFErrorRef *error)
{
HC_INIT_ERROR(error);
xpc_object_t xpcquery = _CFXPCCreateXPCObjectFromCFObject(attributes);
if (xpcquery == NULL)
return false;
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(request, "command", "setattributes");
HeimCredSetUUID(request, "uuid", cred->uuid);
xpc_dictionary_set_value(request, "attributes", xpcquery);
xpc_release(xpcquery);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, request);
xpc_release(request);
if (reply == NULL)
return false;
dispatch_sync(HeimCredCTX.queue, ^{
CFRELEASE_NULL(cred->attributes);
cred->attributes = HeimCredMessageCopyAttributes(reply, "attributes");
});
xpc_release(reply);
return true;
}
CFTypeRef
HeimCredCopyAttribute(HeimCredRef cred, CFTypeRef attribute)
{
CFDictionaryRef attrs = HeimCredCopyAttributes(cred, NULL, NULL);
if (attrs == NULL)
return NULL;
CFTypeRef ref = CFDictionaryGetValue(attrs, attribute);
if (ref)
CFRetain(ref);
CFRelease(attrs);
return ref;
}
CFDictionaryRef
HeimCredCopyAttributes(HeimCredRef cred, CFSetRef attributes, CFErrorRef *error)
{
__block CFDictionaryRef attrs;
HC_INIT_ERROR(error);
dispatch_sync(HeimCredCTX.queue, ^{
if (cred->attributes)
CFRetain(cred->attributes);
attrs = cred->attributes;
});
if (attrs == NULL) {
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(request, "command", "fetch");
HeimCredSetUUID(request, "uuid", cred->uuid);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, request);
xpc_release(request);
if (reply == NULL)
return NULL;
dispatch_sync(HeimCredCTX.queue, ^{
CFRELEASE_NULL(cred->attributes);
attrs = cred->attributes = HeimCredMessageCopyAttributes(reply, "attributes");
if (attrs)
CFRetain(attrs);
});
xpc_release(reply);
}
return attrs;
}
static xpc_object_t
SendQueryCommand(const char *command, CFDictionaryRef query)
{
xpc_object_t xpcquery = _CFXPCCreateXPCObjectFromCFObject(query);
if (xpcquery == NULL)
return NULL;
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(request, "command", command);
xpc_dictionary_set_value(request, "query", xpcquery);
xpc_release(xpcquery);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, request);
xpc_release(request);
return reply;
}
CFArrayRef
HeimCredCopyQuery(CFDictionaryRef query)
{
HC_INIT();
xpc_object_t reply = SendQueryCommand("query", query);
if (reply == NULL)
return NULL;
CFMutableArrayRef result = CFArrayCreateMutable(NULL, 0, &kCFTypeArrayCallBacks);
if (result == NULL) {
xpc_release(reply);
return NULL;
}
xpc_object_t objects = xpc_dictionary_get_value(reply, "items");
if (objects && xpc_get_type(objects) == XPC_TYPE_ARRAY) {
xpc_array_apply(objects, ^bool(size_t index, xpc_object_t value) {
CFUUIDRef uuid =_CFXPCCreateCFObjectFromXPCObject(value);
if (uuid == NULL)
return (bool)true;
HeimCredRef cred = HeimCredCreateItem(uuid);
CFRelease(uuid);
if (cred) {
CFArrayAppendValue(result, cred);
CFRelease(cred);
}
return (bool)true;
});
}
xpc_release(reply);
return result;
}
bool
HeimCredDeleteQuery(CFDictionaryRef query, CFErrorRef *error)
{
HC_INIT_ERROR(error);
xpc_object_t reply = SendQueryCommand("delete", query);
if (reply == NULL)
return NULL;
xpc_release(reply);
return false;
}
static xpc_object_t
SendItemCommand(const char *command, CFUUIDRef uuid)
{
const void *keys[1] = { (void *)kHEIMAttrUUID };
const void *values[1] = { (void *)uuid };
CFDictionaryRef query = CFDictionaryCreate(NULL, keys, values, 1, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
xpc_object_t reply = SendQueryCommand(command, query);
CFRelease(query);
return reply;
}
void
HeimCredDeleteByUUID(CFUUIDRef uuid)
{
HC_INIT();
xpc_object_t reply = SendItemCommand("delete", uuid);
if (reply)
xpc_release(reply);
}
void
HeimCredDelete(HeimCredRef cred)
{
HeimCredDeleteByUUID(cred->uuid);
}
void
HeimCredRetainTransient(HeimCredRef cred)
{
HC_INIT();
xpc_object_t reply = SendItemCommand("retain-transient", cred->uuid);
if (reply)
xpc_release(reply);
}
void
HeimCredReleaseTransient(HeimCredRef cred)
{
HC_INIT();
xpc_object_t reply = SendItemCommand("release-transient", cred->uuid);
if (reply)
xpc_release(reply);
}
bool
HeimCredMove(CFUUIDRef from, CFUUIDRef to)
{
HC_INIT();
xpc_object_t request = xpc_dictionary_create(NULL, NULL, 0);
xpc_dictionary_set_string(request, "command", "move");
HeimCredSetUUID(request, "from", from);
HeimCredSetUUID(request, "to", to);
xpc_object_t reply = xpc_connection_send_message_with_reply_sync(HeimCredCTX.conn, request);
xpc_release(request);
xpc_release(reply);
return true;
}
CFDictionaryRef
HeimCredDoAuth(HeimCredRef cred, CFDictionaryRef input)
{
HC_INIT();
return NULL;
}