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 20CFGiblisWithCompareFor(SOSAccount); 21 22 23bool 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 } 39xit: 40 return result; 41} 42 43 44SOSAccountRef 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 72bool 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 89SOSAccountRef 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 101static 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 127void 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 166static 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 172static 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 183dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) { 184 return account->queue; 185} 186 187CFDictionaryRef SOSAccountGetMessageTransports(SOSAccountRef account){ 188 return account->message_transports; 189} 190 191 192void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){ 193 account->user_public_trusted = true; 194} 195 196CFArrayRef 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 209static 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 217static 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 228bool 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 264bool 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 308xit: 309 return success; 310} 311 312bool 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 356bool 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 376SOSCircleRef 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 407void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) { 408 CFArrayAppendValue(a->change_blocks, changeBlock); 409} 410 411void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) { 412 CFArrayRemoveAllValue(a->change_blocks, changeBlock); 413} 414 415void 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 467bool 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 521errout: 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 546static SOSCCStatus SOSCCCircleStatus(SOSCircleRef circle) { 547 if (SOSCircleCountPeers(circle) == 0) 548 return kSOSCCCircleAbsent; 549 550 return kSOSCCNotInCircle; 551} 552 553static 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 566static 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 591SOSCCStatus 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 621static 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 663bool 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 685bool 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 710static 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 745fail: 746 CFReleaseNull(cloud_full_peer); 747 return result; 748} 749 750static 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 788bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) { 789 return SOSAccountJoinCircles_internal(account, false, error); 790} 791 792CFStringRef 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 816bool 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} 843bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) { 844 return SOSAccountJoinCircles_internal(account, true, error); 845} 846 847 848bool 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 862bool 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 890static 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 912bool 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 932bool 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 950CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) { 951 return CFSTR("We're compatible, go away"); 952} 953 954enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) { 955 return account->departure_code; 956} 957 958 959CFArrayRef 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 974bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) { 975 if (!SOSAccountHasPublicKey(account, error)) 976 return NULL; 977 978 return account->user_public_trusted; 979} 980 981 982bool 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} 1027