#include "SOSAccountPriv.h"
#include <Security/SecureObjectSync/SOSPeerInfoCollections.h>
#include <Security/SecureObjectSync/SOSTransportCircle.h>
#include <Security/SecureObjectSync/SOSTransportMessage.h>
#include <Security/SecureObjectSync/SOSTransportMessageIDS.h>
#include <Security/SecureObjectSync/SOSKVSKeys.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameter.h>
#include <Security/SecureObjectSync/SOSTransportKeyParameterKVS.h>
#include <Security/SecureObjectSync/SOSEngine.h>
#include <Security/SecureObjectSync/SOSPeerCoder.h>
#include <Security/SecureObjectSync/SOSInternal.h>
#include <Security/SecureObjectSync/SOSRing.h>
#include <Security/SecureObjectSync/SOSRingUtils.h>
#include <Security/SecureObjectSync/SOSPeerInfoSecurityProperties.h>
#include <Security/SecureObjectSync/SOSPeerInfoV2.h>
#include <Security/SecItemInternal.h>
#include <SOSCircle/CKBridge/SOSCloudKeychainClient.h>
#include <SOSCircle/Regressions/SOSRegressionUtilities.h>
CFGiblisWithCompareFor(SOSAccount);
const CFStringRef SOSTransportMessageTypeIDS = CFSTR("IDS");
const CFStringRef SOSTransportMessageTypeKVS = CFSTR("KVS");
const CFStringRef kSOSDSIDKey = CFSTR("AccountDSID");
const CFStringRef kSOSUnsyncedViewsKey = CFSTR("unsynced");
#define DATE_LENGTH 25
const CFStringRef kSOSAccountDebugScope = CFSTR("Scope");
bool SOSAccountEnsureFactoryCircles(SOSAccountRef a)
{
bool result = false;
CFStringRef circle_name = NULL;
require_quiet(a, xit);
require_quiet(a->factory, xit);
circle_name = SOSDataSourceFactoryCopyName(a->factory);
require(circle_name, xit);
SOSAccountEnsureCircle(a, circle_name, NULL);
result = true;
xit:
CFReleaseNull(circle_name);
return result;
}
SOSAccountRef SOSAccountCreateBasic(CFAllocatorRef allocator,
CFDictionaryRef gestalt,
SOSDataSourceFactoryRef factory) {
SOSAccountRef a = CFTypeAllocate(SOSAccount, struct __OpaqueSOSAccount, allocator);
a->queue = dispatch_queue_create("Account Queue", DISPATCH_QUEUE_SERIAL);
a->notification_cleanups = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
a->gestalt = CFRetainSafe(gestalt);
a->trusted_circle = NULL;
a->trusted_rings = CFDictionaryCreateMutableForCFTypes(allocator);
a->backups = CFDictionaryCreateMutableForCFTypes(allocator);
a->my_identity = NULL;
a->retirees = CFSetCreateMutableForSOSPeerInfosByID(allocator);
a->factory = factory;
a->_user_private = NULL;
a->_password_tmp = NULL;
a->user_private_timer = NULL;
a->change_blocks = CFArrayCreateMutableForCFTypes(allocator);
a->departure_code = kSOSNeverAppliedToCircle;
a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
a->circle_transport = NULL;
a->kvs_message_transport = NULL;
a->ids_message_transport = NULL;
a->expansion = CFDictionaryCreateMutableForCFTypes(allocator);
return a;
}
SOSSecurityPropertyResultCode SOSAccountUpdateSecurityProperty(SOSAccountRef account, CFStringRef property, SOSSecurityPropertyActionCode actionCode, CFErrorRef *error) {
SOSSecurityPropertyResultCode retval = kSOSCCGeneralSecurityPropertyError;
bool updateCircle = false;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
retval = SOSFullPeerInfoUpdateSecurityProperty(account->my_identity, actionCode, property, error);
if(actionCode == kSOSCCSecurityPropertyEnable && retval == kSOSCCSecurityPropertyValid) {
updateCircle = true;
} else if(actionCode == kSOSCCSecurityPropertyDisable && retval == kSOSCCSecurityPropertyNotValid) {
updateCircle = true;
} else if(actionCode == kSOSCCSecurityPropertyPending) {
updateCircle = true;
}
if (updateCircle) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for security property change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
errOut:
return retval;
}
SOSSecurityPropertyResultCode SOSAccountSecurityPropertyStatus(SOSAccountRef account, CFStringRef property, CFErrorRef *error) {
SOSSecurityPropertyResultCode retval = kSOSCCGeneralViewError;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
retval = SOSFullPeerInfoSecurityPropertyStatus(account->my_identity, property, error);
errOut:
return retval;
}
bool SOSAccountUpdateGestalt(SOSAccountRef account, CFDictionaryRef new_gestalt)
{
if (CFEqualSafe(new_gestalt, account->gestalt))
return false;
if (account->trusted_circle && account->my_identity
&& SOSFullPeerInfoUpdateGestalt(account->my_identity, new_gestalt, NULL)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "dCalling SOSCircleUpdatePeerInfo for gestalt change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSAccountGetMyPeerInfo(account));
});
}
CFRetainAssign(account->gestalt, new_gestalt);
return true;
}
bool SOSAccountUpdateDSID(SOSAccountRef account, CFStringRef dsid){
SOSAccountSetValue(account, kSOSDSIDKey, dsid, NULL);
SOSTransportCircleSendOfficialDSID(account->circle_transport, dsid, NULL);
return true;
}
bool SOSAccountUpdateFullPeerInfo(SOSAccountRef account, CFSetRef minimumViews) {
if (account->trusted_circle && account->my_identity) {
if(SOSFullPeerInfoUpdateToCurrent(account->my_identity, minimumViews)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for gestalt change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
}
return true;
}
SOSViewResultCode SOSAccountUpdateView(SOSAccountRef account, CFStringRef viewname, SOSViewActionCode actionCode, CFErrorRef *error) {
SOSViewResultCode retval = kSOSCCGeneralViewError;
SOSViewResultCode currentStatus = kSOSCCGeneralViewError;
bool updateCircle = false;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
require_action_quiet((actionCode == kSOSCCViewEnable) || (actionCode == kSOSCCViewDisable), errOut, CFSTR("Invalid View Action"));
currentStatus = SOSAccountViewStatus(account, viewname, error);
require_action_quiet((currentStatus == kSOSCCViewNotMember) || (currentStatus == kSOSCCViewMember), errOut, CFSTR("View Membership Not Actionable"));
if (CFEqualSafe(viewname, kSOSViewKeychainV0)) {
if (SOSAccountSyncingV0(account)) {
require_action_quiet(actionCode = kSOSCCViewDisable, errOut, CFSTR("Can't disable V0 view and it's on right now"));
retval = kSOSCCViewMember;
} else {
require_action_quiet(actionCode = kSOSCCViewEnable, errOut, CFSTR("Can't enable V0 and it's off right now"));
retval = kSOSCCViewNotMember;
}
} else if (SOSAccountSyncingV0(account) && SOSViewsIsV0Subview(viewname)) {
require_action_quiet(actionCode = kSOSCCViewDisable, errOut, CFSTR("Have V0 peer can't disable"));
retval = kSOSCCViewMember;
} else {
if(actionCode == kSOSCCViewEnable && currentStatus == kSOSCCViewNotMember) {
retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
if(retval == kSOSCCViewMember) updateCircle = true;
} else if(actionCode == kSOSCCViewDisable && currentStatus == kSOSCCViewMember) {
retval = SOSFullPeerInfoUpdateViews(account->my_identity, actionCode, viewname, error);
if(retval == kSOSCCViewNotMember) updateCircle = true;
} else {
retval = currentStatus;
}
if (updateCircle) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
}
errOut:
return retval;
}
SOSViewResultCode SOSAccountViewStatus(SOSAccountRef account, CFStringRef viewname, CFErrorRef *error) {
SOSViewResultCode retval = kSOSCCGeneralViewError;
require_action_quiet(account->trusted_circle, errOut, SOSCreateError(kSOSErrorNoCircle, CFSTR("No Trusted Circle"), NULL, error));
require_action_quiet(account->my_identity, errOut, SOSCreateError(kSOSErrorPeerNotFound, CFSTR("No Peer for Account"), NULL, error));
retval = SOSFullPeerInfoViewStatus(account->my_identity, viewname, error);
errOut:
return retval;
}
static void dumpViewSet(CFStringRef label, CFSetRef views) {
if(views) {
secnotice("circleChange", "%@ list: %@", label, views);
} else {
secnotice("circleChange", "No %@ list provided.", label);
}
}
bool SOSAccountUpdateViewSets(SOSAccountRef account, CFSetRef enabledViews, CFSetRef disabledViews) {
bool updateCircle = false;
dumpViewSet(CFSTR("Enabled"), enabledViews);
dumpViewSet(CFSTR("Disabled"), disabledViews);
require_action_quiet(account->trusted_circle, errOut, secnotice("views", "Attempt to set viewsets with no trusted circle"));
require_action_quiet(account->my_identity, errOut, secnotice("views", "Attempt to set viewsets with no fullPeerInfo"));
require_action_quiet(enabledViews || disabledViews, errOut, secnotice("views", "No work to do"));
SOSFullPeerInfoRef fpi = SOSAccountGetMyFullPeerInfo(account);
SOSPeerInfoRef pi = SOSPeerInfoCreateCopy(kCFAllocatorDefault, SOSFullPeerInfoGetPeerInfo(fpi), NULL);
require_action_quiet(pi, errOut, secnotice("views", "Couldn't copy PeerInfoRef"));
if(!SOSPeerInfoVersionIsCurrent(pi)) {
if(!SOSPeerInfoUpdateToV2(pi, NULL)) {
secnotice("views", "Unable to update peer to V2- can't update views");
return false;
}
}
if(enabledViews) updateCircle = SOSViewSetEnable(pi, enabledViews);
if(disabledViews) updateCircle |= SOSViewSetDisable(pi, disabledViews);
if (updateCircle && SOSFullPeerInfoUpdateToThisPeer(fpi, pi, NULL)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle_to_change) {
secnotice("circleChange", "Calling SOSCircleUpdatePeerInfo for views change");
return SOSCircleUpdatePeerInfo(circle_to_change, SOSFullPeerInfoGetPeerInfo(account->my_identity));
});
}
errOut:
return updateCircle;
}
SOSAccountRef SOSAccountCreate(CFAllocatorRef allocator,
CFDictionaryRef gestalt,
SOSDataSourceFactoryRef factory) {
SOSAccountRef a = SOSAccountCreateBasic(allocator, gestalt, factory);
SOSAccountEnsureFactoryCircles(a);
return a;
}
static void SOSAccountDestroy(CFTypeRef aObj) {
SOSAccountRef a = (SOSAccountRef) aObj;
SOSAccountCleanupNotificationForAllPeers(a);
SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(a->factory, SOSCircleGetName(a->trusted_circle), NULL);
if (engine)
SOSEngineSetSyncCompleteListenerQueue(engine, NULL);
dispatch_sync(a->queue, ^{
CFReleaseNull(a->gestalt);
CFReleaseNull(a->my_identity);
CFReleaseNull(a->trusted_circle);
CFReleaseNull(a->trusted_rings);
CFReleaseNull(a->backups);
CFReleaseNull(a->retirees);
a->user_public_trusted = false;
CFReleaseNull(a->user_public);
CFReleaseNull(a->user_key_parameters);
SOSAccountPurgePrivateCredential(a);
CFReleaseNull(a->previous_public);
CFReleaseNull(a->_user_private);
CFReleaseNull(a->_password_tmp);
a->departure_code = kSOSNeverAppliedToCircle;
CFReleaseNull(a->kvs_message_transport);
CFReleaseNull(a->ids_message_transport);
CFReleaseNull(a->key_transport);
CFReleaseNull(a->circle_transport);
dispatch_release(a->queue);
CFReleaseNull(a->notification_cleanups);
dispatch_release(a->user_private_timer);
CFReleaseNull(a->change_blocks);
CFReleaseNull(a->expansion);
});
}
static OSStatus do_delete(CFDictionaryRef query) {
OSStatus result;
result = SecItemDelete(query);
if (result) {
secerror("SecItemDelete: %d", (int)result);
}
return result;
}
static int
do_keychain_delete_aks_bags()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
kSecAttrAccount, CFSTR("SecureBackupPublicKeybag"),
kSecAttrService, CFSTR("SecureBackupService"),
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_identities()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassKey,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.security.sos"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_lakitu()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.lakitu"),
kSecAttrAccount, CFSTR("EscrowServiceBypassToken"),
kSecAttrService, CFSTR("EscrowService"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
static int
do_keychain_delete_sbd()
{
OSStatus result;
CFDictionaryRef item = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
kSecClass, kSecClassGenericPassword,
kSecAttrSynchronizable, kCFBooleanTrue,
kSecUseTombstones, kCFBooleanFalse,
kSecAttrAccessGroup, CFSTR("com.apple.sbd"),
NULL);
result = do_delete(item);
CFReleaseSafe(item);
return result;
}
void SOSAccountSetToNew(SOSAccountRef a) {
secnotice("accountChange", "Setting Account to New");
int result = 0;
CFReleaseNull(a->my_identity);
CFReleaseNull(a->trusted_circle);
CFReleaseNull(a->trusted_rings);
CFReleaseNull(a->backups);
CFReleaseNull(a->retirees);
CFReleaseNull(a->user_key_parameters);
CFReleaseNull(a->user_public);
CFReleaseNull(a->previous_public);
CFReleaseNull(a->_user_private);
CFReleaseNull(a->_password_tmp);
CFReleaseNull(a->key_transport);
CFReleaseNull(a->circle_transport);
CFReleaseNull(a->kvs_message_transport);
CFReleaseNull(a->ids_message_transport);
CFReleaseNull(a->expansion);
result = do_keychain_delete_aks_bags();
secdebug("set to new", "result for deleting aks bags: %d", result);
result = do_keychain_delete_identities();
secdebug("set to new", "result for deleting identities: %d", result);
result = do_keychain_delete_lakitu();
secdebug("set to new", "result for deleting lakitu: %d", result);
result = do_keychain_delete_sbd();
secdebug("set to new", "result for deleting sbd: %d", result);
a->user_public_trusted = false;
a->departure_code = kSOSNeverAppliedToCircle;
a->user_private_timer = 0;
a->lock_notification_token = 0;
a->key_transport = (SOSTransportKeyParameterRef)SOSTransportKeyParameterKVSCreate(a, NULL);
a->circle_transport = NULL;
a->kvs_message_transport = NULL;
a->ids_message_transport = NULL;
a->trusted_rings = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
a->backups = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
a->retirees = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
a->expansion = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
SOSAccountEnsureFactoryCircles(a); }
static CFStringRef SOSAccountCopyFormatDescription(CFTypeRef aObj, CFDictionaryRef formatOptions) {
SOSAccountRef a = (SOSAccountRef) aObj;
CFStringRef gestaltDescription = CFDictionaryCopyCompactDescription(a->gestalt);
CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("<SOSAccount@%p: Gestalt: %@ Circle: %@ Me: %@>"), a, gestaltDescription, a->trusted_circle, a->my_identity);
CFReleaseNull(gestaltDescription);
return result;
}
CFStringRef SOSAccountCreateCompactDescription(SOSAccountRef a) {
CFStringRef gestaltDescription = CFDictionaryCopySuperCompactDescription(a->gestalt);
CFStringRef result = CFStringCreateWithFormat(NULL, NULL, CFSTR("%@"), gestaltDescription);
CFReleaseNull(gestaltDescription);
return result;
}
static Boolean SOSAccountCompare(CFTypeRef lhs, CFTypeRef rhs)
{
SOSAccountRef laccount = (SOSAccountRef) lhs;
SOSAccountRef raccount = (SOSAccountRef) rhs;
return CFEqualSafe(laccount->gestalt, raccount->gestalt)
&& CFEqualSafe(laccount->trusted_circle, raccount->trusted_circle)
&& CFEqualSafe(laccount->trusted_rings, raccount->trusted_rings)
&& CFEqualSafe(laccount->my_identity, raccount->my_identity);
}
dispatch_queue_t SOSAccountGetQueue(SOSAccountRef account) {
return account->queue;
}
void SOSAccountSetUserPublicTrustedForTesting(SOSAccountRef account){
account->user_public_trusted = true;
}
SOSFullPeerInfoRef SOSAccountCopyAccountIdentityPeerInfo(SOSAccountRef account, CFAllocatorRef allocator, CFErrorRef* error)
{
return CFRetainSafe(account->my_identity);
}
static bool SOSAccountThisDeviceCanSyncWithCircle(SOSAccountRef account) {
bool ok = false;
__block CFErrorRef error = NULL;
if (!SOSAccountHasPublicKey(account, &error)) {
CFReleaseSafe(error);
return false;
}
bool hasID = true;
require_action_quiet(account->my_identity, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Account identity not set"), NULL, &error));
if ((whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent) && account->ids_message_transport) {
CFStringRef deviceID = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
if(deviceID == NULL || CFStringGetLength(deviceID) == 0){
hasID = false;
secerror("Cannot sync with all peers at this time, securityd needs the IDS device ID first.");
__block bool success = false;
SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
success = (sync_error == NULL);
if (!success) {
CFRetainAssign(error, sync_error);
}
});
if(!success){
secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", error);
}
else{
secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
}
}
CFReleaseNull(deviceID);
}
require_action_quiet(account->trusted_circle, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Account trusted circle not set"), NULL, &error));
require_action_quiet(hasID, xit,
SOSCreateError(kSOSErrorBadFormat, CFSTR("Missing IDS device ID"), NULL, &error));
ok = SOSCircleHasPeerWithID(account->trusted_circle,
SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity)), &error);
xit:
if (!ok) {
secerror("sync with device failure: %@", error);
}
CFReleaseSafe(error);
return ok;
}
static bool SOSAccountIsThisPeerIDMe(SOSAccountRef account, CFStringRef circleName, CFStringRef peerID) {
SOSPeerInfoRef mypi = SOSFullPeerInfoGetPeerInfo(account->my_identity);
CFStringRef myPeerID = SOSPeerInfoGetPeerID(mypi);
return myPeerID && CFEqualSafe(myPeerID, peerID);
}
static bool isDefaultsWriteSetupToSyncOverIDS(){
return ((whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent));
}
bool SOSAccountSyncWithAllPeers(SOSAccountRef account, CFErrorRef *error)
{
bool result = true;
__block bool SyncingCompletedOverIDS = true;
__block bool SyncingCompletedOverKVS = true;
__block CFErrorRef localError = NULL;
SOSCircleRef circle = SOSAccountGetCircle(account, error);
CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef peerIds = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
require_action_quiet(SOSAccountThisDeviceCanSyncWithCircle(account), xit,
SOSCreateError(kSOSErrorNoCircle, CFSTR("This device cannot sync with circle"),
NULL, &localError));
SOSCircleForEachValidPeer(circle, account->user_public, ^(SOSPeerInfoRef peer) {
if (!SOSAccountIsThisPeerIDMe(account, SOSCircleGetName(circle), SOSPeerInfoGetPeerID(peer))) {
if (isDefaultsWriteSetupToSyncOverIDS() && SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(account->my_identity), peer)) {
secdebug("IDS Transport", "Syncing with IDS capable peers using IDS!");
CFMutableDictionaryRef circleToIdsId = CFDictionaryCreateMutableForCFTypesWith(kCFAllocatorDefault, SOSCircleGetName(circle), SOSPeerInfoGetPeerID((peer)), NULL);
SyncingCompletedOverIDS = SOSTransportMessageSyncWithPeers(account->ids_message_transport, circleToIdsId, &localError);
CFReleaseNull(circleToIdsId);
} else {
CFArrayAppendValue(peerIds, SOSPeerInfoGetPeerID(peer));
}
}
});
if (CFArrayGetCount(peerIds)) {
secnotice("KVS", "Syncing with KVS capable peers");
CFDictionarySetValue(circleToPeerIDs, SOSCircleGetName(circle), peerIds);
SyncingCompletedOverKVS &= SOSTransportMessageSyncWithPeers(account->kvs_message_transport, circleToPeerIDs, &localError);
}
SOSEngineRef engine = SOSTransportMessageGetEngine(account->kvs_message_transport);
result = SOSEngineSyncWithPeers(engine, account->ids_message_transport, account->kvs_message_transport, &localError);
result &= ((SyncingCompletedOverIDS) &&
(SyncingCompletedOverKVS || (CFDictionaryGetCount(circleToPeerIDs) == 0)));
if (result)
SetCloudKeychainTraceValueForKey(kCloudKeychainNumberOfTimesSyncedWithPeers, 1);
xit:
CFReleaseNull(circleToPeerIDs);
if (!result) {
secdebug("Account", "Could not sync with all peers: %@", localError);
CFErrorPropagate(localError, error);
localError = NULL;
}
CFReleaseNull(peerIds);
CFReleaseSafe(localError);
return result;
}
bool SOSAccountCleanupAfterPeer(SOSAccountRef account, size_t seconds, SOSCircleRef circle,
SOSPeerInfoRef cleanupPeer, CFErrorRef* error)
{
bool success = true;
SOSPeerInfoRef myPeerInfo = SOSFullPeerInfoGetPeerInfo(account->my_identity);
require_action_quiet(account->my_identity && myPeerInfo, xit, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("I have no peer")));
require_quiet(SOSCircleHasActivePeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), error), xit);
CFStringRef cleanupPeerID = SOSPeerInfoGetPeerID(cleanupPeer);
CFStringRef circle_name = SOSCircleGetName(circle);
CFMutableDictionaryRef circleToPeerIDs = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayAppendValue(CFDictionaryEnsureCFArrayAndGetCurrentValue(circleToPeerIDs, circle_name), cleanupPeerID);
CFErrorRef localError = NULL;
if (!(success &= SOSTransportMessageCleanupAfterPeerMessages(account->kvs_message_transport, circleToPeerIDs, &localError))) {
secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
}
if (account->ids_message_transport && !SOSTransportMessageCleanupAfterPeerMessages(account->ids_message_transport, circleToPeerIDs, &localError)) {
secnotice("account", "Failed to cleanup after peer %@ messages: %@", cleanupPeerID, localError);
}
CFReleaseNull(localError);
if((success &= SOSPeerInfoRetireRetirementTicket(seconds, cleanupPeer))) {
if (!(success &= SOSTransportCircleExpireRetirementRecords(account->circle_transport, circleToPeerIDs, &localError))) {
secnotice("account", "Failed to cleanup after peer %@ retirement: %@", cleanupPeerID, localError);
}
}
CFReleaseNull(localError);
CFReleaseNull(circleToPeerIDs);
xit:
return success;
}
bool SOSAccountCleanupRetirementTickets(SOSAccountRef account, size_t seconds, CFErrorRef* error) {
CFMutableSetRef retirees_to_remove = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
__block bool success = true;
CFSetForEach(account->retirees, ^(const void *value) {
SOSPeerInfoRef retiree = (SOSPeerInfoRef) value;
if (retiree) {
if (!SOSPeerInfoIsRetirementTicket(retiree) ||
(SOSPeerInfoRetireRetirementTicket(seconds, retiree) && !SOSCircleHasActivePeer(account->trusted_circle, retiree, NULL))) {
CFSetAddValue(retirees_to_remove, retiree);
};
}
});
CFMutableArrayRef retirees_to_cleanup = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFSetForEach(retirees_to_remove, ^(const void *value) {
CFArrayAppendValue(retirees_to_cleanup, value);
CFSetRemoveValue(account->retirees, value);
});
CFReleaseNull(retirees_to_remove);
CFDictionaryRef retirements_to_remove = CFDictionaryCreateForCFTypes(kCFAllocatorDefault,
SOSCircleGetName(account->trusted_circle), retirees_to_cleanup,
NULL);
CFReleaseNull(retirees_to_cleanup);
success = SOSTransportCircleExpireRetirementRecords(account->circle_transport, retirements_to_remove, error);
CFReleaseNull(retirements_to_remove);
return success;
}
bool SOSAccountScanForRetired(SOSAccountRef account, SOSCircleRef circle, CFErrorRef *error) {
SOSCircleForEachRetiredPeer(circle, ^(SOSPeerInfoRef peer) {
CFSetSetValue(account->retirees, peer);
CFErrorRef cleanupError = NULL;
if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, peer, &cleanupError)) {
secnotice("retirement", "Error cleaning up after peer, probably orphaned some stuff in KVS: (%@) – moving on", cleanupError);
}
CFReleaseSafe(cleanupError);
});
return true;
}
SOSCircleRef SOSAccountCloneCircleWithRetirement(SOSAccountRef account, SOSCircleRef starting_circle, CFErrorRef *error) {
SOSCircleRef new_circle = SOSCircleCopyCircle(NULL, starting_circle, error);
if(!new_circle) return NULL;
if (account->retirees) {
CFSetForEach(account->retirees, ^(const void* value) {
SOSPeerInfoRef pi = (SOSPeerInfoRef) value;
if (isSOSPeerInfo(pi)) {
SOSCircleUpdatePeerInfo(new_circle, pi);
}
});
}
if(SOSCircleCountPeers(new_circle) == 0) {
SOSCircleResetToEmpty(new_circle, NULL);
}
return new_circle;
}
void SOSAccountAddChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
SOSAccountCircleMembershipChangeBlock copy = Block_copy(changeBlock);
CFArrayAppendValue(a->change_blocks, copy);
CFReleaseNull(copy);
}
void SOSAccountRemoveChangeBlock(SOSAccountRef a, SOSAccountCircleMembershipChangeBlock changeBlock) {
CFArrayRemoveAllValue(a->change_blocks, changeBlock);
}
void SOSAccountAddSyncablePeerBlock(SOSAccountRef a, CFStringRef ds_name, SOSAccountSyncablePeersBlock changeBlock) {
if (!changeBlock) return;
CFRetainSafe(ds_name);
SOSAccountCircleMembershipChangeBlock block_to_register = ^void (SOSCircleRef new_circle,
CFSetRef added_peers, CFSetRef removed_peers,
CFSetRef added_applicants, CFSetRef removed_applicants) {
if (!CFEqualSafe(SOSCircleGetName(new_circle), ds_name))
return;
SOSPeerInfoRef myPi = SOSFullPeerInfoGetPeerInfo(a->my_identity);
CFStringRef myPi_id = myPi ? SOSPeerInfoGetPeerID(myPi) : NULL;
CFMutableArrayRef peer_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef added_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableArrayRef removed_ids = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
if (SOSCircleHasPeer(new_circle, myPi, NULL)) {
SOSCircleForEachPeer(new_circle, ^(SOSPeerInfoRef peer) {
CFArrayAppendValueIfNot(peer_ids, SOSPeerInfoGetPeerID(peer), myPi_id);
});
CFSetForEach(added_peers, ^(const void *value) {
CFArrayAppendValueIfNot(added_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
});
CFSetForEach(removed_peers, ^(const void *value) {
CFArrayAppendValueIfNot(removed_ids, SOSPeerInfoGetPeerID((SOSPeerInfoRef) value), myPi_id);
});
}
if (CFArrayGetCount(peer_ids) || CFSetContainsValue(removed_peers, myPi))
changeBlock(peer_ids, added_ids, removed_ids);
CFReleaseSafe(peer_ids);
CFReleaseSafe(added_ids);
CFReleaseSafe(removed_ids);
};
CFRetainSafe(changeBlock);
SOSAccountAddChangeBlock(a, block_to_register);
CFSetRef empty = CFSetCreateMutableForSOSPeerInfosByID(kCFAllocatorDefault);
if (a->trusted_circle && CFEqualSafe(ds_name, SOSCircleGetName(a->trusted_circle))) {
block_to_register(a->trusted_circle, empty, empty, empty, empty);
}
CFReleaseSafe(empty);
}
void SOSAccountPurgeIdentity(SOSAccountRef account) {
if (account->my_identity) {
CFErrorRef purgeError = NULL;
if (!SOSFullPeerInfoPurgePersistentKey(account->my_identity, &purgeError)) {
secwarning("Couldn't purge persistent key for %@ [%@]", account->my_identity, purgeError);
}
CFReleaseNull(purgeError);
CFReleaseNull(account->my_identity);
}
}
bool sosAccountLeaveCircle(SOSAccountRef account, SOSCircleRef circle, CFErrorRef* error) {
SOSFullPeerInfoRef fpi = account->my_identity;
if(!fpi) return false;
CFErrorRef localError = NULL;
bool retval = false;
SOSPeerInfoRef retire_peer = SOSFullPeerInfoPromoteToRetiredAndCopy(fpi, &localError);
if (!retire_peer) {
secerror("Create ticket failed for peer %@: %@", fpi, localError);
} else {
if(SOSCircleHasApplicant(circle, retire_peer, NULL)) {
SOSCircleWithdrawRequest(circle, retire_peer, NULL);
} else if (SOSCircleHasPeer(circle, retire_peer, NULL)) {
if (SOSCircleUpdatePeerInfo(circle, retire_peer)) {
CFErrorRef cleanupError = NULL;
if (!SOSAccountCleanupAfterPeer(account, RETIREMENT_FINALIZATION_SECONDS, circle, retire_peer, &cleanupError)) {
secerror("Error cleanup up after peer (%@): %@", retire_peer, cleanupError);
}
CFReleaseSafe(cleanupError);
}
}
CFSetAddValue(account->retirees, retire_peer);
CFErrorRef postError = NULL;
if (!SOSTransportCirclePostRetirement(account->circle_transport, SOSCircleGetName(circle), retire_peer, &postError)){
secwarning("Couldn't post retirement (%@)", postError);
}
if(!SOSTransportCircleFlushChanges(account->circle_transport, &postError)){
secwarning("Couldn't flush retirement data (%@)", postError);
}
CFReleaseNull(postError);
}
SOSAccountPurgeIdentity(account);
retval = true;
CFReleaseNull(localError);
CFReleaseNull(retire_peer);
return retval;
}
bool sosAccountLeaveRing(SOSAccountRef account, SOSRingRef ring, CFErrorRef* error) {
SOSFullPeerInfoRef fpi = account->my_identity;
if(!fpi) return false;
SOSPeerInfoRef pi = SOSFullPeerInfoGetPeerInfo(fpi);
CFStringRef peerID = SOSPeerInfoGetPeerID(pi);
CFErrorRef localError = NULL;
bool retval = false;
bool writeRing = false;
bool writePeerInfo = false;
if(SOSRingHasPeerID(ring, peerID)) {
writePeerInfo = true;
}
#if 0
if(SOSRingHasApplicant(ring, peerID)) {
writeRing = true;
}
#endif
if(writePeerInfo || writeRing) {
SOSRingWithdraw(ring, NULL, fpi, error);
}
CFDataRef peerInfoData = SOSFullPeerInfoCopyEncodedData(fpi, kCFAllocatorDefault, error);
SOSTransportCircleSendPeerInfo(account->circle_transport, peerID, peerInfoData, NULL);
if (writeRing) {
CFDataRef ring_data = SOSRingCopyEncodedData(ring, error);
if (ring_data) {
SOSTransportCircleRingPostRing(account->circle_transport, SOSRingGetName(ring), ring_data, NULL); }
CFReleaseNull(ring_data);
}
retval = true;
CFReleaseNull(localError);
return retval;
}
bool SOSAccountPostDebugScope(SOSAccountRef account, CFTypeRef scope, CFErrorRef *error) {
bool result = false;
SOSTransportCircleRef transport = account->circle_transport;
if (transport) {
result = SOSTransportCircleSendDebugInfo(transport, kSOSAccountDebugScope, scope, error);
}
return result;
}
static SOSCCStatus SOSCCThisDeviceStatusInCircle(SOSCircleRef circle, SOSPeerInfoRef this_peer) {
if (!circle)
return kSOSCCNotInCircle;
if (circle && SOSCircleCountPeers(circle) == 0)
return kSOSCCCircleAbsent;
if (this_peer) {
if(SOSPeerInfoIsRetirementTicket(this_peer))
return kSOSCCNotInCircle;
if (SOSCircleHasPeer(circle, this_peer, NULL))
return kSOSCCInCircle;
if (SOSCircleHasApplicant(circle, this_peer, NULL))
return kSOSCCRequestPending;
}
return kSOSCCNotInCircle;
}
bool SOSAccountIsInCircle(SOSAccountRef account, CFErrorRef *error) {
SOSCCStatus result = SOSAccountGetCircleStatus(account, error);
if (result != kSOSCCInCircle && result != kSOSCCError) {
SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("Not in circle"));
return false;
}
return true;
}
SOSCCStatus SOSAccountGetCircleStatus(SOSAccountRef account, CFErrorRef* error) {
if (!SOSAccountHasPublicKey(account, error)) {
return kSOSCCError;
}
return SOSCCThisDeviceStatusInCircle(account->trusted_circle, SOSAccountGetMyPeerInfo(account));
}
static bool SOSAccountResetCircleToOffering(SOSAccountRef account, SecKeyRef user_key, CFErrorRef *error) {
bool result = false;
require(SOSAccountHasCircle(account, error), fail);
require(SOSAccountEnsureFullPeerAvailable(account, error), fail);
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
bool result = false;
SOSFullPeerInfoRef cloud_identity = NULL;
CFErrorRef localError = NULL;
require_quiet(SOSCircleResetToOffering(circle, user_key, account->my_identity, &localError), err_out);
{
SOSPeerInfoRef cloud_peer = GenerateNewCloudIdentityPeerInfo(error);
require_quiet(cloud_peer, err_out);
cloud_identity = CopyCloudKeychainIdentity(cloud_peer, error);
CFReleaseNull(cloud_peer);
require_quiet(cloud_identity, err_out);
}
account->departure_code = kSOSNeverLeftCircle;
require_quiet(SOSCircleRequestAdmission(circle, user_key, cloud_identity, &localError), err_out);
require_quiet(SOSCircleAcceptRequest(circle, user_key, account->my_identity, SOSFullPeerInfoGetPeerInfo(cloud_identity), &localError), err_out);
result = true;
SOSAccountPublishCloudParameters(account, NULL);
err_out:
if (result == false)
secerror("error resetting circle (%@) to offering: %@", circle, localError);
if (localError && error && *error == NULL) {
*error = localError;
localError = NULL;
}
CFReleaseNull(localError);
CFReleaseNull(cloud_identity);
return result;
});
result = true;
fail:
return result;
}
bool SOSAccountResetToOffering(SOSAccountRef account, CFErrorRef* error) {
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
if (!user_key)
return false;
CFReleaseNull(account->my_identity);
return user_key && SOSAccountResetCircleToOffering(account, user_key, error);
}
bool SOSAccountResetToEmpty(SOSAccountRef account, CFErrorRef* error) {
if (!SOSAccountHasPublicKey(account, error))
return false;
CFReleaseNull(account->my_identity);
__block bool result = true;
account->departure_code = kSOSWithdrewMembership;
result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
result = SOSCircleResetToEmpty(circle, error);
return result;
});
if (!result) {
secerror("error: %@", error ? *error : NULL);
}
return result;
}
static bool SOSAccountHasBeenInSync(SOSAccountRef account) {
CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
CFSetRef unsynced = asSet(unsyncedObject, NULL);
return !(unsyncedObject == kCFBooleanTrue || (unsynced && (CFSetGetCount(unsynced) > 0)));
}
static bool SOSAccountUpdateOutOfSyncViews(SOSAccountRef account, CFSetRef viewsInSync) {
bool notifyOfChange = false;
SOSCCStatus circleStatus = SOSAccountGetCircleStatus(account, NULL);
bool inOrApplying = (circleStatus == kSOSCCInCircle) || (circleStatus == kSOSCCRequestPending);
CFTypeRef unsyncedObject = SOSAccountGetValue(account, kSOSUnsyncedViewsKey, NULL);
if (!inOrApplying) {
if (unsyncedObject != NULL) {
SOSAccountClearValue(account, kSOSUnsyncedViewsKey, NULL);
secnotice("initial-sync", "in sync, clearing pending");
notifyOfChange = true;
}
} else if (circleStatus == kSOSCCInCircle) {
__block CFMutableSetRef viewsToSync = CFSetCreateMutableForCFTypes(kCFAllocatorDefault);
SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
SOSPeerInfoWithEnabledViewSet(peer, ^(CFSetRef enabled) {
CFSetUnion(viewsToSync, enabled);
});
});
if (viewsInSync) {
CFSetSubtract(viewsToSync, viewsInSync);
}
if (unsyncedObject == kCFBooleanTrue) {
if (CFSetGetCount(viewsToSync) == 0) {
secnotice("initial-sync", "No views to wait for");
SOSAccountClearValue(account, kSOSUnsyncedViewsKey, NULL);
} else {
__block CFSetRef newViews = NULL;
SOSPeerInfoWithEnabledViewSet(SOSAccountGetMyPeerInfo(account), ^(CFSetRef enabled) {
newViews = CFSetCreateIntersection(kCFAllocatorDefault, enabled, viewsToSync);
});
secnotice("initial-sync", "Pending views set from True: %@", newViews);
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newViews, NULL);
CFReleaseNull(newViews);
}
notifyOfChange = true;
} else if (isSet(unsyncedObject)) {
CFSetRef waiting = (CFMutableSetRef) unsyncedObject;
CFSetRef newViews = CFSetCreateIntersection(kCFAllocatorDefault, waiting, viewsToSync);
if (!CFEqualSafe(waiting, newViews)) {
secnotice("initial-sync", "Pending views updated: %@", newViews);
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, newViews, NULL);
notifyOfChange = true;
}
CFReleaseNull(newViews);
}
CFReleaseNull(viewsToSync);
}
if (notifyOfChange) {
secnotice("initial-sync-notify", "In sync: Posting: %s", kSOSCCInitialSyncChangedNotification);
notify_post(kSOSCCInitialSyncChangedNotification);
account->circle_rings_retirements_need_attention = true;
}
return SOSAccountHasBeenInSync(account);
}
static void SOSAccountPeerGotInSync(SOSAccountRef account, CFStringRef peerID) {
secnotice("initial-sync", "Heard PeerID is in sync: %@", peerID);
if (account->trusted_circle) {
SOSPeerInfoRef peer = SOSCircleCopyPeerWithID(account->trusted_circle, peerID, NULL);
if (peer) {
CFSetRef views = SOSPeerInfoCopyEnabledViews(peer);
SOSAccountUpdateOutOfSyncViews(account, views);
CFReleaseNull(views);
}
CFReleaseNull(peer);
}
}
void SOSAccountCleanupNotificationForAllPeers(SOSAccountRef account) {
SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);
CFDictionaryForEach(account->notification_cleanups, ^(const void *key, const void *value) {
if (engine) {
SOSEngineSetSyncCompleteListener(engine, key, NULL);
}
dispatch_async(account->queue, value);
});
CFDictionaryRemoveAllValues(account->notification_cleanups);
}
static void SOSAccountCleanupNotificationForPeer(SOSAccountRef account, CFStringRef peerID) {
dispatch_block_t cleanup = CFDictionaryGetValue(account->notification_cleanups, peerID);
if (cleanup) {
SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);
if (engine) {
SOSEngineSetSyncCompleteListener(engine, peerID, NULL);
}
dispatch_async(account->queue, cleanup);
}
CFDictionaryRemoveValue(account->notification_cleanups, peerID);
}
static void SOSAccountRegisterCleanupBlock(SOSAccountRef account, CFStringRef peerID, dispatch_block_t block) {
dispatch_block_t copy = Block_copy(block);
CFDictionarySetValue(account->notification_cleanups, peerID, copy);
CFReleaseNull(copy);
}
void SOSAccountEnsureSyncChecking(SOSAccountRef account) {
if (CFDictionaryGetCount(account->notification_cleanups) == 0) {
SOSEngineRef engine = SOSDataSourceFactoryGetEngineForDataSourceName(account->factory, SOSCircleGetName(account->trusted_circle), NULL);
SOSEngineSetSyncCompleteListenerQueue(engine, account->queue);
if (engine) {
SOSAccountForEachCirclePeerExceptMe(account, ^(SOSPeerInfoRef peer) {
CFStringRef peerID = CFStringCreateCopy(kCFAllocatorDefault, SOSPeerInfoGetPeerID(peer));
SOSAccountRegisterCleanupBlock(account, peerID, ^{
CFReleaseSafe(peerID);
});
SOSEngineSetSyncCompleteListener(engine, peerID, ^{
SOSAccountPeerGotInSync(account, peerID);
SOSAccountCleanupNotificationForPeer(account, peerID);
SOSAccountFinishTransaction(account);
});
});
} else {
secerror("Couldn't find engine to setup notifications!!!");
}
}
}
void SOSAccountCancelSyncChecking(SOSAccountRef account) {
SOSAccountCleanupNotificationForAllPeers(account);
SOSAccountUpdateOutOfSyncViews(account, NULL);
}
bool SOSAccountCheckHasBeenInSync(SOSAccountRef account) {
bool hasBeenInSync = false;
if (!SOSAccountIsInCircle(account, NULL)) {
SOSAccountCancelSyncChecking(account);
} else {
hasBeenInSync = SOSAccountHasBeenInSync(account);
if (!hasBeenInSync) {
hasBeenInSync = SOSAccountUpdateOutOfSyncViews(account, NULL);
if (hasBeenInSync) {
SOSAccountCancelSyncChecking(account);
} else {
SOSAccountEnsureSyncChecking(account);
}
}
}
return hasBeenInSync;
}
static bool SOSAccountJoinCircle(SOSAccountRef account, SecKeyRef user_key,
bool use_cloud_peer, CFErrorRef* error) {
__block bool result = false;
__block SOSFullPeerInfoRef cloud_full_peer = NULL;
require_action_quiet(account->trusted_circle, fail, SOSCreateErrorWithFormat(kSOSErrorPeerNotFound, NULL, error, NULL, CFSTR("Don't have circle when joining???")));
require_quiet(SOSAccountEnsureFullPeerAvailable(account, error), fail);
SOSFullPeerInfoRef myCirclePeer = account->my_identity;
if (use_cloud_peer) {
cloud_full_peer = SOSCircleCopyiCloudFullPeerInfoRef(account->trusted_circle, NULL);
} else {
SOSAccountSetValue(account, kSOSUnsyncedViewsKey, kCFBooleanTrue, NULL);
}
if (SOSCircleCountPeers(account->trusted_circle) == 0) {
result = SOSAccountResetCircleToOffering(account, user_key, error);
} else {
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
result = SOSCircleRequestAdmission(circle, user_key, myCirclePeer, error);
account->departure_code = kSOSNeverLeftCircle;
if(result && cloud_full_peer) {
CFErrorRef localError = NULL;
CFStringRef cloudid = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(cloud_full_peer));
require_quiet(cloudid, finish);
require_quiet(SOSCircleHasActivePeerWithID(circle, cloudid, &localError), finish);
require_quiet(SOSCircleAcceptRequest(circle, user_key, cloud_full_peer, SOSFullPeerInfoGetPeerInfo(myCirclePeer), &localError), finish);
finish:
if (localError){
secerror("Failed to join with cloud identity: %@", localError);
CFReleaseNull(localError);
}
}
return result;
});
}
fail:
CFReleaseNull(cloud_full_peer);
return result;
}
static bool SOSAccountJoinCircles_internal(SOSAccountRef account, bool use_cloud_identity, CFErrorRef* error) {
bool success = false;
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
require_quiet(user_key, done);
require_action_quiet(account->trusted_circle, done, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle to join")));
if (account->my_identity != NULL) {
SOSPeerInfoRef myPeer = SOSFullPeerInfoGetPeerInfo(account->my_identity);
success = SOSCircleHasPeer(account->trusted_circle, myPeer, NULL);
require_quiet(!success, done);
SOSCircleRemoveRejectedPeer(account->trusted_circle, myPeer, NULL);
if (!SOSCircleHasApplicant(account->trusted_circle, myPeer, NULL)) {
secerror("Resetting my peer (ID: %@) for circle '%@' during application", SOSPeerInfoGetPeerID(myPeer), SOSCircleGetName(account->trusted_circle));
CFReleaseNull(account->my_identity);
myPeer = NULL;
}
}
success = SOSAccountJoinCircle(account, user_key, use_cloud_identity, error);
require_quiet(success, done);
account->departure_code = kSOSNeverLeftCircle;
done:
return success;
}
bool SOSAccountJoinCircles(SOSAccountRef account, CFErrorRef* error) {
return SOSAccountJoinCircles_internal(account, false, error);
}
CFStringRef SOSAccountCopyDeviceID(SOSAccountRef account, CFErrorRef *error){
CFStringRef result = NULL;
require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
result = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
fail:
return result;
}
bool SOSAccountSetMyDSID(SOSAccountRef account, CFStringRef IDS, CFErrorRef* error){
bool result = true;
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture){
secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
result = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef circle) {
SOSFullPeerInfoUpdateDeviceID(account->my_identity, IDS, error);
SOSFullPeerInfoUpdateTransportType(account->my_identity, SOSTransportMessageTypeIDS, error);
SOSFullPeerInfoUpdateTransportPreference(account->my_identity, kCFBooleanTrue, error);
return SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), NULL);
});
}
else
result = false;
}
else if(whichTransportType == kSOSTransportPresent){
secdebug("IDS Transport", "We are setting our device ID: %@", IDS);
if(IDS != NULL && (CFStringGetLength(IDS) > 0)){
require_action_quiet(account->my_identity, fail, SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No peer for me")));
result = SOSAccountModifyCircle(account, error, ^bool(SOSCircleRef circle) {
SOSFullPeerInfoUpdateDeviceID(account->my_identity, IDS, error);
SOSFullPeerInfoUpdateTransportType(account->my_identity, SOSTransportMessageTypeKVS, error);
SOSFullPeerInfoUpdateTransportPreference(account->my_identity, kCFBooleanTrue, error);
return SOSCircleHasPeer(circle, SOSFullPeerInfoGetPeerInfo(account->my_identity), NULL);
});
}
else
result = false;
}
SOSCCSyncWithAllPeers();
fail:
return result;
}
bool SOSAccountSendIDSTestMessage(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
bool result = true;
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFStringRef operation = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSSendOneMessage);
CFDataRef operationData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, operation, kCFStringEncodingUTF8, 0);
CFDataRef messageData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(operationData) + CFDataGetLength(messageData));
CFDataAppend(mutableData, operationData);
CFDataAppend(mutableData, messageData);
SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableData);
});
CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
CFReleaseNull(operation);
CFReleaseNull(mutableData);
CFReleaseNull(operationData);
CFReleaseNull(messageData);
CFReleaseNull(peerToMessage);
CFReleaseNull(circleToPeerMessages);
}
return result;
}
bool SOSAccountStartPingTest(SOSAccountRef account, CFStringRef message, CFErrorRef *error){
bool result = true;
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
CFMutableDictionaryRef circleToPeerMessages = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFMutableDictionaryRef peerToMessage = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
CFStringRef operation = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%d"), kIDSStartPingTestMessage);
CFDataRef operationData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, operation, kCFStringEncodingUTF8, 0);
CFDataRef messageData = CFStringCreateExternalRepresentation(kCFAllocatorDefault, message, kCFStringEncodingUTF8, 0);
CFMutableDataRef mutableData = CFDataCreateMutable(kCFAllocatorDefault, CFDataGetLength(operationData) + CFDataGetLength(messageData));
CFDataAppend(mutableData, operationData);
CFDataAppend(mutableData, messageData);
SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
CFDictionaryAddValue(peerToMessage, SOSPeerInfoGetPeerID(peer), mutableData);
});
CFDictionaryAddValue(circleToPeerMessages, SOSCircleGetName(account->trusted_circle), peerToMessage);
result = SOSTransportMessageSendMessages(account->ids_message_transport, circleToPeerMessages, error);
CFReleaseNull(operation);
CFReleaseNull(mutableData);
CFReleaseNull(operationData);
CFReleaseNull(messageData);
CFReleaseNull(peerToMessage);
CFReleaseNull(circleToPeerMessages);
}
return result;
}
bool SOSAccountRetrieveDeviceIDFromIDSKeychainSyncingProxy(SOSAccountRef account, CFErrorRef *error){
bool result = true;
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
__block bool success = false;
dispatch_semaphore_t wait_for = dispatch_semaphore_create(0);
dispatch_retain(wait_for);
SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
success = (sync_error == NULL);
if (error) {
CFRetainAssign(*error, sync_error);
}
dispatch_semaphore_signal(wait_for);
dispatch_release(wait_for);
});
dispatch_semaphore_wait(wait_for, DISPATCH_TIME_FOREVER);
dispatch_release(wait_for);
if(!success){
secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", *error);
}
else{
secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
}
}
return result;
}
bool SOSAccountJoinCirclesAfterRestore(SOSAccountRef account, CFErrorRef* error) {
return SOSAccountJoinCircles_internal(account, true, error);
}
bool SOSAccountLeaveCircle(SOSAccountRef account, CFErrorRef* error)
{
bool result = true;
result &= SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
return sosAccountLeaveCircle(account, circle, error);
});
account->departure_code = kSOSWithdrewMembership;
return result;
}
bool SOSAccountBail(SOSAccountRef account, uint64_t limit_in_seconds, CFErrorRef* error) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_t group = dispatch_group_create();
__block bool result = false;
secnotice("circle", "Attempting to leave circle - best effort - in %llu seconds\n", limit_in_seconds);
dispatch_group_async(group, queue, ^{
SOSAccountModifyCircle(account, error, ^(SOSCircleRef circle) {
return sosAccountLeaveCircle(account, circle, error);
});
});
dispatch_time_t milestone = dispatch_time(DISPATCH_TIME_NOW, limit_in_seconds * NSEC_PER_SEC);
dispatch_group_wait(group, milestone);
account->departure_code = kSOSWithdrewMembership;
dispatch_release(group);
return result;
}
static void for_each_applicant_in_each_circle(SOSAccountRef account, CFArrayRef peer_infos,
bool (^action)(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer)) {
SOSPeerInfoRef me = SOSFullPeerInfoGetPeerInfo(account->my_identity);
CFErrorRef peer_error = NULL;
if (account->trusted_circle && me &&
SOSCircleHasPeer(account->trusted_circle, me, &peer_error)) {
SOSAccountModifyCircle(account, NULL, ^(SOSCircleRef circle) {
__block bool modified = false;
CFArrayForEach(peer_infos, ^(const void *value) {
SOSPeerInfoRef peer = (SOSPeerInfoRef) value;
if (isSOSPeerInfo(peer) && SOSCircleHasApplicant(circle, peer, NULL)) {
if (action(circle, account->my_identity, peer)) {
modified = true;
}
}
});
return modified;
});
}
if (peer_error)
secerror("Got error in SOSCircleHasPeer: %@", peer_error);
CFReleaseSafe(peer_error); }
bool SOSAccountAcceptApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
SecKeyRef user_key = SOSAccountGetPrivateCredential(account, error);
if (!user_key)
return false;
__block bool success = true;
__block int64_t num_peers = 0;
for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
bool accepted = SOSCircleAcceptRequest(circle, user_key, myCirclePeer, peer, error);
if (!accepted)
success = false;
else
num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
return accepted;
});
return success;
}
bool SOSAccountRejectApplicants(SOSAccountRef account, CFArrayRef applicants, CFErrorRef* error) {
__block bool success = true;
__block int64_t num_peers = 0;
for_each_applicant_in_each_circle(account, applicants, ^(SOSCircleRef circle, SOSFullPeerInfoRef myCirclePeer, SOSPeerInfoRef peer) {
bool rejected = SOSCircleRejectRequest(circle, myCirclePeer, peer, error);
if (!rejected)
success = false;
else
num_peers = MAX(num_peers, SOSCircleCountPeers(circle));
return rejected;
});
return success;
}
CFStringRef SOSAccountCopyIncompatibilityInfo(SOSAccountRef account, CFErrorRef* error) {
return CFSTR("We're compatible, go away");
}
enum DepartureReason SOSAccountGetLastDepartureReason(SOSAccountRef account, CFErrorRef* error) {
return account->departure_code;
}
void SOSAccountSetLastDepartureReason(SOSAccountRef account, enum DepartureReason reason) {
account->departure_code = reason;
}
CFArrayRef SOSAccountCopyGeneration(SOSAccountRef account, CFErrorRef *error) {
CFArrayRef result = NULL;
CFNumberRef generation = NULL;
require_quiet(SOSAccountHasPublicKey(account, error), fail);
require_action_quiet(account->trusted_circle, fail, SOSErrorCreate(kSOSErrorNoCircle, error, NULL, CFSTR("No circle")));
generation = (CFNumberRef)SOSCircleGetGeneration(account->trusted_circle);
result = CFArrayCreateForCFTypes(kCFAllocatorDefault, generation, NULL);
fail:
return result;
}
bool SOSValidateUserPublic(SOSAccountRef account, CFErrorRef *error) {
if (!SOSAccountHasPublicKey(account, error))
return NULL;
return account->user_public_trusted;
}
bool SOSAccountEnsurePeerRegistration(SOSAccountRef account, CFErrorRef *error) {
bool result = true;
secnotice("updates", "Ensuring peer registration.");
require_quiet(account->trusted_circle, done);
require_quiet(account->my_identity, done);
require_quiet(SOSAccountIsMyPeerActive(account, NULL), done);
CFStringRef my_id = SOSPeerInfoGetPeerID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
SOSCircleForEachPeer(account->trusted_circle, ^(SOSPeerInfoRef peer) {
if (!SOSPeerInfoPeerIDEqual(peer, my_id)) {
CFErrorRef localError = NULL;
SOSTransportMessageRef messageTransport = NULL;
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
messageTransport = SOSPeerInfoHasDeviceID(peer) ? account->ids_message_transport : account->kvs_message_transport;
}
else
messageTransport = account->kvs_message_transport;
SOSPeerCoderInitializeForPeer(messageTransport->engine, account->my_identity, peer, &localError);
if (localError)
secnotice("updates", "can't initialize transport for peer %@ with %@ (%@)", peer, account->my_identity, localError);
CFReleaseSafe(localError);
}
});
if(whichTransportType == kSOSTransportIDS || whichTransportType == kSOSTransportFuture || whichTransportType == kSOSTransportPresent){
CFStringRef deviceID = SOSPeerInfoCopyDeviceID(SOSFullPeerInfoGetPeerInfo(account->my_identity));
if( deviceID == NULL || CFStringGetLength(deviceID) == 0){
__block bool success = false;
__block CFErrorRef localError = NULL;
SOSCloudKeychainGetIDSDeviceID(^(CFDictionaryRef returnedValues, CFErrorRef sync_error){
success = (sync_error == NULL);
if (localError) {
CFRetainAssign(localError, sync_error);
}
});
if(!success){
secerror("Could not ask IDSKeychainSyncingProxy for Device ID: %@", localError);
}
else{
secdebug("IDS Transport", "Attempting to retrieve the IDS Device ID");
}
CFReleaseNull(localError);
}
CFReleaseNull(deviceID);
}
done:
return result;
}
static inline bool SOSAccountEnsureExpansion(SOSAccountRef account, CFErrorRef *error) {
if (!account->expansion) {
account->expansion = CFDictionaryCreateMutableForCFTypes(NULL);
}
return SecAllocationError(account->expansion, error, CFSTR("Can't Alloc Account Expansion dictionary"));
}
bool SOSAccountClearValue(SOSAccountRef account, const void *key, CFErrorRef *error) {
bool success = SOSAccountEnsureExpansion(account, error);
require_quiet(success, errOut);
CFDictionaryRemoveValue(account->expansion, key);
errOut:
return success;
}
bool SOSAccountSetValue(SOSAccountRef account, const void *key, const void *value, CFErrorRef *error) {
bool success = SOSAccountEnsureExpansion(account, error);
require_quiet(success, errOut);
CFDictionarySetValue(account->expansion, key, value);
errOut:
return success;
}
const void *SOSAccountGetValue(SOSAccountRef account, const void *key, CFErrorRef *error) {
if (!account->expansion) {
return NULL;
}
return CFDictionaryGetValue(account->expansion, key);
}
void SOSAccountFinishTransaction(SOSAccountRef account) {
if(account->circle_rings_retirements_need_attention){
CFErrorRef localError = NULL;
if(!SOSTransportCircleFlushChanges(account->circle_transport, &localError)) {
secerror("flush circle failed %@", localError);
}
CFReleaseSafe(localError);
SOSAccountNotifyEngines(account); }
SOSAccountCheckHasBeenInSync(account);
account->circle_rings_retirements_need_attention = false;
}