1/*
2 * Copyright (c) 2012 Apple Computer, Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24
25/*
26 * SOSCloudTransport.c -  Implementation of the transport layer from CKBridge to SOSAccount/SOSCircle
27 */
28/*
29    This XPC service is essentially just a proxy to iCloud KVS, which exists since
30    the main security code cannot link against Foundation.
31
32    See sendTSARequestWithXPC in tsaSupport.c for how to call the service
33
34    The client of an XPC service does not get connection events, nor does it
35    need to deal with transactions.
36*/
37
38#include <SOSCloudTransport.h>
39#include <utilities/SecCFError.h>
40#include <dispatch/dispatch.h>
41#include <stdlib.h>
42
43static CFStringRef sErrorDomain = CFSTR("com.apple.security.sos.transport.error");
44
45enum
46{
47    kSOSObjectMallocFailed = 1,
48    kAddDuplicateEntry,
49    kSOSObjectNotFoundError = 1,
50    kSOSObjectCantBeConvertedToXPCObject,
51    kSOSOUnexpectedConnectionEvent,
52    kSOSOUnexpectedXPCEvent,
53    kSOSConnectionNotOpen
54};
55
56/* SOSCloudTransport, a statically initialized transport singleton. */
57struct SOSCloudTransport
58{
59    struct CloudTransport t;
60    unsigned version;
61    CFStringRef kvsID;
62    dispatch_once_t once;
63};
64
65#pragma mark ---------- Communication with XPC ----------
66
67
68//------------------------------------------------------------------------------------------------
69
70#include <AssertMacros.h>
71
72#include <xpc/xpc.h>
73#include <CoreFoundation/CoreFoundation.h>
74#include <CoreFoundation/CFXPCBridge.h>
75#include <sysexits.h>
76#include <syslog.h>
77
78#include <utilities/debugging.h>
79#include <utilities/SecCFWrappers.h>
80
81#include "CKConstants.h"
82
83#define zsecdebug(format...)  secerror(format)
84
85static xpc_connection_t serviceConnection = NULL;
86static dispatch_queue_t xpc_queue = NULL;
87static CloudKeychainReplyBlock itemsChangedBlock;
88
89static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event);
90static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error);
91
92// extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xo);
93// extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef cf);
94
95// Debug
96static void describeXPCObject(char *prefix, xpc_object_t object);
97static void describeXPCType(char *prefix, xpc_type_t xtype);
98
99
100
101#define WANTXPCREPLY 0
102
103#pragma mark ----- utilities -----
104
105static CFErrorRef makeError(CFIndex which)
106{
107    CFDictionaryRef userInfo = NULL;
108    return CFErrorCreate(kCFAllocatorDefault, sErrorDomain, which, userInfo);
109}
110
111#pragma mark ----- SPI -----
112
113static void initXPCConnection()
114{
115    zsecdebug("initXPCConnection\n");
116
117    xpc_queue = dispatch_queue_create(xpcServiceName, DISPATCH_QUEUE_SERIAL);
118
119    serviceConnection = xpc_connection_create_mach_service(xpcServiceName, xpc_queue, 0);
120
121//    serviceConnection = xpc_connection_create(xpcServiceName, xpc_queue);
122    zsecdebug("serviceConnection: %p\n", serviceConnection);
123
124    xpc_connection_set_event_handler(serviceConnection, ^(xpc_object_t event)
125    {
126        zsecdebug("xpc_connection_set_event_handler\n");
127        handle_xpc_event(serviceConnection, event);
128    });
129
130    xpc_connection_resume(serviceConnection);
131    xpc_retain(serviceConnection);
132}
133
134#if 0   // unused
135static void closeXPCConnection()
136{
137    zsecdebug("closeXPCConnection\n");
138    xpc_release(serviceConnection);
139}
140#endif
141
142static void setItemsChangedBlock(CloudKeychainReplyBlock icb)
143{
144    if (icb != itemsChangedBlock)
145    {
146        if (itemsChangedBlock)
147            Block_release(itemsChangedBlock);
148        itemsChangedBlock = icb;
149        Block_copy(itemsChangedBlock);
150    }
151}
152
153// typedef void (^CloudKeychainReplyBlock)(CFDictionaryRef returnedValues, CFErrorRef error);
154
155static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event)
156{
157    CFErrorRef localError = NULL;
158    zsecdebug(">>>>> handle_connection_event via event_handler <<<<<\n");
159    bool result = false;
160    if ((result = xpc_event_filter(peer, event, &localError)))
161    {
162        const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
163        if (!operation || strcmp(operation, kMessageOperationItemChanged))  // some op we don't care about
164        {
165            zsecdebug("operation: %s", operation);
166            return result;
167        }
168
169        xpc_object_t xrv = xpc_dictionary_get_value(event, kMessageKeyValue);
170        if (!xrv)
171        {
172            zsecdebug("xrv null for kMessageKeyValue");
173            return result;
174        }
175        describeXPCObject("xrv", xrv);
176
177        CFDictionaryRef returnedValues = _CFXPCCreateCFObjectFromXPCObject(xrv);
178        zsecdebug("returnedValues: %@", returnedValues);
179
180        if (itemsChangedBlock)
181            itemsChangedBlock(returnedValues, localError);
182    }
183    CFReleaseSafe(localError);
184
185    return result;
186}
187
188static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error)
189{
190    // return true if the type is XPC_TYPE_DICTIONARY (and therefore something more to process)
191    zsecdebug("handle_connection_event\n");
192    xpc_type_t xtype = xpc_get_type(event);
193    describeXPCType("handle_xpc_event", xtype);
194    if (XPC_TYPE_CONNECTION == xtype)
195    {
196        zsecdebug("handle_xpc_event: XPC_TYPE_CONNECTION (unexpected)");
197        // The client of an XPC service does not get connection events
198        // For nwo, we log this and keep going
199        describeXPCObject("handle_xpc_event: XPC_TYPE_CONNECTION, obj : ", event);
200#if 0
201        if (error)
202            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
203        assert(true);
204#endif
205    }
206    else
207    if (XPC_TYPE_ERROR == xtype)
208    {
209        zsecdebug("default: xpc error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
210        if (error)
211            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
212    }
213    else
214    if (XPC_TYPE_DICTIONARY == xtype)
215    {
216        zsecdebug("received dictionary event %p\n", event);
217        return true;
218    }
219    else
220    {
221        zsecdebug("default: unexpected connection event %p\n", event);
222        describeXPCObject("handle_xpc_event: obj : ", event);
223        if (error)
224            *error = makeError(kSOSOUnexpectedXPCEvent);
225    }
226    return false;
227}
228
229static void talkWithKVS(xpc_object_t message, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
230{
231    secerror("start");
232    __block CFErrorRef error = NULL;
233    __block CFTypeRef object = NULL;
234
235    dispatch_block_t callback = ^{
236            secerror("callback");
237            if (replyBlock)
238                replyBlock(object, error);
239    //        if (object)
240     //           CFRelease(object);
241            if (error)
242            {
243                secerror("callback error: %@", error);
244     //           CFRelease(error);
245            }
246            dispatch_release(processQueue);
247        };
248
249    require_action(serviceConnection, xit, error = makeError(kSOSConnectionNotOpen));
250    require_action(message, xit, error = makeError(kSOSObjectNotFoundError));
251    dispatch_retain(processQueue);
252    secerror("xpc_connection_send_message_with_reply called");
253
254    Block_copy(callback);   // TODO -- this should not be needed, I think
255
256//#if !WANTXPCREPLY
257 //   xpc_connection_send_message(serviceConnection, message);            // Send message; don't want a reply
258//#else
259    xpc_connection_send_message_with_reply(serviceConnection, message, xpc_queue, ^(xpc_object_t reply)
260        {
261            secerror("xpc_connection_send_message_with_reply handler called back");
262            if (xpc_event_filter(serviceConnection, reply, &error) && reply)
263            {
264                describeXPCObject("getValuesFromKVS: reply : ", reply);
265                xpc_object_t xrv = xpc_dictionary_get_value(reply, kMessageKeyValue);
266                if (xrv)
267                {
268                    describeXPCObject("talkWithKVS: xrv: ", xrv);
269                    /*
270                        * The given XPC object must be one that was previously returned by
271                        * _CFXPCCreateXPCMessageWithCFObject().
272                    */
273                    object = _CFXPCCreateCFObjectFromXPCObject(xrv);   // CF object is retained; release in callback
274                    secerror("converted CF object: %@", object);
275                }
276                else
277                    secerror("missing value reply");
278            }
279            dispatch_async(processQueue, callback);
280        });
281//#endif
282
283//sleep(5);   // DEBUG DEBUG FIX
284 //   xpc_release(message);
285    return;
286
287xit:
288    secerror("talkWithKVS error: %@", error);
289    if (replyBlock)
290        dispatch_async(processQueue, callback);
291}
292
293static void putValuesWithXPC(CFDictionaryRef values, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
294{
295    CFErrorRef error = NULL;
296
297    require_action(values, xit, error = makeError(kSOSObjectNotFoundError));
298
299    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
300    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
301    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationPUTDictionary);
302
303    xpc_object_t xobject = _CFXPCCreateXPCObjectFromCFObject(values);
304    require_action(xobject, xit, error = makeError(kSOSObjectCantBeConvertedToXPCObject));
305    xpc_dictionary_set_value(message, kMessageKeyValue, xobject);
306
307    talkWithKVS(message, processQueue, replyBlock);
308    return;
309
310xit:
311    if (replyBlock)
312        replyBlock(NULL, error);
313}
314
315static void synchronizeKVS(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
316{
317    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
318    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
319    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationSynchronize);
320    talkWithKVS(message, processQueue, replyBlock);
321}
322
323static void clearAll(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
324{
325    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
326    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
327    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore);
328    talkWithKVS(message, processQueue, replyBlock);
329}
330
331static void getValuesFromKVS(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
332{
333    secerror("start");
334    CFErrorRef error = NULL;
335    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
336    xpc_object_t xkeysToGet = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
337
338    require_action(xkeysToGet, xit, error = makeError(kSOSObjectNotFoundError));
339
340    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToGet);
341
342    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
343    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
344    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationGETv2);
345    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
346
347    talkWithKVS(message, processQueue, replyBlock);
348
349    xpc_release(message);
350    return;
351
352xit:
353    if (replyBlock)
354        replyBlock(NULL, error);
355}
356
357static void registerKeysForKVS(CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
358{
359    secerror("start");
360
361    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
362    xpc_object_t xkeysToRegister = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
363    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToRegister);
364
365    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
366    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
367    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRegisterKeysAndGet);
368    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
369
370    if (clientIdentifier)
371    {
372        char *clientid = CFStringToCString(clientIdentifier);
373        if (clientid)
374        {
375            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
376            free(clientid);
377        }
378    }
379
380    setItemsChangedBlock(replyBlock);
381    talkWithKVS(message, processQueue, replyBlock);
382
383    xpc_release(message);
384}
385
386static void unregisterKeysForKVS(CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
387{
388#if NO_SERVERz
389    if (gCKD->unregisterKeys) {
390        return gCKD->unregisterKeys(...);
391    }
392#endif
393
394    secerror("start");
395
396    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
397    xpc_object_t xkeysToUnregister = keysToUnregister ? _CFXPCCreateXPCObjectFromCFObject(keysToUnregister) : xpc_null_create();
398    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToUnregister);
399
400    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
401    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
402    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationUnregisterKeys);
403    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
404
405    if (clientIdentifier)
406    {
407        char *clientid = CFStringToCString(clientIdentifier);
408        if (clientid)
409        {
410            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
411            free(clientid);
412        }
413    }
414
415    talkWithKVS(message, processQueue, replyBlock);
416
417    xpc_release(message);
418}
419
420#pragma mark ----- DEBUG Utilities -----
421
422//------------------------------------------------------------------------------------------------
423//          DEBUG only
424//------------------------------------------------------------------------------------------------
425
426static void describeXPCObject(char *prefix, xpc_object_t object)
427{
428//#ifndef NDEBUG
429    // This is useful for debugging.
430    if (object)
431    {
432      char *desc = xpc_copy_description(object);
433    zsecdebug("%s%s\n", prefix, desc);
434    free(desc);
435    }
436    else
437        zsecdebug("%s<NULL>\n", prefix);
438//#endif
439}
440
441static void describeXPCType(char *prefix, xpc_type_t xtype)
442{
443    /*
444        Add these as necessary:
445        XPC_TYPE_ENDPOINT
446        XPC_TYPE_NULL
447        XPC_TYPE_BOOL
448        XPC_TYPE_INT64
449        XPC_TYPE_UINT64
450        XPC_TYPE_DOUBLE
451        XPC_TYPE_DATE
452        XPC_TYPE_DATA
453        XPC_TYPE_STRING
454        XPC_TYPE_UUID
455        XPC_TYPE_FD
456        XPC_TYPE_SHMEM
457        XPC_TYPE_ARRAY
458    */
459
460#ifndef NDEBUG
461    // This is useful for debugging.
462    char msg[256]={0,};
463    if (XPC_TYPE_CONNECTION == xtype)
464        strcpy(msg, "XPC_TYPE_CONNECTION");
465    else if (XPC_TYPE_ERROR == xtype)
466        strcpy(msg, "XPC_TYPE_ERROR");
467    else if  (XPC_TYPE_DICTIONARY == xtype)
468        strcpy(msg, "XPC_TYPE_DICTIONARY");
469    else
470        strcpy(msg, "<unknown>");
471
472    zsecdebug("%s type:%s\n", prefix, msg);
473#endif
474}
475
476#pragma mark ---------- SOSCloudTransport ----------
477
478static void SOSCloudTransportPut(SOSCloudTransportRef transport, CFDictionaryRef valuesToPut, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
479{
480    secerror("%@", valuesToPut);
481    putValuesWithXPC(valuesToPut, processQueue, replyBlock);
482}
483
484static void SOSCloudTransportGet(SOSCloudTransportRef transport, CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
485{
486//    struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport;
487    /* Get from KVS */
488    secerror("%@", keysToGet);
489    getValuesFromKVS(keysToGet, processQueue, replyBlock);
490}
491
492static void SOSCloudTransportRegisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
493{
494    /* RegisterKeys with KVS */
495    registerKeysForKVS(keysToGet, clientIdentifier, processQueue, replyBlock);
496}
497
498static void SOSCloudTransportUnregisterKeys(SOSCloudTransportRef transport, CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
499{
500    /* unregister from KVS */
501    unregisterKeysForKVS(keysToUnregister, clientIdentifier, processQueue, replyBlock);
502}
503
504static void SOSCloudTransportGetAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
505{
506    /* Get from KVS */
507    getValuesFromKVS(NULL, processQueue, replyBlock);
508}
509
510static void SOSCloudTransportSync(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
511{
512//    struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport;
513    /* Sync KVS */
514    synchronizeKVS(processQueue, replyBlock);
515}
516
517static void SOSCloudTransportClearAll(SOSCloudTransportRef transport, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
518{
519//    struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport;
520    /* clear KVS */
521    clearAll(processQueue, replyBlock);
522}
523
524static void SOSCloudTransportSetItemsChangedBlock(SOSCloudTransportRef transport, CloudKeychainReplyBlock itemsChangedBlock)
525{
526//    struct SOSCloudTransport *t = (struct SOSCloudTransport *)transport;
527    /* clear KVS */
528    setItemsChangedBlock(itemsChangedBlock);
529}
530
531static void SOSCloudTransportInit(void *ctx)
532{
533    struct SOSCloudTransport *st = (struct SOSCloudTransport *)ctx;
534    st->t.put = SOSCloudTransportPut;
535    st->t.registerKeys = SOSCloudTransportRegisterKeys;
536    st->t.unregisterKeys = SOSCloudTransportUnregisterKeys;
537    st->t.get = SOSCloudTransportGet;
538    st->t.getAll = SOSCloudTransportGetAll;
539    st->t.synchronize = SOSCloudTransportSync;
540    st->t.clearAll = SOSCloudTransportClearAll;
541    st->t.setItemsChangedBlock = SOSCloudTransportSetItemsChangedBlock;
542
543    if (st->kvsID)
544        CFRetain(st->kvsID);
545
546    st->version = 0;
547    initXPCConnection();
548}
549
550SOSCloudTransportRef SOSCloudTransportDefaultTransport(CFStringRef kvsID, uint32_t options)
551{
552    static struct SOSCloudTransport cloudTransport = {};
553    cloudTransport.kvsID = kvsID;
554    dispatch_once_f(&cloudTransport.once, &cloudTransport, SOSCloudTransportInit);
555    return &cloudTransport.t;
556}
557
558
559