2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
10 #include "SOSAccountPriv.h"
11 #include <SecureObjectSync/SOSPeerInfoCollections.h>
12 #include <SecureObjectSync/SOSTransportCircle.h>
13 #include <SecureObjectSync/SOSTransportMessage.h>
14 #include <SecureObjectSync/SOSKVSKeys.h>
15 #include <SecureObjectSync/SOSTransportKeyParameter.h>
16 #include <SecureObjectSync/SOSTransportKeyParameterKVS.h>
17 #include <SecureObjectSync/SOSEngine.h>
18 #include <SecureObjectSync/SOSPeerCoder.h>
20 CFGiblisWithCompareFor(SOSAccount
);
23 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a
)
28 require(a
->factory
, xit
);
29 CFArrayRef circle_names
= a
->factory
->copy_names(a
->factory
);
30 require(circle_names
, xit
);
31 CFArrayForEach(circle_names
, ^(const void*name
) {
33 SOSAccountEnsureCircle(a
, (CFStringRef
)name
, NULL
);
36 CFReleaseNull(circle_names
);
44 SOSAccountRef
SOSAccountCreateBasic(CFAllocatorRef allocator
,
45 CFDictionaryRef gestalt
,
46 SOSDataSourceFactoryRef factory
) {
47 SOSAccountRef a
= CFTypeAllocate(SOSAccount
, struct __OpaqueSOSAccount
, allocator
);
49 a
->queue
= dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL
);
51 a
->gestalt
= CFRetainSafe(gestalt
);
53 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
54 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
56 a
->factory
= factory
; // We adopt the factory. kthanksbai.
58 a
->change_blocks
= CFArrayCreateMutableForCFTypes(allocator
);
60 a
->departure_code
= kSOSNeverAppliedToCircle
;
62 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
63 a
->circle_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
64 a
->message_transports
= CFDictionaryCreateMutableForCFTypes(allocator
);
72 bool SOSAccountUpdateGestalt(SOSAccountRef account
, CFDictionaryRef new_gestalt
)
74 if (CFEqual(new_gestalt
, account
->gestalt
))
76 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
77 if (SOSFullPeerInfoUpdateGestalt(full_peer
, new_gestalt
, NULL
)) {
78 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
),
79 NULL
, ^(SOSCircleRef circle_to_change
) {
80 return SOSCircleUpdatePeerInfo(circle_to_change
, SOSFullPeerInfoGetPeerInfo(full_peer
));
85 CFRetainAssign(account
->gestalt
, new_gestalt
);
89 SOSAccountRef
SOSAccountCreate(CFAllocatorRef allocator
,
90 CFDictionaryRef gestalt
,
91 SOSDataSourceFactoryRef factory
) {
92 SOSAccountRef a
= SOSAccountCreateBasic(allocator
, gestalt
, factory
);
94 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
96 SOSAccountEnsureFactoryCircles(a
);
101 static void SOSAccountDestroy(CFTypeRef aObj
) {
102 SOSAccountRef a
= (SOSAccountRef
) aObj
;
104 // We don't own the factory, meerly have a reference to the singleton
108 CFReleaseNull(a
->gestalt
);
109 CFReleaseNull(a
->circle_identities
);
110 CFReleaseNull(a
->circles
);
111 CFReleaseNull(a
->retired_peers
);
113 a
->user_public_trusted
= false;
114 CFReleaseNull(a
->user_public
);
115 CFReleaseNull(a
->user_key_parameters
);
117 SOSAccountPurgePrivateCredential(a
);
118 CFReleaseNull(a
->previous_public
);
120 a
->departure_code
= kSOSNeverAppliedToCircle
;
121 CFReleaseNull(a
->message_transports
);
122 CFReleaseNull(a
->key_transport
);
123 CFReleaseNull(a
->circle_transports
);
124 dispatch_release(a
->queue
);
127 void SOSAccountSetToNew(SOSAccountRef a
) {
128 CFAllocatorRef allocator
= CFGetAllocator(a
);
129 CFReleaseNull(a
->circle_identities
);
130 CFReleaseNull(a
->circles
);
131 CFReleaseNull(a
->retired_peers
);
133 CFReleaseNull(a
->user_key_parameters
);
134 CFReleaseNull(a
->user_public
);
135 CFReleaseNull(a
->previous_public
);
136 CFReleaseNull(a
->_user_private
);
138 CFReleaseNull(a
->key_transport
);
139 CFReleaseNull(a
->circle_transports
);
140 CFReleaseNull(a
->message_transports
);
142 a
->user_public_trusted
= false;
143 a
->departure_code
= kSOSNeverAppliedToCircle
;
144 a
->user_private_timer
= 0;
145 a
->lock_notification_token
= 0;
151 // update_interest_block;
154 a
->circles
= CFDictionaryCreateMutableForCFTypes(allocator
);
155 a
->circle_identities
= CFDictionaryCreateMutableForCFTypes(allocator
);
156 a
->retired_peers
= CFDictionaryCreateMutableForCFTypes(allocator
);
158 a
->key_transport
= (SOSTransportKeyParameterRef
)SOSTransportKeyParameterKVSCreate(a
, NULL
);
159 a
->circle_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
160 a
->message_transports
= (CFMutableDictionaryRef
)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
162 SOSAccountEnsureFactoryCircles(a
);
166 static CFStringRef
SOSAccountCopyDescription(CFTypeRef aObj
) {
167 SOSAccountRef a
= (SOSAccountRef
) aObj
;
169 return CFStringCreateWithFormat(NULL
, NULL
, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a
, a
->gestalt
, a
->circles
, a
->circle_identities
);
172 static Boolean
SOSAccountCompare(CFTypeRef lhs
, CFTypeRef rhs
)
174 SOSAccountRef laccount
= (SOSAccountRef
) lhs
;
175 SOSAccountRef raccount
= (SOSAccountRef
) rhs
;
177 return CFEqual(laccount
->gestalt
, raccount
->gestalt
)
178 && CFEqual(laccount
->circles
, raccount
->circles
)
179 && CFEqual(laccount
->circle_identities
, raccount
->circle_identities
);
183 dispatch_queue_t
SOSAccountGetQueue(SOSAccountRef account
) {
184 return account
->queue
;
187 CFDictionaryRef
SOSAccountGetMessageTransports(SOSAccountRef account
){
188 return account
->message_transports
;
192 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account
){
193 account
->user_public_trusted
= true;
196 CFArrayRef
SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account
, CFAllocatorRef allocator
, CFErrorRef
* error
)
198 CFMutableArrayRef result
= CFArrayCreateMutableForCFTypes(allocator
);
200 CFDictionaryForEach(account
->circle_identities
, ^(const void *key
, const void *value
) {
201 SOSFullPeerInfoRef fpi
= (SOSFullPeerInfoRef
) value
;
203 CFArrayAppendValue(result
, SOSFullPeerInfoGetPeerInfo(fpi
));
209 static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account
, SOSCircleRef circle
) {
210 CFErrorRef error
= NULL
;
211 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, SOSCircleGetName(circle
), &error
);
212 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
213 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
214 return SOSCircleHasPeerWithID(circle
, myPeerID
, &error
);
217 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account
, CFStringRef circleName
, CFStringRef peerID
) {
218 CFErrorRef error
= NULL
;
219 SOSFullPeerInfoRef myfpi
= SOSAccountGetMyFullPeerInCircleNamedIfPresent(account
, circleName
, &error
);
223 SOSPeerInfoRef mypi
= SOSFullPeerInfoGetPeerInfo(myfpi
);
224 CFStringRef myPeerID
= SOSPeerInfoGetPeerID(mypi
);
225 return CFEqualSafe(myPeerID
, peerID
);
228 bool SOSAccountSyncWithAllPeers(SOSAccountRef account
, CFErrorRef
*error
)
230 __block
bool result
= true;
232 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
234 if (SOSAccountThisDeviceCanSyncWithCircle(account
, circle
)) {
235 CFStringRef circleName
= SOSCircleGetName(circle
);
236 SOSTransportMessageRef thisPeerTransport
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
238 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
240 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
241 // Figure out transport for peer; for now we always use KVS
242 CFStringRef peerID
= SOSPeerInfoGetPeerID(peer
);
243 if (!SOSAccountIsThisPeerIDMe(account
, circleName
, peerID
)) {
244 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circleName
), peerID
);
248 result
&= SOSTransportMessageSyncWithPeers(thisPeerTransport
, circleToPeerIDs
, error
);
250 CFReleaseNull(circleToPeerIDs
);
254 // Tell each transport to sync with its collection of peers we know we should sync with.
258 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers
, 1);
264 bool SOSAccountCleanupAfterPeer(SOSAccountRef account
, size_t seconds
, SOSCircleRef circle
,
265 SOSPeerInfoRef cleanupPeer
, CFErrorRef
* error
)
267 bool success
= false;
268 if(!SOSAccountIsMyPeerActiveInCircle(account
, circle
, NULL
)) return true;
270 SOSPeerInfoRef myPeerInfo
= SOSAccountGetMyPeerInCircle(account
, circle
, error
);
271 require(myPeerInfo
, xit
);
273 CFStringRef cleanupPeerID
= SOSPeerInfoGetPeerID(cleanupPeer
);
274 CFStringRef circle_name
= SOSCircleGetName(circle
);
276 if (CFEqual(cleanupPeerID
, SOSPeerInfoGetPeerID(myPeerInfo
))) {
277 CFErrorRef destroyError
= NULL
;
278 if (!SOSAccountDestroyCirclePeerInfo(account
, circle
, &destroyError
)) {
279 secerror("Unable to destroy peer info: %@", destroyError
);
281 CFReleaseSafe(destroyError
);
283 account
->departure_code
= kSOSWithdrewMembership
;
288 CFMutableDictionaryRef circleToPeerIDs
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
289 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs
, circle_name
), cleanupPeerID
);
292 CFErrorRef localError
= NULL
;
293 SOSTransportMessageRef tMessage
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
294 if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage
, circleToPeerIDs
, &localError
)) {
295 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID
, localError
);
297 CFReleaseNull(localError
);
298 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(circle
));
299 if(SOSPeerInfoRetireRetirementTicket(seconds
, cleanupPeer
)) {
300 if (!SOSTransportCircleExpireRetirementRecords(tCircle
, circleToPeerIDs
, &localError
)) {
301 secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID
, localError
);
304 CFReleaseNull(localError
);
306 CFReleaseNull(circleToPeerIDs
);
312 bool SOSAccountCleanupRetirementTickets(SOSAccountRef account
, size_t seconds
, CFErrorRef
* error
) {
313 CFMutableDictionaryRef retirements_to_remove
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
314 CFDictionaryRef original_retired_peers
= account
->retired_peers
;
315 __block
bool success
= true;
316 account
->retired_peers
= CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault
);
318 CFDictionaryForEach(original_retired_peers
, ^(const void *key
, const void *value
) {
319 if (isString(key
) && isDictionary(value
)) {
320 CFStringRef circle_name
= key
;
321 __block CFMutableDictionaryRef still_active_circle_retirements
= NULL
;
322 CFDictionaryForEach((CFMutableDictionaryRef
) value
, ^(const void *key
, const void *value
) {
323 if (isString(key
) && isData(value
)) {
324 CFStringRef retired_peer_id
= (CFStringRef
) key
;
325 SOSPeerInfoRef retired_peer
= SOSPeerInfoCreateFromData(kCFAllocatorDefault
, NULL
, (CFDataRef
) value
);
326 if (retired_peer
&& SOSPeerInfoIsRetirementTicket(retired_peer
) && CFEqual(retired_peer_id
, SOSPeerInfoGetPeerID(retired_peer
))) {
327 // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
328 // if not, clear any recordings of his retirement from our transport.
329 if (SOSAccountIsActivePeerInCircleNamed(account
, circle_name
, retired_peer_id
, NULL
) ||
330 !SOSPeerInfoRetireRetirementTicket(seconds
, retired_peer
)) {
331 // He's still around or not expired. Keep record.
332 if (still_active_circle_retirements
== NULL
) {
333 still_active_circle_retirements
= CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account
->retired_peers
, circle_name
);
335 CFDictionarySetValue(still_active_circle_retirements
, retired_peer_id
, value
);
337 CFMutableArrayRef retirements
= CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove
, circle_name
);
338 CFArrayAppendValue(retirements
, retired_peer_id
);
341 CFReleaseNull(retired_peer
);
345 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, circle_name
);
346 success
&= SOSTransportCircleExpireRetirementRecords(tCircle
, retirements_to_remove
, error
);
350 CFReleaseNull(original_retired_peers
);
351 CFReleaseNull(retirements_to_remove
);
356 bool SOSAccountScanForRetired(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
*error
) {
357 __block CFMutableDictionaryRef circle_retirees
= (CFMutableDictionaryRef
) CFDictionaryGetValue(account
->retired_peers
, SOSCircleGetName(circle
));
359 SOSCircleForEachRetiredPeer(circle
, ^(SOSPeerInfoRef peer
) {
360 CFStringRef peer_id
= SOSPeerInfoGetPeerID(peer
);
361 if(!circle_retirees
|| !CFDictionaryGetValueIfPresent(circle_retirees
, peer_id
, NULL
)) {
362 if (!circle_retirees
) {
363 circle_retirees
= CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account
->retired_peers
, SOSCircleGetName(circle
));
365 CFDataRef value
= SOSPeerInfoCopyEncodedData(peer
, NULL
, NULL
);
367 CFDictionarySetValue(circle_retirees
, peer_id
, value
);
368 SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, peer
, error
);
370 CFReleaseSafe(value
);
376 SOSCircleRef
SOSAccountCloneCircleWithRetirement(SOSAccountRef account
, SOSCircleRef starting_circle
, CFErrorRef
*error
) {
377 CFStringRef circle_to_mod
= SOSCircleGetName(starting_circle
);
379 SOSCircleRef new_circle
= SOSCircleCopyCircle(NULL
, starting_circle
, error
);
380 if(!new_circle
) return NULL
;
382 CFDictionaryRef circle_retirements
= CFDictionaryGetValue(account
->retired_peers
, circle_to_mod
);
384 if (isDictionary(circle_retirements
)) {
385 CFDictionaryForEach(circle_retirements
, ^(const void* id
, const void* value
) {
387 SOSPeerInfoRef pi
= SOSPeerInfoCreateFromData(NULL
, error
, (CFDataRef
) value
);
388 if (pi
&& CFEqualSafe(id
, SOSPeerInfoGetPeerID(pi
))) {
389 SOSCircleUpdatePeerInfo(new_circle
, pi
);
396 if(SOSCircleCountPeers(new_circle
) == 0) {
397 SOSCircleResetToEmpty(new_circle
, NULL
);
404 // MARK: Circle Membership change notificaion
407 void SOSAccountAddChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
408 CFArrayAppendValue(a
->change_blocks
, changeBlock
);
411 void SOSAccountRemoveChangeBlock(SOSAccountRef a
, SOSAccountCircleMembershipChangeBlock changeBlock
) {
412 CFArrayRemoveAllValue(a
->change_blocks
, changeBlock
);
415 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a
, CFStringRef ds_name
, SOSAccountSyncablePeersBlock changeBlock
) {
416 if (!changeBlock
) return;
418 CFRetainSafe(ds_name
);
419 SOSAccountCircleMembershipChangeBlock block_to_register
= ^void (SOSCircleRef new_circle
,
420 CFSetRef added_peers
, CFSetRef removed_peers
,
421 CFSetRef added_applicants
, CFSetRef removed_applicants
) {
423 if (!CFEqualSafe(SOSCircleGetName(new_circle
), ds_name
))
426 SOSPeerInfoRef myPi
= SOSAccountGetMyPeerInCircle(a
, new_circle
, NULL
);
427 CFStringRef myPi_id
= myPi
? SOSPeerInfoGetPeerID(myPi
) : NULL
;
429 CFMutableArrayRef peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
430 CFMutableArrayRef added_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
431 CFMutableArrayRef removed_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
433 if (SOSCircleHasPeer(new_circle
, myPi
, NULL
)) {
434 SOSCircleForEachPeer(new_circle
, ^(SOSPeerInfoRef peer
) {
435 CFArrayAppendValueIfNot(peer_ids
, SOSPeerInfoGetPeerID(peer
), myPi_id
);
438 CFSetForEach(added_peers
, ^(const void *value
) {
439 CFArrayAppendValueIfNot(added_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
442 CFSetForEach(removed_peers
, ^(const void *value
) {
443 CFArrayAppendValueIfNot(removed_ids
, SOSPeerInfoGetPeerID((SOSPeerInfoRef
) value
), myPi_id
);
447 if (CFArrayGetCount(peer_ids
) || CFSetContainsValue(removed_peers
, myPi
))
448 changeBlock(peer_ids
, added_ids
, removed_ids
);
450 CFReleaseSafe(peer_ids
);
451 CFReleaseSafe(added_ids
);
452 CFReleaseSafe(removed_ids
);
455 CFRetainSafe(changeBlock
);
456 SOSAccountAddChangeBlock(a
, Block_copy(block_to_register
));
458 CFSetRef empty
= CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault
);
459 SOSCircleRef circle
= (SOSCircleRef
) CFDictionaryGetValue(a
->circles
, ds_name
);
461 block_to_register(circle
, empty
, empty
, empty
, empty
);
463 CFReleaseSafe(empty
);
467 bool sosAccountLeaveCircle(SOSAccountRef account
, SOSCircleRef circle
, CFErrorRef
* error
) {
468 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, NULL
);
469 if(!fpi
) return false;
470 CFErrorRef localError
= NULL
;
471 SOSPeerInfoRef retire_peer
= SOSFullPeerInfoPromoteToRetiredAndCopy(fpi
, &localError
);
472 CFStringRef retire_id
= SOSPeerInfoGetPeerID(retire_peer
);
474 // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
475 CFStringRef retire_key
= SOSRetirementKeyCreateWithCircleAndPeer(circle
, retire_id
);
476 CFDataRef retire_value
= NULL
;
478 bool writeCircle
= false;
480 // Create a Retirement Ticket and store it in the retired_peers of the account.
481 require_action_quiet(retire_peer
, errout
, secerror("Create ticket failed for peer %@: %@", fpi
, localError
));
482 retire_value
= SOSPeerInfoCopyEncodedData(retire_peer
, NULL
, &localError
);
483 require_action_quiet(retire_value
, errout
, secerror("Failed to encode retirement peer %@: %@", retire_peer
, localError
));
485 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
486 if(SOSCircleHasApplicant(circle
, retire_peer
, NULL
)) {
487 // Remove our application if we have one.
488 SOSCircleWithdrawRequest(circle
, retire_peer
, NULL
);
490 } else if (SOSCircleHasPeer(circle
, retire_peer
, NULL
)) {
491 if (SOSCircleUpdatePeerInfo(circle
, retire_peer
)) {
492 CFErrorRef cleanupError
= NULL
;
493 if (!SOSAccountCleanupAfterPeer(account
, RETIREMENT_FINALIZATION_SECONDS
, circle
, retire_peer
, &cleanupError
))
494 secerror("Error cleanup up after peer (%@): %@", retire_peer
, cleanupError
);
495 CFReleaseSafe(cleanupError
);
500 // Store the retirement record locally.
501 CFDictionarySetValue(account
->retired_peers
, retire_key
, retire_value
);
503 // Write retirement to Transport
504 SOSTransportCircleRef tCircle
= (SOSTransportCircleRef
)CFDictionaryGetValue(account
->circle_transports
, SOSCircleGetName(circle
));
505 SOSTransportCirclePostRetirement(tCircle
, SOSCircleGetName(circle
), retire_id
, retire_value
, NULL
); // TODO: Handle errors?
507 // Kill peer key but don't return error if we can't.
508 if(!SOSAccountDestroyCirclePeerInfo(account
, circle
, &localError
))
509 secerror("Couldn't purge key for peer %@ on retirement: %@", fpi
, localError
);
512 CFDataRef circle_data
= SOSCircleCopyEncodedData(circle
, kCFAllocatorDefault
, error
);
515 SOSTransportCirclePostCircle(tCircle
, SOSCircleGetName(circle
), circle_data
, NULL
); // TODO: Handle errors?
517 CFReleaseNull(circle_data
);
522 CFReleaseNull(localError
);
523 CFReleaseNull(retire_peer
);
524 CFReleaseNull(retire_key
);
525 CFReleaseNull(retire_value
);
530 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
531 local value that has been overwritten by a distant value. If there is no
532 conflict between the local and the distant values when doing the initial
533 sync (e.g. if the cloud has no data stored or the client has not stored
534 any data yet), you'll never see that notification.
536 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
537 with server but initial round trip with server does not imply
538 NSUbiquitousKeyValueStoreInitialSyncChange.
543 // MARK: Status summary
546 static SOSCCStatus
SOSCCCircleStatus(SOSCircleRef circle
) {
547 if (SOSCircleCountPeers(circle
) == 0)
548 return kSOSCCCircleAbsent
;
550 return kSOSCCNotInCircle
;
553 static SOSCCStatus
SOSCCThisDeviceStatusInCircle(SOSCircleRef circle
, SOSPeerInfoRef this_peer
) {
554 if (SOSCircleCountPeers(circle
) == 0)
555 return kSOSCCCircleAbsent
;
557 if (SOSCircleHasPeer(circle
, this_peer
, NULL
))
558 return kSOSCCInCircle
;
560 if (SOSCircleHasApplicant(circle
, this_peer
, NULL
))
561 return kSOSCCRequestPending
;
563 return kSOSCCNotInCircle
;
566 static SOSCCStatus
UnionStatus(SOSCCStatus accumulated_status
, SOSCCStatus additional_circle_status
) {
567 switch (additional_circle_status
) {
569 return accumulated_status
;
570 case kSOSCCRequestPending
:
571 return (accumulated_status
== kSOSCCInCircle
) ?
572 kSOSCCRequestPending
:
574 case kSOSCCNotInCircle
:
575 return (accumulated_status
== kSOSCCInCircle
||
576 accumulated_status
== kSOSCCRequestPending
) ?
579 case kSOSCCCircleAbsent
:
580 return (accumulated_status
== kSOSCCInCircle
||
581 accumulated_status
== kSOSCCRequestPending
||
582 accumulated_status
== kSOSCCNotInCircle
) ?
586 return additional_circle_status
;
591 SOSCCStatus
SOSAccountIsInCircles(SOSAccountRef account
, CFErrorRef
* error
) {
592 if (!SOSAccountHasPublicKey(account
, error
)) {
596 __block
bool set_once
= false;
597 __block SOSCCStatus status
= kSOSCCInCircle
;
599 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
601 status
= UnionStatus(status
, kSOSCCNotInCircle
);
602 }, ^(SOSCircleRef circle
) {
604 status
= UnionStatus(status
, SOSCCCircleStatus(circle
));
605 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
607 SOSCCStatus circle_status
= SOSCCThisDeviceStatusInCircle(circle
, SOSFullPeerInfoGetPeerInfo(full_peer
));
608 status
= UnionStatus(status
, circle_status
);
612 status
= kSOSCCCircleAbsent
;
618 // MARK: Account Reset Circles
621 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account
, SOSCircleRef circle
, SecKeyRef user_key
, CFErrorRef
*error
) {
622 SOSFullPeerInfoRef myCirclePeer
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
626 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
628 SOSFullPeerInfoRef cloud_identity
= NULL
;
629 CFErrorRef localError
= NULL
;
631 require_quiet(SOSCircleResetToOffering(circle
, user_key
, myCirclePeer
, &localError
), err_out
);
634 SOSPeerInfoRef cloud_peer
= GenerateNewCloudIdentityPeerInfo(error
);
635 require_quiet(cloud_peer
, err_out
);
636 cloud_identity
= CopyCloudKeychainIdentity(cloud_peer
, error
);
637 CFReleaseNull(cloud_peer
);
638 require_quiet(cloud_identity
, err_out
);
641 account
->departure_code
= kSOSNeverLeftCircle
;
642 require_quiet(SOSCircleRequestAdmission(circle
, user_key
, cloud_identity
, &localError
), err_out
);
643 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, myCirclePeer
, SOSFullPeerInfoGetPeerInfo(cloud_identity
), &localError
), err_out
);
645 SOSAccountPublishCloudParameters(account
, NULL
);
649 secerror("error resetting circle (%@) to offering: %@", circle
, localError
);
650 if (localError
&& error
&& *error
== NULL
) {
654 CFReleaseNull(localError
);
655 CFReleaseNull(cloud_identity
);
663 bool SOSAccountResetToOffering(SOSAccountRef account
, CFErrorRef
* error
) {
664 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
668 __block
bool result
= true;
670 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
671 SOSCircleRef circle
= SOSCircleCreate(NULL
, name
, NULL
);
673 CFDictionaryAddValue(account
->circles
, name
, circle
);
675 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
676 }, ^(SOSCircleRef circle
) {
677 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
678 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
679 SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
685 bool SOSAccountResetToEmpty(SOSAccountRef account
, CFErrorRef
* error
) {
686 if (!SOSAccountHasPublicKey(account
, error
))
689 __block
bool result
= true;
690 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
691 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
692 if (!SOSCircleResetToEmpty(circle
, error
))
694 secerror("error: %@", *error
);
697 account
->departure_code
= kSOSWithdrewMembership
;
710 static bool SOSAccountJoinThisCircle(SOSAccountRef account
, SecKeyRef user_key
,
711 SOSCircleRef circle
, bool use_cloud_peer
, CFErrorRef
* error
) {
712 __block
bool result
= false;
713 __block SOSFullPeerInfoRef cloud_full_peer
= NULL
;
715 SOSFullPeerInfoRef myCirclePeer
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
717 require_action_quiet(myCirclePeer
, fail
,
718 SOSCreateErrorWithFormat(kSOSErrorPeerNotFound
, NULL
, error
, NULL
, CFSTR("Can't find/create peer for circle: %@"), circle
));
719 if (use_cloud_peer
) {
720 cloud_full_peer
= SOSCircleGetiCloudFullPeerInfoRef(circle
);
723 if (SOSCircleCountPeers(circle
) == 0) {
724 result
= SOSAccountResetThisCircleToOffering(account
, circle
, user_key
, error
);
726 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
727 result
= SOSCircleRequestAdmission(circle
, user_key
, myCirclePeer
, error
);
728 account
->departure_code
= kSOSNeverLeftCircle
;
729 if(result
&& cloud_full_peer
) {
730 CFErrorRef localError
= NULL
;
731 CFStringRef cloudid
= SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer
));
732 require_quiet(cloudid
, finish
);
733 require_quiet(SOSCircleHasActivePeerWithID(circle
, cloudid
, &localError
), finish
);
734 require_quiet(SOSCircleAcceptRequest(circle
, user_key
, cloud_full_peer
, SOSFullPeerInfoGetPeerInfo(myCirclePeer
), &localError
), finish
);
737 secerror("Failed to join with cloud identity: %@", localError
);
738 CFReleaseNull(localError
);
746 CFReleaseNull(cloud_full_peer
);
750 static bool SOSAccountJoinCircles_internal(SOSAccountRef account
, bool use_cloud_identity
, CFErrorRef
* error
) {
751 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
755 __block
bool success
= true;
757 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) { // Incompatible
759 SOSCreateError(kSOSErrorIncompatibleCircle
, CFSTR("Incompatible circle"), NULL
, error
);
760 }, ^(SOSCircleRef circle
) { //no peer
761 success
= SOSAccountJoinThisCircle(account
, user_key
, circle
, use_cloud_identity
, error
) && success
;
762 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) { // Have Peer
763 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(full_peer
);
764 if(SOSCircleHasPeer(circle
, myPeer
, NULL
)) goto already_present
;
765 if(SOSCircleHasApplicant(circle
, myPeer
, NULL
)) goto already_applied
;
766 if(SOSCircleHasRejectedApplicant(circle
, myPeer
, NULL
)) {
767 SOSCircleRemoveRejectedPeer(circle
, myPeer
, NULL
);
770 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer
), SOSCircleGetName(circle
));
771 CFErrorRef localError
= NULL
;
772 if (!SOSAccountDestroyCirclePeerInfo(account
, circle
, &localError
)) {
773 secerror("Failed to destroy peer (%@) during application, error=%@", myPeer
, localError
);
774 CFReleaseNull(localError
);
777 success
= SOSAccountJoinThisCircle(account
, user_key
, circle
, use_cloud_identity
, error
) && success
;
784 if(success
) account
->departure_code
= kSOSNeverLeftCircle
;
788 bool SOSAccountJoinCircles(SOSAccountRef account
, CFErrorRef
* error
) {
789 return SOSAccountJoinCircles_internal(account
, false, error
);
792 CFStringRef
SOSAccountGetDeviceID(SOSAccountRef account
, CFErrorRef
*error
){
793 __block CFStringRef result
= NULL
;
794 __block CFStringRef temp
= NULL
;
795 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
796 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
798 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
800 temp
= SOSPeerInfoGetDeviceID(myPeer
);
802 result
= CFStringCreateCopy(kCFAllocatorDefault
, temp
);
806 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
810 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
816 bool SOSAccountSetMyDSID(SOSAccountRef account
, CFStringRef IDS
, CFErrorRef
* error
){
817 __block
bool result
= false;
819 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
820 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
821 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
823 SOSPeerInfoRef myPeer
= SOSFullPeerInfoGetPeerInfo(fpi
);
825 SOSPeerInfoSetDeviceID(myPeer
, IDS
);
829 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle
));
834 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle
));
843 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account
, CFErrorRef
* error
) {
844 return SOSAccountJoinCircles_internal(account
, true, error
);
848 bool SOSAccountLeaveCircles(SOSAccountRef account
, CFErrorRef
* error
)
850 __block
bool result
= true;
851 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
852 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
853 result
= sosAccountLeaveCircle(account
, circle
, error
); // TODO: What about multiple errors!
858 account
->departure_code
= kSOSWithdrewMembership
;
862 bool SOSAccountBail(SOSAccountRef account
, uint64_t limit_in_seconds
, CFErrorRef
* error
) {
863 dispatch_queue_t queue
= dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT
, 0);
864 dispatch_group_t group
= dispatch_group_create();
865 __block
bool result
= false;
866 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds
);
867 // Add a task to the group
868 dispatch_group_async(group
, queue
, ^{
869 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
) {
870 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), error
, ^(SOSCircleRef circle
) {
871 result
= sosAccountLeaveCircle(account
, circle
, error
); // TODO: What about multiple errors!
876 account
->departure_code
= kSOSWithdrewMembership
;
878 dispatch_time_t milestone
= dispatch_time(DISPATCH_TIME_NOW
, limit_in_seconds
* NSEC_PER_SEC
);
880 dispatch_group_wait(group
, milestone
);
881 dispatch_release(group
);
890 static void for_each_applicant_in_each_circle(SOSAccountRef account
, CFArrayRef peer_infos
,
891 bool (^action
)(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
)) {
893 SOSAccountForEachKnownCircle(account
, NULL
, NULL
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
894 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(full_peer
);
895 CFErrorRef peer_error
= NULL
;
896 if (SOSCircleHasPeer(circle
, me
, &peer_error
)) {
897 CFArrayForEach(peer_infos
, ^(const void *value
) {
898 SOSPeerInfoRef peer
= (SOSPeerInfoRef
) value
;
899 if (SOSCircleHasApplicant(circle
, peer
, NULL
)) {
900 SOSAccountModifyCircle(account
, SOSCircleGetName(circle
), NULL
, ^(SOSCircleRef circle
) {
901 return action(circle
, full_peer
, peer
);
907 secerror("Got error in SOSCircleHasPeer: %@", peer_error
);
908 CFReleaseSafe(peer_error
); // TODO: We should be accumulating errors here.
912 bool SOSAccountAcceptApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
913 SecKeyRef user_key
= SOSAccountGetPrivateCredential(account
, error
);
917 __block
bool success
= true;
918 __block
int64_t num_peers
= 0;
920 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
921 bool accepted
= SOSCircleAcceptRequest(circle
, user_key
, myCirclePeer
, peer
, error
);
925 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
932 bool SOSAccountRejectApplicants(SOSAccountRef account
, CFArrayRef applicants
, CFErrorRef
* error
) {
933 __block
bool success
= true;
934 __block
int64_t num_peers
= 0;
936 for_each_applicant_in_each_circle(account
, applicants
, ^(SOSCircleRef circle
, SOSFullPeerInfoRef myCirclePeer
, SOSPeerInfoRef peer
) {
937 bool rejected
= SOSCircleRejectRequest(circle
, myCirclePeer
, peer
, error
);
941 num_peers
= MAX(num_peers
, SOSCircleCountPeers(circle
));
950 CFStringRef
SOSAccountCopyIncompatibilityInfo(SOSAccountRef account
, CFErrorRef
* error
) {
951 return CFSTR("We're compatible, go away");
954 enum DepartureReason
SOSAccountGetLastDepartureReason(SOSAccountRef account
, CFErrorRef
* error
) {
955 return account
->departure_code
;
959 CFArrayRef
SOSAccountCopyGeneration(SOSAccountRef account
, CFErrorRef
*error
) {
960 if (!SOSAccountHasPublicKey(account
, error
))
962 CFMutableArrayRef generations
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
964 SOSAccountForEachCircle(account
, ^(SOSCircleRef circle
) {
965 CFNumberRef generation
= (CFNumberRef
)SOSCircleGetGeneration(circle
);
966 CFArrayAppendValue(generations
, SOSCircleGetName(circle
));
967 CFArrayAppendValue(generations
, generation
);
974 bool SOSValidateUserPublic(SOSAccountRef account
, CFErrorRef
*error
) {
975 if (!SOSAccountHasPublicKey(account
, error
))
978 return account
->user_public_trusted
;
982 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account
, CFErrorRef
*error
) {
983 __block
bool result
= true;
985 secnotice("updates", "Ensuring peer registration.");
987 SOSAccountForEachKnownCircle(account
, ^(CFStringRef name
) {
988 }, ^(SOSCircleRef circle
) {
989 }, ^(SOSCircleRef circle
, SOSFullPeerInfoRef full_peer
) {
990 SOSFullPeerInfoRef fpi
= SOSAccountGetMyFullPeerInCircle(account
, circle
, error
);
991 SOSPeerInfoRef me
= SOSFullPeerInfoGetPeerInfo(fpi
);
992 CFMutableArrayRef trusted_peer_ids
= NULL
;
993 CFMutableArrayRef untrusted_peer_ids
= NULL
;
994 CFStringRef my_id
= NULL
;
995 if (SOSCircleHasPeer(circle
, me
, NULL
)) {
996 my_id
= SOSPeerInfoGetPeerID(me
);
997 trusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
998 untrusted_peer_ids
= CFArrayCreateMutableForCFTypes(kCFAllocatorDefault
);
999 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
1000 CFMutableArrayRef arrayToAddTo
= SOSPeerInfoApplicationVerify(peer
, account
->user_public
, NULL
) ? trusted_peer_ids
: untrusted_peer_ids
;
1002 CFArrayAppendValueIfNot(arrayToAddTo
, SOSPeerInfoGetPeerID(peer
), my_id
);
1006 SOSEngineRef engine
= SOSDataSourceFactoryGetEngineForDataSourceName(account
->factory
, SOSCircleGetName(circle
), NULL
);
1008 SOSEngineCircleChanged(engine
, my_id
, trusted_peer_ids
, untrusted_peer_ids
);
1010 CFReleaseNull(trusted_peer_ids
);
1011 CFReleaseNull(untrusted_peer_ids
);
1013 SOSTransportMessageRef transport
= (SOSTransportMessageRef
)CFDictionaryGetValue(account
->message_transports
, SOSCircleGetName(circle
));
1014 SOSCircleForEachPeer(circle
, ^(SOSPeerInfoRef peer
) {
1015 if (!CFEqualSafe(me
, peer
)) {
1016 CFErrorRef localError
= NULL
;
1017 SOSPeerCoderInitializeForPeer(transport
, full_peer
, peer
, &localError
);
1019 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer
, full_peer
, localError
);
1020 CFReleaseSafe(localError
);