]> git.saurik.com Git - apple/security.git/blob - Security/sec/SOSCircle/SecureObjectSync/SOSAccount.c
Security-57031.10.10.tar.gz
[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 secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
81 return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(full_peer));
82 });
83 };
84 });
85
86 CFRetainAssign(account->gestalt, new_gestalt);
87 return true;
88 }
89
90 SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
91 CFDictionaryRef gestalt,
92 SOSDataSourceFactoryRef factory) {
93 SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
94
95 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
96
97 SOSAccountEnsureFactoryCircles(a);
98
99 return a;
100 }
101
102 static void SOSAccountDestroy(CFTypeRef aObj) {
103 SOSAccountRef a = (SOSAccountRef) aObj;
104
105 // We don't own the factory, meerly have a reference to the singleton
106 // don't free it.
107 // a->factory
108
109 CFReleaseNull(a->gestalt);
110 CFReleaseNull(a->circle_identities);
111 CFReleaseNull(a->circles);
112 CFReleaseNull(a->retired_peers);
113
114 a->user_public_trusted = false;
115 CFReleaseNull(a->user_public);
116 CFReleaseNull(a->user_key_parameters);
117
118 SOSAccountPurgePrivateCredential(a);
119 CFReleaseNull(a->previous_public);
120
121 a->departure_code = kSOSNeverAppliedToCircle;
122 CFReleaseNull(a->message_transports);
123 CFReleaseNull(a->key_transport);
124 CFReleaseNull(a->circle_transports);
125 dispatch_release(a->queue);
126 }
127
128 void SOSAccountSetToNew(SOSAccountRef a) {
129 secnotice("accountChange", "Setting Account to New");
130 CFAllocatorRef allocator = CFGetAllocator(a);
131 CFReleaseNull(a->circle_identities);
132 CFReleaseNull(a->circles);
133 CFReleaseNull(a->retired_peers);
134
135 CFReleaseNull(a->user_key_parameters);
136 CFReleaseNull(a->user_public);
137 CFReleaseNull(a->previous_public);
138 CFReleaseNull(a->_user_private);
139
140 CFReleaseNull(a->key_transport);
141 CFReleaseNull(a->circle_transports);
142 CFReleaseNull(a->message_transports);
143
144 a->user_public_trusted = false;
145 a->departure_code = kSOSNeverAppliedToCircle;
146 a->user_private_timer = 0;
147 a->lock_notification_token = 0;
148
149 // keeping gestalt;
150 // keeping factory;
151 // Live Notification
152 // change_blocks;
153 // update_interest_block;
154 // update_block;
155
156 a->circles = CFDictionaryCreateMutableForCFTypes(allocator);
157 a->circle_identities = CFDictionaryCreateMutableForCFTypes(allocator);
158 a->retired_peers = CFDictionaryCreateMutableForCFTypes(allocator);
159
160 a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
161 a->circle_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
162 a->message_transports = (CFMutableDictionaryRef)CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
163
164 SOSAccountEnsureFactoryCircles(a);
165 }
166
167
168 static CFStringRef SOSAccountCopyDescription(CFTypeRef aObj) {
169 SOSAccountRef a = (SOSAccountRef) aObj;
170
171 return CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@\n Circles: %@ CircleIDs: %@>"), a, a->gestalt, a->circles, a->circle_identities);
172 }
173
174 static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
175 {
176 SOSAccountRef laccount = (SOSAccountRef) lhs;
177 SOSAccountRef raccount = (SOSAccountRef) rhs;
178
179 return CFEqual(laccount->gestalt, raccount->gestalt)
180 && CFEqual(laccount->circles, raccount->circles)
181 && CFEqual(laccount->circle_identities, raccount->circle_identities);
182 // ??? retired_peers
183 }
184
185 dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
186 return account->queue;
187 }
188
189 CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){
190 return account->message_transports;
191 }
192
193
194 void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
195 account->user_public_trusted = true;
196 }
197
198 CFArrayRef SOSAccountCopyAccountIdentityPeerInfos(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
199 {
200 CFMutableArrayRef result = CFArrayCreateMutableForCFTypes(allocator);
201
202 CFDictionaryForEach(account->circle_identities, ^(const void *key, const void *value) {
203 SOSFullPeerInfoRef fpi = (SOSFullPeerInfoRef) value;
204
205 CFArrayAppendValue(result, SOSFullPeerInfoGetPeerInfo(fpi));
206 });
207
208 return result;
209 }
210
211 static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account, SOSCircleRef circle) {
212 CFErrorRef error = NULL;
213 SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, SOSCircleGetName(circle), &error);
214 SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
215 CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
216 return SOSCircleHasPeerWithID(circle, myPeerID, &error);
217 }
218
219 static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
220 CFErrorRef error = NULL;
221 SOSFullPeerInfoRef myfpi = SOSAccountGetMyFullPeerInCircleNamedIfPresent(account, circleName, &error);
222 if (!myfpi) {
223 return false;
224 }
225 SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(myfpi);
226 CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
227 return CFEqualSafe(myPeerID, peerID);
228 }
229
230 bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
231 {
232 __block bool result = true;
233
234 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
235
236 if (SOSAccountThisDeviceCanSyncWithCircle(account, circle)) {
237 CFStringRef circleName = SOSCircleGetName(circle);
238 SOSTransportMessageRef thisPeerTransport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
239 ;
240 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
241
242 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
243 // Figure out transport for peer; for now we always use KVS
244 CFStringRef peerID = SOSPeerInfoGetPeerID(peer);
245 if (!SOSAccountIsThisPeerIDMe(account, circleName, peerID)) {
246 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circleName), peerID);
247 }
248 });
249
250 result &= SOSTransportMessageSyncWithPeers(thisPeerTransport, circleToPeerIDs, error);
251
252 CFReleaseNull(circleToPeerIDs);
253 }
254 });
255
256 // Tell each transport to sync with its collection of peers we know we should sync with.
257
258
259 if (result)
260 SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
261
262
263 return result;
264 }
265
266 bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
267 SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
268 {
269 bool success = false;
270 if(!SOSAccountIsMyPeerActiveInCircle(account, circle, NULL)) return true;
271
272 SOSPeerInfoRef myPeerInfo = SOSAccountGetMyPeerInCircle(account, circle, error);
273 require(myPeerInfo, xit);
274
275 CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
276 CFStringRef circle_name = SOSCircleGetName(circle);
277
278 if (CFEqual(cleanupPeerID, SOSPeerInfoGetPeerID(myPeerInfo))) {
279 CFErrorRef destroyError = NULL;
280 if (!SOSAccountDestroyCirclePeerInfo(account, circle, &destroyError)) {
281 secerror("Unable to destroy peer info: %@", destroyError);
282 }
283 CFReleaseSafe(destroyError);
284
285 account->departure_code = kSOSWithdrewMembership;
286
287 return true;
288 }
289
290 CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
291 CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
292
293
294 CFErrorRef localError = NULL;
295 SOSTransportMessageRef tMessage = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
296 if (!SOSTransportMessageCleanupAfterPeerMessages(tMessage, circleToPeerIDs, &localError)) {
297 secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
298 }
299 CFReleaseNull(localError);
300 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
301 if(SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer)) {
302 if (!SOSTransportCircleExpireRetirementRecords(tCircle, circleToPeerIDs, &localError)) {
303 secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
304 }
305 }
306 CFReleaseNull(localError);
307
308 CFReleaseNull(circleToPeerIDs);
309
310 xit:
311 return success;
312 }
313
314 bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
315 CFMutableDictionaryRef retirements_to_remove = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
316 CFDictionaryRef original_retired_peers = account->retired_peers;
317 __block bool success = true;
318 account->retired_peers = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
319
320 CFDictionaryForEach(original_retired_peers, ^(const void *key, const void *value) {
321 if (isString(key) && isDictionary(value)) {
322 CFStringRef circle_name = key;
323 __block CFMutableDictionaryRef still_active_circle_retirements = NULL;
324 CFDictionaryForEach((CFMutableDictionaryRef) value, ^(const void *key, const void *value) {
325 if (isString(key) && isData(value)) {
326 CFStringRef retired_peer_id = (CFStringRef) key;
327 SOSPeerInfoRef retired_peer = SOSPeerInfoCreateFromData(kCFAllocatorDefault, NULL, (CFDataRef) value);
328 if (retired_peer && SOSPeerInfoIsRetirementTicket(retired_peer) && CFEqual(retired_peer_id, SOSPeerInfoGetPeerID(retired_peer))) {
329 // He's a retired peer all right, if he's active or not yet expired we keep a record of his retirement.
330 // if not, clear any recordings of his retirement from our transport.
331 if (SOSAccountIsActivePeerInCircleNamed(account, circle_name, retired_peer_id, NULL) ||
332 !SOSPeerInfoRetireRetirementTicket(seconds, retired_peer)) {
333 // He's still around or not expired. Keep record.
334 if (still_active_circle_retirements == NULL) {
335 still_active_circle_retirements = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, circle_name);
336 }
337 CFDictionarySetValue(still_active_circle_retirements, retired_peer_id, value);
338 } else {
339 CFMutableArrayRef retirements = CFDictionaryEnsureCFArrayAndGetCurrentValue(retirements_to_remove, circle_name);
340 CFArrayAppendValue(retirements, retired_peer_id);
341 }
342 }
343 CFReleaseNull(retired_peer);
344 }
345 });
346
347 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, circle_name);
348 success &= SOSTransportCircleExpireRetirementRecords(tCircle, retirements_to_remove, error);
349 }
350 });
351
352 CFReleaseNull(original_retired_peers);
353 CFReleaseNull(retirements_to_remove);
354
355 return success;
356 }
357
358 bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
359 __block CFMutableDictionaryRef circle_retirees = (CFMutableDictionaryRef) CFDictionaryGetValue(account->retired_peers, SOSCircleGetName(circle));
360
361 SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
362 CFStringRef peer_id = SOSPeerInfoGetPeerID(peer);
363 if(!circle_retirees || !CFDictionaryGetValueIfPresent(circle_retirees, peer_id, NULL)) {
364 if (!circle_retirees) {
365 circle_retirees = CFDictionaryEnsureCFDictionaryAndGetCurrentValue(account->retired_peers, SOSCircleGetName(circle));
366 }
367 CFDataRef value = SOSPeerInfoCopyEncodedData(peer, NULL, NULL);
368 if(value) {
369 CFDictionarySetValue(circle_retirees, peer_id, value);
370 SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, error);
371 }
372 CFReleaseSafe(value);
373 }
374 });
375 return true;
376 }
377
378 SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
379 CFStringRef circle_to_mod = SOSCircleGetName(starting_circle);
380
381 SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
382 if(!new_circle) return NULL;
383
384 CFDictionaryRef circle_retirements = CFDictionaryGetValue(account->retired_peers, circle_to_mod);
385
386 if (isDictionary(circle_retirements)) {
387 CFDictionaryForEach(circle_retirements, ^(const void* id, const void* value) {
388 if (isData(value)) {
389 SOSPeerInfoRef pi = SOSPeerInfoCreateFromData(NULL, error, (CFDataRef) value);
390 if (pi && CFEqualSafe(id, SOSPeerInfoGetPeerID(pi))) {
391 SOSCircleUpdatePeerInfo(new_circle, pi);
392 }
393 CFReleaseSafe(pi);
394 }
395 });
396 }
397
398 if(SOSCircleCountPeers(new_circle) == 0) {
399 SOSCircleResetToEmpty(new_circle, NULL);
400 }
401
402 return new_circle;
403 }
404
405 //
406 // MARK: Circle Membership change notificaion
407 //
408
409 void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
410 CFArrayAppendValue(a->change_blocks, changeBlock);
411 }
412
413 void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
414 CFArrayRemoveAllValue(a->change_blocks, changeBlock);
415 }
416
417 void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
418 if (!changeBlock) return;
419
420 CFRetainSafe(ds_name);
421 SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
422 CFSetRef added_peers, CFSetRef removed_peers,
423 CFSetRef added_applicants, CFSetRef removed_applicants) {
424
425 if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
426 return;
427
428 SOSPeerInfoRef myPi = SOSAccountGetMyPeerInCircle(a, new_circle, NULL);
429 CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
430
431 CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
432 CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
433 CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
434
435 if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
436 SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
437 CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
438 });
439
440 CFSetForEach(added_peers, ^(const void *value) {
441 CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
442 });
443
444 CFSetForEach(removed_peers, ^(const void *value) {
445 CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
446 });
447 }
448
449 if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
450 changeBlock(peer_ids, added_ids, removed_ids);
451
452 CFReleaseSafe(peer_ids);
453 CFReleaseSafe(added_ids);
454 CFReleaseSafe(removed_ids);
455 };
456
457 CFRetainSafe(changeBlock);
458 SOSAccountAddChangeBlock(a, Block_copy(block_to_register));
459
460 CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
461 SOSCircleRef circle = (SOSCircleRef) CFDictionaryGetValue(a->circles, ds_name);
462 if (circle) {
463 block_to_register(circle, empty, empty, empty, empty);
464 }
465 CFReleaseSafe(empty);
466 }
467
468
469 bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
470 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, NULL);
471 if(!fpi) return false;
472 if(!SOSFullPeerInfoValidate(fpi, NULL)) return false;
473 CFErrorRef localError = NULL;
474 SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
475 CFStringRef retire_id = SOSPeerInfoGetPeerID(retire_peer);
476
477 // Account should move away from a dictionary of KVS keys to a Circle -> Peer -> Retirement ticket storage soonish.
478 CFStringRef retire_key = SOSRetirementKeyCreateWithCircleAndPeer(circle, retire_id);
479 CFDataRef retire_value = NULL;
480 bool retval = false;
481 bool writeCircle = false;
482
483 // Create a Retirement Ticket and store it in the retired_peers of the account.
484 require_action_quiet(retire_peer, errout, secerror("Create ticket failed for peer %@: %@", fpi, localError));
485 retire_value = SOSPeerInfoCopyEncodedData(retire_peer, NULL, &localError);
486 require_action_quiet(retire_value, errout, secerror("Failed to encode retirement peer %@: %@", retire_peer, localError));
487
488 // See if we need to repost the circle we could either be an applicant or a peer already in the circle
489 if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
490 // Remove our application if we have one.
491 SOSCircleWithdrawRequest(circle, retire_peer, NULL);
492 writeCircle = true;
493 } else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
494 if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
495 CFErrorRef cleanupError = NULL;
496 if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError))
497 secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
498 CFReleaseSafe(cleanupError);
499 }
500 writeCircle = true;
501 }
502
503 // Store the retirement record locally.
504 CFDictionarySetValue(account->retired_peers, retire_key, retire_value);
505
506 // Write retirement to Transport
507 SOSTransportCircleRef tCircle = (SOSTransportCircleRef)CFDictionaryGetValue(account->circle_transports, SOSCircleGetName(circle));
508 SOSTransportCirclePostRetirement(tCircle, SOSCircleGetName(circle), retire_id, retire_value, NULL); // TODO: Handle errors?
509
510 // Kill peer key but don't return error if we can't.
511 if(!SOSAccountDestroyCirclePeerInfo(account, circle, &localError))
512 secerror("Couldn't purge key for peer %@ on retirement: %@", fpi, localError);
513
514 if (writeCircle) {
515 CFDataRef circle_data = SOSCircleCopyEncodedData(circle, kCFAllocatorDefault, error);
516
517 if (circle_data) {
518 SOSTransportCirclePostCircle(tCircle, SOSCircleGetName(circle), circle_data, NULL); // TODO: Handle errors?
519 }
520 CFReleaseNull(circle_data);
521 }
522 retval = true;
523
524 errout:
525 CFReleaseNull(localError);
526 CFReleaseNull(retire_peer);
527 CFReleaseNull(retire_key);
528 CFReleaseNull(retire_value);
529 return retval;
530 }
531
532 /*
533 NSUbiquitousKeyValueStoreInitialSyncChange is only posted if there is any
534 local value that has been overwritten by a distant value. If there is no
535 conflict between the local and the distant values when doing the initial
536 sync (e.g. if the cloud has no data stored or the client has not stored
537 any data yet), you'll never see that notification.
538
539 NSUbiquitousKeyValueStoreInitialSyncChange implies an initial round trip
540 with server but initial round trip with server does not imply
541 NSUbiquitousKeyValueStoreInitialSyncChange.
542 */
543
544
545 //
546 // MARK: Status summary
547 //
548
549 static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) {
550 if (SOSCircleCountPeers(circle) == 0)
551 return kSOSCCCircleAbsent;
552
553 return kSOSCCNotInCircle;
554 }
555
556 static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
557 if (SOSCircleCountPeers(circle) == 0)
558 return kSOSCCCircleAbsent;
559
560 if (SOSCircleHasPeer(circle, this_peer, NULL))
561 return kSOSCCInCircle;
562
563 if (SOSCircleHasApplicant(circle, this_peer, NULL))
564 return kSOSCCRequestPending;
565
566 return kSOSCCNotInCircle;
567 }
568
569 static SOSCCStatus UnionStatus(SOSCCStatus accumulated_status, SOSCCStatus additional_circle_status) {
570 switch (additional_circle_status) {
571 case kSOSCCInCircle:
572 return accumulated_status;
573 case kSOSCCRequestPending:
574 return (accumulated_status == kSOSCCInCircle) ?
575 kSOSCCRequestPending :
576 accumulated_status;
577 case kSOSCCNotInCircle:
578 return (accumulated_status == kSOSCCInCircle ||
579 accumulated_status == kSOSCCRequestPending) ?
580 kSOSCCNotInCircle :
581 accumulated_status;
582 case kSOSCCCircleAbsent:
583 return (accumulated_status == kSOSCCInCircle ||
584 accumulated_status == kSOSCCRequestPending ||
585 accumulated_status == kSOSCCNotInCircle) ?
586 kSOSCCCircleAbsent :
587 accumulated_status;
588 default:
589 return additional_circle_status;
590 };
591
592 }
593
594 SOSCCStatus SOSAccountIsInCircles(SOSAccountRef account, CFErrorRef* error) {
595 if (!SOSAccountHasPublicKey(account, error)) {
596 return kSOSCCError;
597 }
598
599 __block bool set_once = false;
600 __block SOSCCStatus status = kSOSCCInCircle;
601
602 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
603 set_once = true;
604 status = UnionStatus(status, kSOSCCNotInCircle);
605 }, ^(SOSCircleRef circle) {
606 set_once = true;
607 status = UnionStatus(status, SOSCCCircleStatus(circle));
608 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
609 set_once = true;
610 SOSCCStatus circle_status = SOSCCThisDeviceStatusInCircle(circle, SOSFullPeerInfoGetPeerInfo(full_peer));
611 status = UnionStatus(status, circle_status);
612 });
613
614 if (!set_once)
615 status = kSOSCCCircleAbsent;
616
617 return status;
618 }
619
620 //
621 // MARK: Account Reset Circles
622 //
623
624 static bool SOSAccountResetThisCircleToOffering(SOSAccountRef account, SOSCircleRef circle, SecKeyRef user_key, CFErrorRef *error) {
625 SOSFullPeerInfoRef myCirclePeer = SOSAccountMakeMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
626 if (!myCirclePeer)
627 return false;
628 if(!SOSFullPeerInfoValidate(myCirclePeer, NULL)) return false;
629
630
631 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
632 bool result = false;
633 SOSFullPeerInfoRef cloud_identity = NULL;
634 CFErrorRef localError = NULL;
635
636 require_quiet(SOSCircleResetToOffering(circle, user_key, myCirclePeer, &localError), err_out);
637
638 {
639 SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
640 require_quiet(cloud_peer, err_out);
641 cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
642 CFReleaseNull(cloud_peer);
643 require_quiet(cloud_identity, err_out);
644 }
645
646 account->departure_code = kSOSNeverLeftCircle;
647 require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
648 require_quiet(SOSCircleAcceptRequest(circle, user_key, myCirclePeer, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
649 result = true;
650 SOSAccountPublishCloudParameters(account, NULL);
651
652 err_out:
653 if (result == false)
654 secerror("error resetting circle (%@) to offering: %@", circle, localError);
655 if (localError && error && *error == NULL) {
656 *error = localError;
657 localError = NULL;
658 }
659 CFReleaseNull(localError);
660 CFReleaseNull(cloud_identity);
661 return result;
662 });
663
664 return true;
665 }
666
667
668 bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
669 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
670 if (!user_key)
671 return false;
672
673 __block bool result = true;
674
675 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
676 SOSCircleRef circle = SOSCircleCreate(NULL, name, NULL);
677 if (circle)
678 CFDictionaryAddValue(account->circles, name, circle);
679
680 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
681 }, ^(SOSCircleRef circle) {
682 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
683 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
684 SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
685 });
686
687 return result;
688 }
689
690 bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
691 if (!SOSAccountHasPublicKey(account, error))
692 return false;
693
694 __block bool result = true;
695 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
696 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
697 if (!SOSCircleResetToEmpty(circle, error))
698 {
699 secerror("error: %@", *error);
700 result = false;
701 }
702 account->departure_code = kSOSWithdrewMembership;
703 return result;
704 });
705 });
706
707 return result;
708 }
709
710
711 //
712 // MARK: Joining
713 //
714
715 static bool SOSAccountJoinThisCircle(SOSAccountRef account, SecKeyRef user_key,
716 SOSCircleRef circle, bool use_cloud_peer, CFErrorRef* error) {
717 __block bool result = false;
718 __block SOSFullPeerInfoRef cloud_full_peer = NULL;
719
720 SOSFullPeerInfoRef myCirclePeer = SOSAccountMakeMyFullPeerInCircleNamed(account, SOSCircleGetName(circle), error);
721
722 require_action_quiet(myCirclePeer, fail,
723 SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Can't find/create peer for circle: %@"), circle));
724 if (use_cloud_peer) {
725 cloud_full_peer = SOSCircleGetiCloudFullPeerInfoRef(circle);
726 }
727
728 if (SOSCircleCountPeers(circle) == 0) {
729 result = SOSAccountResetThisCircleToOffering(account, circle, user_key, error);
730 } else {
731 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
732 result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
733 account->departure_code = kSOSNeverLeftCircle;
734 if(result && cloud_full_peer) {
735 CFErrorRef localError = NULL;
736 CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
737 require_quiet(cloudid, finish);
738 require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
739 require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
740 finish:
741 if (localError){
742 secerror("Failed to join with cloud identity: %@", localError);
743 CFReleaseNull(localError);
744 }
745 }
746 return result;
747 });
748 }
749
750 fail:
751 CFReleaseNull(cloud_full_peer);
752 return result;
753 }
754
755 static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
756 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
757 if (!user_key)
758 return false;
759
760 __block bool success = true;
761
762 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) { // Incompatible
763 success = false;
764 SOSCreateError(kSOSErrorIncompatibleCircle, CFSTR("Incompatible circle"), NULL, error);
765 }, ^(SOSCircleRef circle) { //no peer
766 success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
767 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) { // Have Peer
768 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(full_peer);
769 if(SOSCircleHasPeer(circle, myPeer, NULL)) goto already_present;
770 if(SOSCircleHasApplicant(circle, myPeer, NULL)) goto already_applied;
771 if(SOSCircleHasRejectedApplicant(circle, myPeer, NULL)) {
772 SOSCircleRemoveRejectedPeer(circle, myPeer, NULL);
773 }
774
775 secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(circle));
776 CFErrorRef localError = NULL;
777 if (!SOSAccountDestroyCirclePeerInfo(account, circle, &localError)) {
778 secerror("Failed to destroy peer (%@) during application, error=%@", myPeer, localError);
779 CFReleaseNull(localError);
780 }
781 already_applied:
782 success = SOSAccountJoinThisCircle(account, user_key, circle, use_cloud_identity, error) && success;
783 return;
784 already_present:
785 success = true;
786 return;
787 });
788
789 if(success) account->departure_code = kSOSNeverLeftCircle;
790 return success;
791 }
792
793 bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
794 return SOSAccountJoinCircles_internal(account, false, error);
795 }
796
797 CFStringRef SOSAccountGetDeviceID(SOSAccountRef account, CFErrorRef *error){
798 __block CFStringRef result = NULL;
799 __block CFStringRef temp = NULL;
800 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
801 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
802 if(fpi){
803 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
804 if(myPeer){
805 temp = SOSPeerInfoGetDeviceID(myPeer);
806 if(!isNull(temp)){
807 result = CFStringCreateCopy(kCFAllocatorDefault, temp);
808 }
809 }
810 else{
811 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
812 }
813 }
814 else{
815 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
816 }
817 });
818 return result;
819 }
820
821 bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
822 __block bool result = false;
823
824 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
825 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
826 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
827 if(fpi){
828 SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(fpi);
829 if(myPeer){
830 SOSPeerInfoSetDeviceID(myPeer, IDS);
831 result = true;
832 }
833 else{
834 secnotice("circle", "Could not acquire my peer info in circle: %@", SOSCircleGetName(circle));
835 result = false;
836 }
837 }
838 else{
839 secnotice("circle", "Could not acquire my full peer info in circle: %@", SOSCircleGetName(circle));
840 result = false;
841 }
842 return result;
843 });
844 });
845 return result;
846
847 }
848 bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
849 return SOSAccountJoinCircles_internal(account, true, error);
850 }
851
852
853 bool SOSAccountLeaveCircles(SOSAccountRef account, CFErrorRef* error)
854 {
855 __block bool result = true;
856 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
857 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
858 result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
859 return result;
860 });
861 });
862
863 account->departure_code = kSOSWithdrewMembership;
864 return result;
865 }
866
867 bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) {
868 dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
869 dispatch_group_t group = dispatch_group_create();
870 __block bool result = false;
871 secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
872 // Add a task to the group
873 dispatch_group_async(group, queue, ^{
874 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer) {
875 SOSAccountModifyCircle(account, SOSCircleGetName(circle), error, ^(SOSCircleRef circle) {
876 result = sosAccountLeaveCircle(account, circle, error); // TODO: What about multiple errors!
877 return result;
878 });
879 });
880
881 account->departure_code = kSOSWithdrewMembership;
882 });
883 dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
884
885 dispatch_group_wait(group, milestone);
886 dispatch_release(group);
887 return result;
888 }
889
890
891 //
892 // MARK: Application
893 //
894
895 static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
896 bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
897
898 SOSAccountForEachKnownCircle(account, NULL, NULL, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
899 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(full_peer);
900 CFErrorRef peer_error = NULL;
901 if (SOSCircleHasPeer(circle, me, &peer_error)) {
902 CFArrayForEach(peer_infos, ^(const void *value) {
903 SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
904 if (SOSCircleHasApplicant(circle, peer, NULL)) {
905 SOSAccountModifyCircle(account, SOSCircleGetName(circle), NULL, ^(SOSCircleRef circle) {
906 return action(circle, full_peer, peer);
907 });
908 }
909 });
910 }
911 if (peer_error)
912 secerror("Got error in SOSCircleHasPeer: %@", peer_error);
913 CFReleaseSafe(peer_error); // TODO: We should be accumulating errors here.
914 });
915 }
916
917 bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
918 SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
919 if (!user_key)
920 return false;
921
922 __block bool success = true;
923 __block int64_t num_peers = 0;
924
925 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
926 bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
927 if (!accepted)
928 success = false;
929 else
930 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
931 return accepted;
932 });
933
934 return success;
935 }
936
937 bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
938 __block bool success = true;
939 __block int64_t num_peers = 0;
940
941 for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
942 bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
943 if (!rejected)
944 success = false;
945 else
946 num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
947 return rejected;
948 });
949
950 return success;
951 }
952
953
954
955 CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
956 return CFSTR("We're compatible, go away");
957 }
958
959 enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
960 return account->departure_code;
961 }
962
963
964 CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
965 if (!SOSAccountHasPublicKey(account, error))
966 return NULL;
967 CFMutableArrayRef generations = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
968
969 SOSAccountForEachCircle(account, ^(SOSCircleRef circle) {
970 CFNumberRef generation = (CFNumberRef)SOSCircleGetGeneration(circle);
971 CFArrayAppendValue(generations, SOSCircleGetName(circle));
972 CFArrayAppendValue(generations, generation);
973 });
974
975 return generations;
976
977 }
978
979 bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
980 if (!SOSAccountHasPublicKey(account, error))
981 return NULL;
982
983 return account->user_public_trusted;
984 }
985
986
987 bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
988 __block bool result = true;
989
990 secnotice("updates", "Ensuring peer registration.");
991
992 SOSAccountForEachKnownCircle(account, ^(CFStringRef name) {
993 }, ^(SOSCircleRef circle) {
994 }, ^(SOSCircleRef circle, SOSFullPeerInfoRef full_peer) {
995 SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInCircle(account, circle, error);
996 SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(fpi);
997 CFMutableArrayRef trusted_peer_ids = NULL;
998 CFMutableArrayRef untrusted_peer_ids = NULL;
999 CFStringRef my_id = NULL;
1000 if (SOSCircleHasPeer(circle, me, NULL)) {
1001 my_id = SOSPeerInfoGetPeerID(me);
1002 trusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1003 untrusted_peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
1004 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1005 CFMutableArrayRef arrayToAddTo = SOSPeerInfoApplicationVerify(peer, account->user_public, NULL) ? trusted_peer_ids : untrusted_peer_ids;
1006
1007 CFArrayAppendValueIfNot(arrayToAddTo, SOSPeerInfoGetPeerID(peer), my_id);
1008 });
1009 }
1010
1011 SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(circle), NULL);
1012 if (engine)
1013 SOSEngineCircleChanged(engine, my_id, trusted_peer_ids, untrusted_peer_ids);
1014
1015 CFReleaseNull(trusted_peer_ids);
1016 CFReleaseNull(untrusted_peer_ids);
1017
1018 SOSTransportMessageRef transport = (SOSTransportMessageRef)CFDictionaryGetValue(account->message_transports, SOSCircleGetName(circle));
1019 SOSCircleForEachPeer(circle, ^(SOSPeerInfoRef peer) {
1020 if (!CFEqualSafe(me, peer)) {
1021 CFErrorRef localError = NULL;
1022 SOSPeerCoderInitializeForPeer(transport, full_peer, peer, &localError);
1023 if (localError)
1024 secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, full_peer, localError);
1025 CFReleaseSafe(localError);
1026 }
1027 });
1028 });
1029
1030 return result;
1031 }