]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/SecureObjectSync/SOSAccount.c
4a601f4712fbbb83fdcc6b38a2d7a0489a98d044
[apple/security.git] / Security / sec / SOSCircle / SecureObjectSync / SOSAccount.c
1 /*
2 * Copyright (c) 2012-2014 Apple Inc. All Rights Reserved.
3 */
4
5 /*
6 * SOSAccount.c - Implementation of the secure object syncing account.
7 * An account contains a SOSCircle for each protection domain synced.
8 */
9
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>
19
20 CFGiblisWithCompareFor(SOSAccount);
21
22
23 bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
24 {
25 bool result = false;
26 if (a)
27 {
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) {
32 if (isString(name))
33 SOSAccountEnsureCircle(a, (CFStringRef)name, NULL);
34 });
35
36 CFReleaseNull(circle_names);
37 result = true;
38 }
39 xit:
40 return result;
41 }
42
43
44 SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
45 CFDictionaryRef gestalt,
46 SOSDataSourceFactoryRef factory) {
47 SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
48
49 a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
50
51 a->gestalt = CFRetainSafe(gestalt);
52
53 a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
54 a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
55
56 a->factory = factory; // We adopt the factory. kthanksbai.
57
58 a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
59
60 a->departure_code = kSOSNeverAppliedToCircle;
61
62 a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
63 a->circle_transports = CFDictionaryCreateMutableForCFTypes(allocator);
64 a->message_transports = CFDictionaryCreateMutableForCFTypes(allocator);
65
66 return a;
67 }
68
69
70
71
72 bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
73 {
74 if (CFEqual(new_gestalt, account->gestalt))
75 return false;
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));
81 });
82 };
83 });
84
85 CFRetainAssign(account->gestalt, new_gestalt);
86 return true;
87 }
88
89 SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
90 CFDictionaryRef gestalt,
91 SOSDataSourceFactoryRef factory) {
92 SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
93
94 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
95
96 SOSAccountEnsureFactoryCircles(a);
97
98 return a;
99 }
100
101 static void SOSAccountDestroy(CFTypeRef aObj) {
102 SOSAccountRef a = (SOSAccountRef) aObj;
103
104 // We don't own the factory, meerly have a reference to the singleton
105 // don't free it.
106 // a->factory
107
108 CFReleaseNull(a->gestalt);
109 CFReleaseNull(a->circle_identities);
110 CFReleaseNull(a->circles);
111 CFReleaseNull(a->retired_peers);
112
113 a->user_public_trusted = false;
114 CFReleaseNull(a->user_public);
115 CFReleaseNull(a->user_key_parameters);
116
117 SOSAccountPurgePrivateCredential(a);
118 CFReleaseNull(a->previous_public);
119
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);
125 }
126
127 void SOSAccountSetToNew(SOSAccountRef a) {
128 CFAllocatorRef allocator = CFGetAllocator(a);
129 CFReleaseNull(a->circle_identities);
130 CFReleaseNull(a->circles);
131 CFReleaseNull(a->retired_peers);
132
133 CFReleaseNull(a->user_key_parameters);
134 CFReleaseNull(a->user_public);
135 CFReleaseNull(a->previous_public);
136 CFReleaseNull(a->_user_private);
137
138 CFReleaseNull(a->key_transport);
139 CFReleaseNull(a->circle_transports);
140 CFReleaseNull(a->message_transports);
141
142 a->user_public_trusted = false;
143 a->departure_code = kSOSNeverAppliedToCircle;
144 a->user_private_timer = 0;
145 a->lock_notification_token = 0;
146
147 // keeping gestalt;
148 // keeping factory;
149 // Live Notification
150 // change_blocks;
151 // update_interest_block;
152 // update_block;
153
154 a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
155 a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
156 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
157
158 a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
159 a->circle_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
160 a->message_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
161
162 SOSAccountEnsureFactoryCircles(a);
163 }
164
165
166 static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) {
167 SOSAccountRef a = (SOSAccountRef) aObj;
168
169 return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
170 }
171
172 static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
173 {
174 SOSAccountRef laccount = (SOSAccountRef) lhs;
175 SOSAccountRef raccount = (SOSAccountRef) rhs;
176
177 return CFEqual(laccount->gestalt, raccount->gestalt)
178 && CFEqual(laccount->circles, raccount->circles)
179 && CFEqual(laccount->circle_identities, raccount->circle_identities);
180 // ??? retired_peers
181 }
182
183 dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
184 return account->queue;
185 }
186
187 CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){
188 return account->message_transports;
189 }
190
191
192 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
193 account->user_public_trusted = true;
194 }
195
196 CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
197 {
198 CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
199
200 CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
201 SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
202
203 CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
204 });
205
206 return result;
207 }
208
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);
215 }
216
217 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
218 CFErrorRef error = NULL;
219 SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circleName, &error);
220 if (!myfpi) {
221 return false;
222 }
223 SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
224 CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
225 return CFEqualSafe(myPeerID, peerID);
226 }
227
228 bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
229 {
230 __block bool result = true;
231
232 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
233
234 if (SOSAccountThisDeviceCanSyncWithCircle(account, circle)) {
235 CFStringRef circleName = SOSCircleGetName(circle);
236 SOSTransportMessageRef thisPeerTransport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
237 ;
238 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
239
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);
245 }
246 });
247
248 result &= SOSTransportMessageSyncWithPeers(thisPeerTransport, circleToPeerIDs, error);
249
250 CFReleaseNull(circleToPeerIDs);
251 }
252 });
253
254 // Tell each transport to sync with its collection of peers we know we should sync with.
255
256
257 if (result)
258 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
259
260
261 return result;
262 }
263
264 bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
265 SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
266 {
267 bool success = false;
268 if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
269
270 SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInCircle(account, circle, error);
271 require(myPeerInfo, xit);
272
273 CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
274 CFStringRef circle_name = SOSCircleGetName(circle);
275
276 if (CFEqual(cleanupPeerID, SOSPeerInfoGetPeerID(myPeerInfo))) {
277 CFErrorRef destroyError = NULL;
278 if (!SOSAccountDestroyCirclePeerInfo(account, circle, &destroyError)) {
279 secerror("Unable to destroy peer info: %@", destroyError);
280 }
281 CFReleaseSafe(destroyError);
282
283 account->departure_code = kSOSWithdrewMembership;
284
285 return true;
286 }
287
288 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
289 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
290
291
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);
296 }
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);
302 }
303 }
304 CFReleaseNull(localError);
305
306 CFReleaseNull(circleToPeerIDs);
307
308 xit:
309 return success;
310 }
311
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);
317
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);
334 }
335 CFDictionarySetValue(still_active_circle_retirements, retired_peer_id, value);
336 } else {
337 CFMutableArrayRef retirements = CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove, circle_name);
338 CFArrayAppendValue(retirements, retired_peer_id);
339 }
340 }
341 CFReleaseNull(retired_peer);
342 }
343 });
344
345 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, circle_name);
346 success &= SOSTransportCircleExpireRetirementRecords(tCircle, retirements_to_remove, error);
347 }
348 });
349
350 CFReleaseNull(original_retired_peers);
351 CFReleaseNull(retirements_to_remove);
352
353 return success;
354 }
355
356 bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
357 __block CFMutableDictionaryRef circle_retirees = (CFMutableDictionaryRef) CFDictionaryGetValue(account->retired_peers, SOSCircleGetName(circle));
358
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));
364 }
365 CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
366 if(value) {
367 CFDictionarySetValue(circle_retirees, peer_id, value);
368 SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
369 }
370 CFReleaseSafe(value);
371 }
372 });
373 return true;
374 }
375
376 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
377 CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
378
379 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
380 if(!new_circle) return NULL;
381
382 CFDictionaryRef circle_retirements = CFDictionaryGetValue(account->retired_peers, circle_to_mod);
383
384 if (isDictionary(circle_retirements)) {
385 CFDictionaryForEach(circle_retirements, ^(const void* id, const void* value) {
386 if (isData(value)) {
387 SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
388 if (pi && CFEqualSafe(id, SOSPeerInfoGetPeerID(pi))) {
389 SOSCircleUpdatePeerInfo(new_circle, pi);
390 }
391 CFReleaseSafe(pi);
392 }
393 });
394 }
395
396 if(SOSCircleCountPeers(new_circle) == 0) {
397 SOSCircleResetToEmpty(new_circle, NULL);
398 }
399
400 return new_circle;
401 }
402
403 //
404 // MARK: Circle Membership change notificaion
405 //
406
407 void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
408 CFArrayAppendValue(a->change_blocks, changeBlock);
409 }
410
411 void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
412 CFArrayRemoveAllValue(a->change_blocks, changeBlock);
413 }
414
415 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
416 if (!changeBlock) return;
417
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) {
422
423 if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
424 return;
425
426 SOSPeerInfoRef myPi = SOSAccountGetMyPeerInCircle(a, new_circle, NULL);
427 CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
428
429 CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
430 CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
431 CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
432
433 if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
434 SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
435 CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
436 });
437
438 CFSetForEach(added_peers, ^(const void *value) {
439 CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
440 });
441
442 CFSetForEach(removed_peers, ^(const void *value) {
443 CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
444 });
445 }
446
447 if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
448 changeBlock(peer_ids, added_ids, removed_ids);
449
450 CFReleaseSafe(peer_ids);
451 CFReleaseSafe(added_ids);
452 CFReleaseSafe(removed_ids);
453 };
454
455 CFRetainSafe(changeBlock);
456 SOSAccountAddChangeBlock(a, Block_copy(block_to_register));
457
458 CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
459 SOSCircleRef circle = (SOSCircleRef) CFDictionaryGetValue(a->circles, ds_name);
460 if (circle) {
461 block_to_register(circle, empty, empty, empty, empty);
462 }
463 CFReleaseSafe(empty);
464 }
465
466
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);
473
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;
477 bool retval = false;
478 bool writeCircle = false;
479
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));
484
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);
489 writeCircle = true;
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);
496 }
497 writeCircle = true;
498 }
499
500 // Store the retirement record locally.
501 CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
502
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?
506
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);
510
511 if (writeCircle) {
512 CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
513
514 if (circle_data) {
515 SOSTransportCirclePostCircle(tCircle, SOSCircleGetName(circle), circle_data, NULL); // TODO: Handle errors?
516 }
517 CFReleaseNull(circle_data);
518 }
519 retval = true;
520
521 errout:
522 CFReleaseNull(localError);
523 CFReleaseNull(retire_peer);
524 CFReleaseNull(retire_key);
525 CFReleaseNull(retire_value);
526 return retval;
527 }
528
529 /*
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.
535
536 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
537 with server but initial round trip with server does not imply
538 NSUbiquitousKeyValueStoreInitialSyncChange.
539 */
540
541
542 //
543 // MARK: Status summary
544 //
545
546 static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) {
547 if (SOSCircleCountPeers(circle) == 0)
548 return kSOSCCCircleAbsent;
549
550 return kSOSCCNotInCircle;
551 }
552
553 static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
554 if (SOSCircleCountPeers(circle) == 0)
555 return kSOSCCCircleAbsent;
556
557 if (SOSCircleHasPeer(circle, this_peer, NULL))
558 return kSOSCCInCircle;
559
560 if (SOSCircleHasApplicant(circle, this_peer, NULL))
561 return kSOSCCRequestPending;
562
563 return kSOSCCNotInCircle;
564 }
565
566 static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) {
567 switch (additional_circle_status) {
568 case kSOSCCInCircle:
569 return accumulated_status;
570 case kSOSCCRequestPending:
571 return (accumulated_status == kSOSCCInCircle) ?
572 kSOSCCRequestPending :
573 accumulated_status;
574 case kSOSCCNotInCircle:
575 return (accumulated_status == kSOSCCInCircle ||
576 accumulated_status == kSOSCCRequestPending) ?
577 kSOSCCNotInCircle :
578 accumulated_status;
579 case kSOSCCCircleAbsent:
580 return (accumulated_status == kSOSCCInCircle ||
581 accumulated_status == kSOSCCRequestPending ||
582 accumulated_status == kSOSCCNotInCircle) ?
583 kSOSCCCircleAbsent :
584 accumulated_status;
585 default:
586 return additional_circle_status;
587 };
588
589 }
590
591 SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) {
592 if (!SOSAccountHasPublicKey(account, error)) {
593 return kSOSCCError;
594 }
595
596 __block bool set_once = false;
597 __block SOSCCStatus status = kSOSCCInCircle;
598
599 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
600 set_once = true;
601 status = UnionStatus(status, kSOSCCNotInCircle);
602 }, ^(SOSCircleRef circle) {
603 set_once = true;
604 status = UnionStatus(status, SOSCCCircleStatus(circle));
605 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
606 set_once = true;
607 SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
608 status = UnionStatus(status, circle_status);
609 });
610
611 if (!set_once)
612 status = kSOSCCCircleAbsent;
613
614 return status;
615 }
616
617 //
618 // MARK: Account Reset Circles
619 //
620
621 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
622 SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
623 if (!myCirclePeer)
624 return false;
625
626 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
627 bool result = false;
628 SOSFullPeerInfoRef cloud_identity = NULL;
629 CFErrorRef localError = NULL;
630
631 require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out);
632
633 {
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);
639 }
640
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);
644 result = true;
645 SOSAccountPublishCloudParameters(account, NULL);
646
647 err_out:
648 if (result == false)
649 secerror("error resetting circle (%@) to offering: %@", circle, localError);
650 if (localError && error && *error == NULL) {
651 *error = localError;
652 localError = NULL;
653 }
654 CFReleaseNull(localError);
655 CFReleaseNull(cloud_identity);
656 return result;
657 });
658
659 return true;
660 }
661
662
663 bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
664 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
665 if (!user_key)
666 return false;
667
668 __block bool result = true;
669
670 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
671 SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
672 if (circle)
673 CFDictionaryAddValue(account->circles, name, circle);
674
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);
680 });
681
682 return result;
683 }
684
685 bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
686 if (!SOSAccountHasPublicKey(account, error))
687 return false;
688
689 __block bool result = true;
690 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
691 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
692 if (!SOSCircleResetToEmpty(circle, error))
693 {
694 secerror("error: %@", *error);
695 result = false;
696 }
697 account->departure_code = kSOSWithdrewMembership;
698 return result;
699 });
700 });
701
702 return result;
703 }
704
705
706 //
707 // MARK: Joining
708 //
709
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;
714
715 SOSFullPeerInfoRef myCirclePeer = SOSAccountGetMyFullPeerInCircle(account, circle, error);
716
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);
721 }
722
723 if (SOSCircleCountPeers(circle) == 0) {
724 result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
725 } else {
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);
735 finish:
736 if (localError){
737 secerror("Failed to join with cloud identity: %@", localError);
738 CFReleaseNull(localError);
739 }
740 }
741 return result;
742 });
743 }
744
745 fail:
746 CFReleaseNull(cloud_full_peer);
747 return result;
748 }
749
750 static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
751 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
752 if (!user_key)
753 return false;
754
755 __block bool success = true;
756
757 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
758 success = false;
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);
768 }
769
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);
775 }
776 already_applied:
777 success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
778 return;
779 already_present:
780 success = true;
781 return;
782 });
783
784 if(success) account->departure_code = kSOSNeverLeftCircle;
785 return success;
786 }
787
788 bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
789 return SOSAccountJoinCircles_internal(account, false, error);
790 }
791
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);
797 if(fpi){
798 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
799 if(myPeer){
800 temp = SOSPeerInfoGetDeviceID(myPeer);
801 if(!isNull(temp)){
802 result = CFStringCreateCopy(kCFAllocatorDefault, temp);
803 }
804 }
805 else{
806 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
807 }
808 }
809 else{
810 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
811 }
812 });
813 return result;
814 }
815
816 bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
817 __block bool result = false;
818
819 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
820 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
821 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
822 if(fpi){
823 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
824 if(myPeer){
825 SOSPeerInfoSetDeviceID(myPeer, IDS);
826 result = true;
827 }
828 else{
829 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
830 result = false;
831 }
832 }
833 else{
834 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
835 result = false;
836 }
837 return result;
838 });
839 });
840 return result;
841
842 }
843 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
844 return SOSAccountJoinCircles_internal(account, true, error);
845 }
846
847
848 bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
849 {
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!
854 return result;
855 });
856 });
857
858 account->departure_code = kSOSWithdrewMembership;
859 return result;
860 }
861
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!
872 return result;
873 });
874 });
875
876 account->departure_code = kSOSWithdrewMembership;
877 });
878 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
879
880 dispatch_group_wait(group, milestone);
881 dispatch_release(group);
882 return result;
883 }
884
885
886 //
887 // MARK: Application
888 //
889
890 static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
891 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
892
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);
902 });
903 }
904 });
905 }
906 if (peer_error)
907 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
908 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
909 });
910 }
911
912 bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
913 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
914 if (!user_key)
915 return false;
916
917 __block bool success = true;
918 __block int64_t num_peers = 0;
919
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);
922 if (!accepted)
923 success = false;
924 else
925 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
926 return accepted;
927 });
928
929 return success;
930 }
931
932 bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
933 __block bool success = true;
934 __block int64_t num_peers = 0;
935
936 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
937 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
938 if (!rejected)
939 success = false;
940 else
941 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
942 return rejected;
943 });
944
945 return success;
946 }
947
948
949
950 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
951 return CFSTR("We're compatible, go away");
952 }
953
954 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
955 return account->departure_code;
956 }
957
958
959 CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
960 if (!SOSAccountHasPublicKey(account, error))
961 return NULL;
962 CFMutableArrayRef generations = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
963
964 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
965 CFNumberRef generation = (CFNumberRef)SOSCircleGetGeneration(circle);
966 CFArrayAppendValue(generations, SOSCircleGetName(circle));
967 CFArrayAppendValue(generations, generation);
968 });
969
970 return generations;
971
972 }
973
974 bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
975 if (!SOSAccountHasPublicKey(account, error))
976 return NULL;
977
978 return account->user_public_trusted;
979 }
980
981
982 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
983 __block bool result = true;
984
985 secnotice("updates", "Ensuring peer registration.");
986
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;
1001
1002 CFArrayAppendValueIfNot(arrayToAddTo, SOSPeerInfoGetPeerID(peer), my_id);
1003 });
1004 }
1005
1006 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(circle), NULL);
1007 if (engine)
1008 SOSEngineCircleChanged(engine, my_id, trusted_peer_ids, untrusted_peer_ids);
1009
1010 CFReleaseNull(trusted_peer_ids);
1011 CFReleaseNull(untrusted_peer_ids);
1012
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);
1018 if (localError)
1019 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, full_peer, localError);
1020 CFReleaseSafe(localError);
1021 }
1022 });
1023 });
1024
1025 return result;
1026 }