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//  ckdxpcclient.c
26//  ckd-xpc
27//
28
29/*
30    This XPC service is essentially just a proxy to iCloud KVS, which exists since
31    the main security code cannot link against Foundation.
32
33    See sendTSARequestWithXPC in tsaSupport.c for how to call the service
34
35    The client of an XPC service does not get connection events, nor does it
36    need to deal with transactions.
37*/
38
39//------------------------------------------------------------------------------------------------
40
41#include <AssertMacros.h>
42
43#include <xpc/xpc.h>
44#include <CoreFoundation/CoreFoundation.h>
45#include <CoreFoundation/CFXPCBridge.h>
46#include <sysexits.h>
47#include <syslog.h>
48
49#include <utilities/debugging.h>
50#include <utilities/SecCFWrappers.h>
51
52#include "CKConstants.h"
53
54#define __CKDXPC_CLIENT_PRIVATE_INDIRECT__ 1
55#include "CKClient.h"
56
57
58#define pdebug(format...) secerror(format)
59
60#define verboseCKDDebugging 1
61
62#ifndef NDEBUG
63    #define xpdebug(format...) \
64        do {  \
65            if (verboseCKDDebugging) \
66                printf(format);  \
67        } while (0)
68#else
69    //empty
70    #define xpdebug(format...)
71#endif
72
73
74static xpc_connection_t serviceConnection = NULL;
75static dispatch_queue_t xpc_queue = NULL;
76static CloudKeychainReplyBlock itemsChangedBlock;
77
78static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event);
79static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error);
80
81// extern CFTypeRef _CFXPCCreateCFObjectFromXPCObject(xpc_object_t xo);
82// extern xpc_object_t _CFXPCCreateXPCObjectFromCFObject(CFTypeRef cf);
83
84// Debug
85static void describeXPCObject(char *prefix, xpc_object_t object);
86void describeXPCType(char *prefix, xpc_type_t xtype);
87
88static CFStringRef sErrorDomain = CFSTR("com.apple.security.cloudkeychain");
89
90enum {
91    kSOSObjectMallocFailed = 1,
92    kAddDuplicateEntry,
93    kSOSObjectNotFoundError = 1,
94    kSOSObjectCantBeConvertedToXPCObject,
95    kSOSOUnexpectedConnectionEvent,
96    kSOSOUnexpectedXPCEvent,
97    kSOSConnectionNotOpen
98};
99
100#define WANTXPCREPLY 0
101
102#pragma mark ----- utilities -----
103
104static CFErrorRef makeError(CFIndex which)
105{
106    CFDictionaryRef userInfo = NULL;
107    return CFErrorCreate(kCFAllocatorDefault, sErrorDomain, which, userInfo);
108}
109
110#pragma mark ----- SPI -----
111
112void initXPCConnection()
113{
114    pdebug("initXPCConnection\n");
115
116    xpc_queue = dispatch_queue_create(xpcServiceName, DISPATCH_QUEUE_SERIAL);
117
118    serviceConnection = xpc_connection_create_mach_service(xpcServiceName, xpc_queue, 0);
119
120//    serviceConnection = xpc_connection_create(xpcServiceName, xpc_queue);
121    pdebug("serviceConnection: %p\n", serviceConnection);
122
123    xpc_connection_set_event_handler(serviceConnection, ^(xpc_object_t event)
124    {
125        pdebug("xpc_connection_set_event_handler\n");
126        handle_xpc_event(serviceConnection, event);
127    });
128
129    xpc_connection_resume(serviceConnection);
130    xpc_retain(serviceConnection);
131}
132
133void closeXPCConnection()
134{
135    pdebug("closeXPCConnection\n");
136    xpc_release(serviceConnection);
137}
138
139void setItemsChangedBlock(CloudKeychainReplyBlock icb)
140{
141    if (icb != itemsChangedBlock)
142    {
143        if (itemsChangedBlock)
144            Block_release(itemsChangedBlock);
145        itemsChangedBlock = icb;
146        Block_copy(itemsChangedBlock);
147    }
148}
149
150// typedef void (^CloudKeychainReplyBlock)(CFDictionaryRef returnedValues, CFErrorRef error);
151
152static bool handle_xpc_event(const xpc_connection_t peer, xpc_object_t event)
153{
154    CFErrorRef localError = NULL;
155    pdebug(">>>>> handle_connection_event via event_handler <<<<<\n");
156    bool result = false;
157    if ((result = xpc_event_filter(peer, event, &localError)))
158    {
159        const char *operation = xpc_dictionary_get_string(event, kMessageKeyOperation);
160        if (!operation || strcmp(operation, kMessageOperationItemChanged))  // some op we don't care about
161        {
162            pdebug("operation: %s", operation);
163            return result;
164        }
165
166        xpc_object_t xrv = xpc_dictionary_get_value(event, kMessageKeyValue);
167        if (!xrv)
168        {
169            pdebug("xrv null for kMessageKeyValue");
170            return result;
171        }
172        describeXPCObject("xrv", xrv);
173
174        CFDictionaryRef returnedValues = _CFXPCCreateCFObjectFromXPCObject(xrv);
175        pdebug("returnedValues: %@", returnedValues);
176
177        if (itemsChangedBlock)
178            itemsChangedBlock(returnedValues, localError);
179    }
180    CFReleaseSafe(localError);
181
182    return result;
183}
184
185static bool xpc_event_filter(const xpc_connection_t peer, xpc_object_t event, CFErrorRef *error)
186{
187    // return true if the type is XPC_TYPE_DICTIONARY (and therefore something more to process)
188    pdebug("handle_connection_event\n");
189    xpc_type_t xtype = xpc_get_type(event);
190    describeXPCType("handle_xpc_event", xtype);
191    if (XPC_TYPE_CONNECTION == xtype)
192    {
193        pdebug("handle_xpc_event: XPC_TYPE_CONNECTION (unexpected)");
194        // The client of an XPC service does not get connection events
195        // For nwo, we log this and keep going
196        describeXPCObject("handle_xpc_event: XPC_TYPE_CONNECTION, obj : ", event);
197#if 0
198        if (error)
199            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
200        assert(true);
201#endif
202    }
203    else
204    if (XPC_TYPE_ERROR == xtype)
205    {
206        pdebug("default: xpc error: %s\n", xpc_dictionary_get_string(event, XPC_ERROR_KEY_DESCRIPTION));
207        if (error)
208            *error = makeError(kSOSOUnexpectedConnectionEvent); // FIX
209    }
210    else
211    if (XPC_TYPE_DICTIONARY == xtype)
212    {
213        pdebug("received dictionary event %p\n", event);
214        return true;
215    }
216    else
217    {
218        pdebug("default: unexpected connection event %p\n", event);
219        describeXPCObject("handle_xpc_event: obj : ", event);
220        if (error)
221            *error = makeError(kSOSOUnexpectedXPCEvent);
222    }
223    return false;
224}
225
226static void talkWithKVS(xpc_object_t message, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
227{
228    secerror("start");
229    __block CFErrorRef error = NULL;
230    __block CFTypeRef object = NULL;
231
232    dispatch_block_t callback = ^{
233            secerror("callback");
234            if (replyBlock)
235                replyBlock(object, error);
236    //        if (object)
237     //           CFRelease(object);
238            if (error)
239            {
240                secerror("callback error: %@", error);
241     //           CFRelease(error);
242            }
243            dispatch_release(processQueue);
244        };
245
246    require_action(serviceConnection, xit, error = makeError(kSOSConnectionNotOpen));
247    require_action(message, xit, error = makeError(kSOSObjectNotFoundError));
248    dispatch_retain(processQueue);
249    secerror("xpc_connection_send_message_with_reply called");
250
251    Block_copy(callback);
252
253//#if !WANTXPCREPLY
254 //   xpc_connection_send_message(serviceConnection, message);            // Send message; don't want a reply
255//#else
256    xpc_connection_send_message_with_reply(serviceConnection, message, xpc_queue, ^(xpc_object_t reply)
257        {
258            secerror("xpc_connection_send_message_with_reply handler called back");
259            if (xpc_event_filter(serviceConnection, reply, &error) && reply)
260            {
261                describeXPCObject("getValuesFromKVS: reply : ", reply);
262                xpc_object_t xrv = xpc_dictionary_get_value(reply, kMessageKeyValue);
263                if (xrv)
264                {
265                    describeXPCObject("talkWithKVS: xrv: ", xrv);
266                    /*
267                        * The given XPC object must be one that was previously returned by
268                        * _CFXPCCreateXPCMessageWithCFObject().
269                    */
270                    object = _CFXPCCreateCFObjectFromXPCObject(xrv);   // CF object is retained; release in callback
271                    secerror("converted CF object: %@", object);
272                }
273                else
274                    secerror("missing value reply");
275            }
276            dispatch_async(processQueue, callback);
277        });
278//#endif
279
280//sleep(5);   // DEBUG DEBUG FIX
281 //   xpc_release(message);
282    return;
283
284xit:
285    secerror("talkWithKVS error: %@", error);
286    if (replyBlock)
287        dispatch_async(processQueue, callback);
288}
289
290void putValuesWithXPC(CFDictionaryRef values, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
291{
292    CFErrorRef error = NULL;
293
294    require_action(values, xit, error = makeError(kSOSObjectNotFoundError));
295
296    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
297    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
298    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationPUTDictionary);
299
300    xpc_object_t xobject = _CFXPCCreateXPCObjectFromCFObject(values);
301    require_action(xobject, xit, error = makeError(kSOSObjectCantBeConvertedToXPCObject));
302    xpc_dictionary_set_value(message, kMessageKeyValue, xobject);
303
304    talkWithKVS(message, processQueue, replyBlock);
305    return;
306
307xit:
308    if (replyBlock)
309        replyBlock(NULL, error);
310}
311
312void synchronizeKVS(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
313{
314    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
315    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
316    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationSynchronize);
317    talkWithKVS(message, processQueue, replyBlock);
318}
319
320void clearAll(dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
321{
322    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
323    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
324    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore);
325    talkWithKVS(message, processQueue, replyBlock);
326}
327
328/*
329extern xpc_object_t xpc_create_reply_with_format(xpc_object_t original, const char * format, ...);
330            xpc_object_t reply = xpc_create_reply_with_format(event,
331                "{keychain-paths: %value, all-paths: %value, extensions: %value, keychain-home: %value}",
332                keychain_paths, all_paths, sandbox_extensions, home);
333*/
334
335void getValuesFromKVS(CFArrayRef keysToGet, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
336{
337    secerror("start");
338    CFErrorRef error = NULL;
339    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
340    xpc_object_t xkeysToGet = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
341
342    require_action(xkeysToGet, xit, error = makeError(kSOSObjectNotFoundError));
343
344    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToGet);
345
346    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
347    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
348    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationGETv2);
349    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
350
351    talkWithKVS(message, processQueue, replyBlock);
352
353    xpc_release(message);
354    return;
355
356xit:
357    if (replyBlock)
358        replyBlock(NULL, error);
359}
360
361void registerKeysForKVS(CFArrayRef keysToGet, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
362{
363    secerror("start");
364
365    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
366    xpc_object_t xkeysToRegister = keysToGet ? _CFXPCCreateXPCObjectFromCFObject(keysToGet) : xpc_null_create();
367    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToRegister);
368
369    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
370    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
371    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationRegisterKeysAndGet);
372    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
373
374    if (clientIdentifier)
375    {
376        char *clientid = CFStringToCString(clientIdentifier);
377        if (clientid)
378        {
379            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
380            free(clientid);
381        }
382    }
383
384    setItemsChangedBlock(replyBlock);
385    talkWithKVS(message, processQueue, replyBlock);
386
387    xpc_release(message);
388}
389
390void unregisterKeysForKVS(CFArrayRef keysToUnregister, CFStringRef clientIdentifier, dispatch_queue_t processQueue, CloudKeychainReplyBlock replyBlock)
391{
392#if NO_SERVERz
393    if (gCKD->unregisterKeys) {
394        return gCKD->unregisterKeys(...);
395    }
396#endif
397
398    secerror("start");
399
400    xpc_object_t xkeysOfInterest = xpc_dictionary_create(NULL, NULL, 0);
401    xpc_object_t xkeysToUnregister = keysToUnregister ? _CFXPCCreateXPCObjectFromCFObject(keysToUnregister) : xpc_null_create();
402    xpc_dictionary_set_value(xkeysOfInterest, kMessageKeyKeysToGet, xkeysToUnregister);
403
404    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
405    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
406    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationUnregisterKeys);
407    xpc_dictionary_set_value(message, kMessageKeyValue, xkeysOfInterest);
408
409    if (clientIdentifier)
410    {
411        char *clientid = CFStringToCString(clientIdentifier);
412        if (clientid)
413        {
414            xpc_dictionary_set_string(message, kMessageKeyClientIdentifier, clientid);
415            free(clientid);
416        }
417    }
418
419    talkWithKVS(message, processQueue, replyBlock);
420
421    xpc_release(message);
422}
423
424#pragma mark ----- CF-XPC Utilities -----
425
426
427#pragma mark ----- DEBUG Utilities -----
428
429//------------------------------------------------------------------------------------------------
430//          DEBUG only
431//------------------------------------------------------------------------------------------------
432
433void clearStore()
434{
435    xpc_object_t message = xpc_dictionary_create(NULL, NULL, 0);
436    xpc_dictionary_set_uint64(message, kMessageKeyVersion, kCKDXPCVersion);
437    xpc_dictionary_set_string(message, kMessageKeyOperation, kOperationClearStore);
438    xpc_connection_send_message(serviceConnection, message);  // Send message; don't wait for a reply
439    xpc_release(message);
440}
441
442void describeXPCObject(char *prefix, xpc_object_t object)
443{
444//#ifndef NDEBUG
445    // This is useful for debugging.
446    if (object)
447    {
448      char *desc = xpc_copy_description(object);
449    pdebug("%s%s\n", prefix, desc);
450    free(desc);
451    }
452    else
453        pdebug("%s<NULL>\n", prefix);
454//#endif
455}
456
457void describeXPCType(char *prefix, xpc_type_t xtype)
458{
459    /*
460        Add these as necessary:
461        XPC_TYPE_ENDPOINT
462        XPC_TYPE_NULL
463        XPC_TYPE_BOOL
464        XPC_TYPE_INT64
465        XPC_TYPE_UINT64
466        XPC_TYPE_DOUBLE
467        XPC_TYPE_DATE
468        XPC_TYPE_DATA
469        XPC_TYPE_STRING
470        XPC_TYPE_UUID
471        XPC_TYPE_FD
472        XPC_TYPE_SHMEM
473        XPC_TYPE_ARRAY
474    */
475
476#ifndef NDEBUG
477    // This is useful for debugging.
478    char msg[256]={0,};
479    if (XPC_TYPE_CONNECTION == xtype)
480        strcpy(msg, "XPC_TYPE_CONNECTION");
481    else if (XPC_TYPE_ERROR == xtype)
482        strcpy(msg, "XPC_TYPE_ERROR");
483    else if  (XPC_TYPE_DICTIONARY == xtype)
484        strcpy(msg, "XPC_TYPE_DICTIONARY");
485    else
486        strcpy(msg, "<unknown>");
487
488    pdebug("%s type:%s\n", prefix, msg);
489#endif
490}
491
492
493
494