#include <Security/SecureObjectSync/SOSPeer.h>
#include <Security/SecureObjectSync/SOSCoder.h>
#include <Security/SecureObjectSync/SOSDigestVector.h>
#include <Security/SecureObjectSync/SOSInternal.h>
#include <Security/SecureObjectSync/SOSTransport.h>
#include <Security/SecureObjectSync/SOSViews.h>
#include <Security/SecureObjectSync/SOSChangeTracker.h>
#include <utilities/SecCFError.h>
#include <utilities/SecCFRelease.h>
#include <utilities/SecCFWrappers.h>
#include <utilities/SecDb.h>
#include <utilities/SecFileLocations.h>
#include <utilities/SecIOFormat.h>
#include <utilities/array_size.h>
#include <utilities/debugging.h>
#include <Security/SecureObjectSync/SOSBackupEvent.h>
#include <Security/SecItemBackup.h>
#include <securityd/SOSCloudCircleServer.h>
#include <CoreFoundation/CoreFoundation.h>
#include <stdlib.h>
#include <AssertMacros.h>
#include <securityd/SecKeybagSupport.h>
#include <notify.h>
static CFStringRef kSOSPeerSequenceNumberKey = CFSTR("sequence-number");
static CFStringRef kSOSPeerGetObjectsKey = CFSTR("get-objects");
static CFStringRef kSOSPeerReceivedUnknownConfirmedDigestKey = CFSTR("received-unknown");
static CFStringRef kSOSPeerJoinRequestedKey = CFSTR("join-requested");
static CFStringRef kSOSPeerSkipHelloKey = CFSTR("skip-hello");
CFStringRef kSOSPeerDataLabel = CFSTR("iCloud Peer Data Meta-data");
static CFStringRef kSOSPeerSendObjectsKey = CFSTR("send-objects"); static CFStringRef kSOSPeerMustSendMessageKey = CFSTR("must-send"); static CFStringRef kSOSPeerPendingObjectsKey = CFSTR("pending-objects"); static CFStringRef kSOSPeerUnwantedManifestKey = CFSTR("unwanted-manifest"); static CFStringRef kSOSPeerConfirmedManifestKey = CFSTR("confirmed-manifest"); static CFStringRef kSOSPeerProposedManifestKey = CFSTR("pending-manifest"); static CFStringRef kSOSPeerLocalManifestKey = CFSTR("local-manifest"); static CFStringRef kSOSPeerVersionKey = CFSTR("vers"); static CFStringRef kSOSPeerCoderKey = CFSTR("coder");
static CFStringRef kSOSPeerPeerIDKey = CFSTR("peer-id"); static CFStringRef kSOSPeerViewsKey = CFSTR("views"); static CFStringRef kSOSPeerKeyBagKey = CFSTR("keybag");
static SOSPeerMetaRef SOSPeerMetaCreate(CFStringRef peerID) {
return CFRetain(peerID);
}
static SOSPeerMetaRef SOSPeerMetaCreateWithViews(CFStringRef peerID, CFSetRef views) {
const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey };
const void *values[] = { peerID, views };
return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
static SOSPeerMetaRef SOSPeerMetaCreateWithViewsAndKeyBag(CFStringRef peerID, CFSetRef views, CFDataRef keybag) {
const void *keys[] = { kSOSPeerPeerIDKey, kSOSPeerViewsKey, kSOSPeerKeyBagKey };
const void *values[] = { peerID, views, keybag };
return CFDictionaryCreate(kCFAllocatorDefault, keys, values, array_size(keys), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
}
SOSPeerMetaRef SOSPeerMetaCreateWithComponents(CFStringRef peerID, CFSetRef views, CFDataRef keybag) {
if (!isString(peerID))
return NULL;
if (views) {
if (keybag)
return SOSPeerMetaCreateWithViewsAndKeyBag(peerID, views, keybag);
else
return SOSPeerMetaCreateWithViews(peerID, views);
} else
return SOSPeerMetaCreate(peerID);
}
SOSPeerMetaRef SOSPeerMetaCreateWithState(CFStringRef peerID, CFDictionaryRef state) {
return SOSPeerMetaCreateWithComponents(peerID, CFDictionaryGetValue(state, kSOSPeerViewsKey), CFDictionaryGetValue(state, kSOSPeerKeyBagKey));
}
CFStringRef SOSPeerMetaGetComponents(SOSPeerMetaRef peerMeta, CFSetRef *views, CFDataRef *keybag, CFErrorRef *error) {
if (isDictionary(peerMeta)) {
CFDictionaryRef meta = (CFDictionaryRef)peerMeta;
CFStringRef peerID = asString(CFDictionaryGetValue(meta, kSOSPeerPeerIDKey), error);
CFSetRef vns = asSet(CFDictionaryGetValue(meta, kSOSPeerViewsKey), error);
if (vns && asDataOptional(CFDictionaryGetValue(meta, kSOSPeerKeyBagKey), keybag, error)) {
if (views)
*views = vns;
return peerID;
}
return NULL;
} else {
if (views) {
*views = SOSViewsGetV0ViewSet();
}
return asString(peerMeta, error);
}
}
CFTypeRef SOSPeerOrStateSetViewsKeyBagAndCreateCopy(CFTypeRef peerOrState, CFSetRef views, CFDataRef keyBag) {
assert(views);
if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) {
SOSPeerRef peer = (SOSPeerRef)peerOrState;
SOSPeerSetViewNameSet(peer, views);
SOSPeerSetKeyBag(peer, keyBag);
return CFRetainSafe(peer);
} else if (peerOrState) {
CFMutableDictionaryRef state = (CFMutableDictionaryRef)peerOrState;
CFDictionarySetValue(state, kSOSPeerViewsKey, views);
if (keyBag)
CFDictionarySetValue(state, kSOSPeerKeyBagKey, keyBag);
else
CFDictionaryRemoveValue(state, kSOSPeerKeyBagKey);
return CFRetainSafe(state);
} else {
if (keyBag)
return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, kSOSPeerKeyBagKey, keyBag, NULL);
else
return CFDictionaryCreateForCFTypes(kCFAllocatorDefault, kSOSPeerViewsKey, views, NULL);
}
}
CFTypeRef SOSPeerOrStateSetViewsAndCopyState(CFTypeRef peerOrState, CFSetRef views) {
assert(views);
if (peerOrState && CFGetTypeID(peerOrState) == SOSPeerGetTypeID()) {
SOSPeerRef peer = (SOSPeerRef)peerOrState;
SOSPeerSetViewNameSet(peer, views);
return SOSPeerCopyState(peer, NULL);
} else if (peerOrState) {
CFMutableDictionaryRef state = (CFMutableDictionaryRef)peerOrState;
CFDictionarySetValue(state, kSOSPeerViewsKey, views);
return CFRetainSafe(peerOrState);
} else {
return NULL;
}
}
bool SOSPeerMapEntryIsBackup(const void *mapEntry) {
if (!mapEntry) return false;
if (CFGetTypeID(mapEntry) == SOSPeerGetTypeID()) {
return SOSPeerGetKeyBag((SOSPeerRef)mapEntry);
} else {
return CFDictionaryContainsKey(mapEntry, kSOSPeerKeyBagKey);
}
}
enum {
kSOSPeerMaxManifestWindowDepth = 4
};
static CFStringRef SOSManifestCreateOptionalDescriptionWithLabel(SOSManifestRef manifest, CFStringRef label) {
if (!manifest) return CFSTR(" - ");
return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@%@"), label, manifest);
}
static CFStringRef SOSManifestArrayCreateOptionalDescriptionWithLabel(CFArrayRef manifests, CFStringRef label) {
CFIndex count = manifests ? CFArrayGetCount(manifests) : 0;
if (count == 0) return CFSTR(" - ");
SOSManifestRef manifest = (SOSManifestRef)CFArrayGetValueAtIndex(manifests, 0);
return CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR(" %@[%" PRIdCFIndex "]%@"), label, count, manifest);
}
static void SOSManifestArraySetManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) {
if (manifest) {
if (*manifests)
CFArrayRemoveAllValues(*manifests);
else
*manifests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
CFArrayAppendValue(*manifests, manifest);
} else {
CFReleaseNull(*manifests);
}
}
static void SOSManifestMutableArrayAppendManifest(CFMutableArrayRef manifests, SOSManifestRef manifest) {
if (manifest) {
CFIndex count = CFArrayGetCount(manifests);
CFIndex ixOfManifest = CFArrayGetFirstIndexOfValue(manifests, CFRangeMake(0, count), manifest);
if (ixOfManifest != 0) {
if (ixOfManifest != kCFNotFound) {
CFArrayRemoveValueAtIndex(manifests, ixOfManifest);
} else {
while (count >= kSOSPeerMaxManifestWindowDepth)
CFArrayRemoveValueAtIndex(manifests, --count);
}
CFArrayInsertValueAtIndex(manifests, 0, manifest);
}
} else {
CFArrayRemoveAllValues(manifests);
}
}
static void SOSManifestArrayAppendManifest(CFMutableArrayRef *manifests, SOSManifestRef manifest) {
if (*manifests)
SOSManifestMutableArrayAppendManifest(*manifests, manifest);
else
SOSManifestArraySetManifest(manifests, manifest);
}
struct __OpaqueSOSPeer {
CFRuntimeBase _base;
SOSCoderRef coder;
CFDataRef coderData;
CFStringRef peer_id;
CFSetRef views;
CFIndex version;
uint64_t sequenceNumber;
bool mustSendMessage;
bool sendObjects;
SOSManifestRef pendingObjects;
SOSManifestRef unwantedManifest;
SOSManifestRef confirmedManifest;
CFMutableArrayRef proposedManifests;
CFMutableArrayRef localManifests;
CFDataRef _keyBag;
FILE *journalFile;
};
CFGiblisWithCompareFor(SOSPeer)
static CFStringRef SOSPeerCopyFormatDescription(CFTypeRef cf, CFDictionaryRef formatOptions) {
SOSPeerRef peer = (SOSPeerRef)cf;
if(peer){
CFStringRef po = SOSManifestCreateOptionalDescriptionWithLabel(peer->pendingObjects, CFSTR("O"));
CFStringRef uo = SOSManifestCreateOptionalDescriptionWithLabel(peer->unwantedManifest, CFSTR("U"));
CFStringRef co = SOSManifestCreateOptionalDescriptionWithLabel(peer->confirmedManifest, CFSTR("C"));
CFStringRef pe = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->proposedManifests, CFSTR("P"));
CFStringRef lo = SOSManifestArrayCreateOptionalDescriptionWithLabel(peer->localManifests, CFSTR("L"));
CFStringRef desc = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("<%@ %s%s%@%@%@%@%@>"),
SOSPeerGetID(peer),
SOSPeerMustSendMessage(peer) ? "F" : "f",
SOSPeerSendObjects(peer) ? "S" : "s",
po, uo, co, pe, lo);
CFReleaseSafe(lo);
CFReleaseSafe(pe);
CFReleaseSafe(co);
CFReleaseSafe(uo);
CFReleaseSafe(po);
return desc;
}
else
return CFSTR("NULL");
}
static Boolean SOSPeerCompare(CFTypeRef cfA, CFTypeRef cfB)
{
SOSPeerRef peerA = (SOSPeerRef)cfA, peerB = (SOSPeerRef)cfB;
return CFStringCompare(SOSPeerGetID(peerA), SOSPeerGetID(peerB), 0) == kCFCompareEqualTo;
}
static void SOSPeerSetCoderData(SOSPeerRef peer, CFDataRef coderData){
if (peer->coder) {
SOSCoderDispose(peer->coder);
peer->coder = NULL;
}
CFRetainAssign(peer->coderData, coderData);
}
static bool SOSPeerCopyCoderData(SOSPeerRef peer, CFDataRef *coderData, CFErrorRef *error) {
bool ok = true;
if (peer->coder) {
#if 1
CFErrorRef localError = NULL;
ok = *coderData = SOSCoderCopyDER(peer->coder, &localError);
if (!ok) {
secerror("failed to der encode coder for peer %@, dropping it: %@", peer->peer_id, localError);
SOSCoderDispose(peer->coder);
peer->coder = NULL;
CFErrorPropagate(localError, error);
}
return ok;
#else
CFAssignRetained(peer->coderData, SOSCoderCopyDER(peer->coder, error));
ok = peer->coderData;
SOSCoderDispose(peer->coder);
peer->coder = NULL;
#endif
}
*coderData = CFRetainSafe(peer->coderData);
return ok;
}
SOSCoderRef SOSPeerGetCoder(SOSPeerRef peer, CFErrorRef *error) {
if (peer->coderData) {
peer->coder = SOSCoderCreateFromData(peer->coderData, error);
CFReleaseNull(peer->coderData);
} else if (!peer->coder) {
SOSErrorCreate(kSOSErrorPeerNotFound, error, NULL, CFSTR("No coderData nor coder for peer: %@"), peer->peer_id);
}
return peer->coder;
}
bool SOSPeerEnsureCoder(SOSPeerRef peer, SOSFullPeerInfoRef myPeerInfo, SOSPeerInfoRef peerInfo, CFErrorRef *error) {
if (!SOSPeerGetCoder(peer, NULL)) {
secinfo("peer", "New coder for id %@.", peer->peer_id);
CFErrorRef localError = NULL;
if(SOSPeerInfoShouldUseIDSTransport(SOSFullPeerInfoGetPeerInfo(myPeerInfo), peerInfo))
peer->coder = SOSCoderCreate(peerInfo, myPeerInfo, kCFBooleanTrue, &localError);
else
peer->coder = SOSCoderCreate(peerInfo, myPeerInfo, kCFBooleanFalse, &localError);
if (!peer->coder) {
secerror("Failed to create coder for %@: %@", peer->peer_id, localError);
CFErrorPropagate(localError, error);
return false;
}
}
return true;
}
static bool SOSPeerGetPersistedBoolean(CFDictionaryRef persisted, CFStringRef key) {
CFBooleanRef boolean = CFDictionaryGetValue(persisted, key);
return boolean && CFBooleanGetValue(boolean);
}
static CFDataRef SOSPeerGetPersistedData(CFDictionaryRef persisted, CFStringRef key) {
return asData(CFDictionaryGetValue(persisted, key), NULL);
}
static int64_t SOSPeerGetPersistedInt64(CFDictionaryRef persisted, CFStringRef key) {
int64_t integer = 0;
CFNumberRef number = CFDictionaryGetValue(persisted, key);
if (number) {
CFNumberGetValue(number, kCFNumberSInt64Type, &integer);
}
return integer;
}
static void SOSPeerGetOptionalPersistedCFIndex(CFDictionaryRef persisted, CFStringRef key, CFIndex *value) {
CFNumberRef number = CFDictionaryGetValue(persisted, key);
if (number) {
CFNumberGetValue(number, kCFNumberCFIndexType, value);
}
}
static CFSetRef SOSPeerGetPersistedViewNameSet(SOSPeerRef peer, CFDictionaryRef persisted, CFStringRef key) {
CFSetRef vns = CFDictionaryGetValue(persisted, key);
if (!vns) {
vns = SOSViewsGetV0ViewSet();
secnotice("peer", "%@ had no views, inferring: %@", peer->peer_id, vns);
}
return vns;
}
void SOSBackupPeerPostNotification(const char *reason) {
secnotice("backup", "posting notification to CloudServices: %s", reason?reason:"");
notify_post(kSecItemBackupNotification);
}
static bool SOSPeerDoWithJournalPath(SOSPeerRef peer, CFErrorRef *error, void(^with)(const char *journalPath)) {
bool ok = true;
char strBuffer[PATH_MAX + 1];
size_t userTempLen = confstr(_CS_DARWIN_USER_TEMP_DIR, strBuffer, sizeof(strBuffer));
if (userTempLen == 0) {
ok = SecCheckErrno(-1, error, CFSTR("confstr on _CS_DARWIN_USER_TEMP_DIR returned an error."));
} else {
CFStringRef journalName = CFStringCreateWithFormat(kCFAllocatorDefault, NULL, CFSTR("%s/SOSBackup-%@"), strBuffer, SOSPeerGetID(peer));
CFStringPerformWithCString(journalName, with);
CFReleaseSafe(journalName);
}
return ok;
}
static FILE *fopen_journal(const char *journalPath, const char *mode, CFErrorRef *error) {
FILE *file = fopen(journalPath, mode);
SecCheckErrno(!file, error, CFSTR("fopen %s,%s"), journalPath, mode);
return file;
}
#include <sys/stat.h>
#if !defined(NDEBUG)
static off_t getFileSize(int fd) {
return lseek(fd, 0, SEEK_END);
}
#endif
int SOSPeerHandoffFD(SOSPeerRef peer, CFErrorRef *error) {
__block int fd = -1;
SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) {
fd = open(journalName, O_RDONLY | O_CLOEXEC);
if (SecCheckErrno(fd < 0, error, CFSTR("open %s"), journalName)) {
if (!SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName)) {
close(fd);
fd = -1;
} else {
secdebug("backup", "Handing off file %s with fd %d of size %llu", journalName, fd, getFileSize(fd));
}
} else {
secdebug("backup", "Handing off file %s failed, %@", journalName, error?*error:NULL);
}
});
return fd;
}
static CFDataRef SOSPeerCopyAKSKeyBag(SOSPeerRef peer, CFErrorRef *error) {
if (CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb)) {
return CFRetainSafe(peer->_keyBag);
} else {
CFDataRef aksKeybag = NULL;
SOSBackupSliceKeyBagRef backupSliceKeyBag = SOSBackupSliceKeyBagCreateFromData(kCFAllocatorDefault, peer->_keyBag, error);
if (backupSliceKeyBag) {
aksKeybag = SOSBSKBCopyAKSBag(backupSliceKeyBag, error);
CFRelease(backupSliceKeyBag);
}
return aksKeybag;
}
}
bool SOSPeerAppendToJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile, keybag_handle_t kbhandle)) {
__block bool ok = true;
ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) {
FILE *file = fopen_journal(fname, "a", error);
if (file) {
keybag_handle_t kbhandle = bad_keybag_handle;
CFDataRef keybag = SOSPeerCopyAKSKeyBag(peer, error);
ok = keybag;
if (ok && (ok = ks_open_keybag(keybag, NULL, &kbhandle, error))) {
with(file, kbhandle);
if (kbhandle != bad_keybag_handle)
ok &= ks_close_keybag(kbhandle, error);
}
CFReleaseSafe(keybag);
fclose(file);
}
});
return ok;
}
static bool SOSPeerTruncateJournal(SOSPeerRef peer, CFErrorRef *error, void(^with)(FILE *journalFile)) {
__block bool ok = true;
ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *fname) {
FILE *file = fopen_journal(fname, "w", error);
if (file) {
with(file);
fclose(file);
}
});
return ok;
}
bool SOSPeerSetState(SOSPeerRef p, SOSEngineRef engine, CFDictionaryRef state, CFErrorRef *error) {
bool ok = true;
if (state) {
SOSPeerGetOptionalPersistedCFIndex(state, kSOSPeerVersionKey, &p->version);
p->sequenceNumber = SOSPeerGetPersistedInt64(state, kSOSPeerSequenceNumberKey);
p->mustSendMessage = SOSPeerGetPersistedBoolean(state, kSOSPeerMustSendMessageKey);
p->sendObjects = SOSPeerGetPersistedBoolean(state, kSOSPeerSendObjectsKey);
CFRetainAssign(p->views, SOSPeerGetPersistedViewNameSet(p, state, kSOSPeerViewsKey));
SOSPeerSetKeyBag(p, SOSPeerGetPersistedData(state, kSOSPeerKeyBagKey));
if (!p->coder)
SOSPeerSetCoderData(p, SOSPeerGetPersistedData(state, kSOSPeerCoderKey));
CFAssignRetained(p->pendingObjects, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerPendingObjectsKey));
CFAssignRetained(p->unwantedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerUnwantedManifestKey));
CFAssignRetained(p->confirmedManifest, SOSEngineCopyPersistedManifest(engine, state, kSOSPeerConfirmedManifestKey));
CFAssignRetained(p->proposedManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerProposedManifestKey, error));
ok &= p->proposedManifests != NULL;
CFAssignRetained(p->localManifests, SOSEngineCopyPersistedManifestArray(engine, state, kSOSPeerLocalManifestKey, error));
ok &= p->localManifests != NULL;
}
return ok;
}
static SOSPeerRef SOSPeerCreate_Internal(SOSEngineRef engine, CFDictionaryRef state, CFStringRef theirPeerID, CFIndex version, CFErrorRef *error) {
SOSPeerRef p = CFTypeAllocate(SOSPeer, struct __OpaqueSOSPeer, kCFAllocatorDefault);
p->peer_id = CFRetainSafe(theirPeerID);
p->version = version;
CFDictionaryRef empty = NULL;
if (!state) {
empty = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
state = empty;
}
if (!SOSPeerSetState(p, engine, state, error)) {
CFReleaseNull(p);
}
CFReleaseNull(empty);
return p;
}
static bool SOSPeerPersistOptionalCoder(SOSPeerRef peer, CFMutableDictionaryRef persist, CFStringRef key, CFErrorRef *error) {
CFDataRef coderData = NULL;
bool ok = SOSPeerCopyCoderData(peer, &coderData, error);
if (coderData) {
CFDictionarySetValue(persist, key, coderData);
CFReleaseSafe(coderData);
}
return ok;
}
static void SOSPeerPersistBool(CFMutableDictionaryRef persist, CFStringRef key, bool value) {
CFDictionarySetValue(persist, key, value ? kCFBooleanTrue : kCFBooleanFalse);
}
static void SOSPeerPersistInt64(CFMutableDictionaryRef persist, CFStringRef key, int64_t value) {
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberSInt64Type, &value);
CFDictionarySetValue(persist, key, number);
CFReleaseSafe(number);
}
static void SOSPeerPersistCFIndex(CFMutableDictionaryRef persist, CFStringRef key, CFIndex value) {
CFNumberRef number = CFNumberCreate(kCFAllocatorDefault, kCFNumberCFIndexType, &value);
CFDictionarySetValue(persist, key, number);
CFReleaseSafe(number);
}
static bool SOSPeerPersistOptionalManifest(CFMutableDictionaryRef persist, CFStringRef key, SOSManifestRef manifest, CFErrorRef *error) {
if (!manifest)
return true;
CFDataRef digest = SOSManifestGetDigest(manifest, error);
bool ok = digest;
if (ok)
CFDictionarySetValue(persist, key, digest);
return ok;
}
static bool SSOSPeerPersistManifestArray(CFMutableDictionaryRef persist, CFStringRef key, CFArrayRef manifests, CFErrorRef *error) {
CFMutableArrayRef digests = CFArrayCreateMutableForCFTypes(kCFAllocatorDefault);
SOSManifestRef manifest;
if (manifests) CFArrayForEachC(manifests, manifest) {
CFDataRef digest = SOSManifestGetDigest(manifest, error);
if (!digest)
CFReleaseNull(digests);
if (digests) {
CFArrayAppendValue(digests, digest);
}
}
if (digests) {
CFDictionarySetValue(persist, key, digests);
CFRelease(digests);
}
return digests;
}
static void SOSPeerPersistOptionalValue(CFMutableDictionaryRef persist, CFStringRef key, CFTypeRef value) {
if (value)
CFDictionarySetValue(persist, key, value);
}
CFDictionaryRef SOSPeerCopyState(SOSPeerRef peer, CFErrorRef *error) {
CFMutableDictionaryRef state = CFDictionaryCreateMutableForCFTypes(kCFAllocatorDefault);
SOSPeerPersistInt64(state, kSOSPeerSequenceNumberKey, peer->sequenceNumber);
if (peer->version) {
SOSPeerPersistCFIndex(state, kSOSPeerVersionKey, peer->version);
}
SOSPeerPersistBool(state, kSOSPeerMustSendMessageKey, peer->mustSendMessage);
SOSPeerPersistBool(state, kSOSPeerSendObjectsKey, peer->sendObjects);
SOSPeerPersistOptionalValue(state, kSOSPeerViewsKey, peer->views);
CFDataRef keybag = SOSPeerGetKeyBag(peer);
if (keybag && !CFEqual(peer->peer_id, kSOSViewKeychainV0_tomb))
SOSPeerPersistOptionalValue(state, kSOSPeerKeyBagKey, keybag);
if (!SOSPeerPersistOptionalCoder(peer, state, kSOSPeerCoderKey, error)
|| !SOSPeerPersistOptionalManifest(state, kSOSPeerPendingObjectsKey, peer->pendingObjects, error)
|| !SOSPeerPersistOptionalManifest(state, kSOSPeerUnwantedManifestKey, peer->unwantedManifest, error)
|| !SOSPeerPersistOptionalManifest(state, kSOSPeerConfirmedManifestKey, peer->confirmedManifest, error)
|| !SSOSPeerPersistManifestArray(state, kSOSPeerProposedManifestKey, peer->proposedManifests, error)
|| !SSOSPeerPersistManifestArray(state, kSOSPeerLocalManifestKey, peer->localManifests, error)) {
CFReleaseNull(state);
}
return state;
}
SOSPeerRef SOSPeerCreateWithState(SOSEngineRef engine, CFStringRef peer_id, CFDictionaryRef state, CFErrorRef *error) {
return SOSPeerCreate_Internal(engine, state, peer_id, 0, error);
}
static void SOSPeerDestroy(CFTypeRef cf) {
SOSPeerRef peer = (SOSPeerRef)cf;
CFReleaseNull(peer->peer_id);
CFReleaseNull(peer->views);
SOSPeerSetCoderData(peer, NULL);
CFReleaseNull(peer->pendingObjects);
CFReleaseNull(peer->unwantedManifest);
CFReleaseNull(peer->confirmedManifest);
CFReleaseNull(peer->proposedManifests);
CFReleaseNull(peer->localManifests);
}
bool SOSPeerDidConnect(SOSPeerRef peer) {
SOSPeerSetMustSendMessage(peer, true);
SOSPeerSetProposedManifest(peer, SOSPeerGetConfirmedManifest(peer));
return true;
}
CFIndex SOSPeerGetVersion(SOSPeerRef peer) {
return peer->version;
}
CFStringRef SOSPeerGetID(SOSPeerRef peer) {
return peer->peer_id;
}
CFSetRef SOSPeerGetViewNameSet(SOSPeerRef peer) {
return peer->views;
}
void SOSPeerSetViewNameSet(SOSPeerRef peer, CFSetRef views) {
CFRetainAssign(peer->views, views);
}
CFDataRef SOSPeerGetKeyBag(SOSPeerRef peer) {
return peer->_keyBag;
}
static bool SOSPeerUnlinkBackupJournal(SOSPeerRef peer, CFErrorRef *error) {
__block bool ok = true;
ok &= SOSPeerDoWithJournalPath(peer, error, ^(const char *journalName) {
secnotice("backup", "%@ unlinking journal file %s", peer, journalName);
ok &= SecCheckErrno(unlink(journalName), error, CFSTR("unlink %s"), journalName);
});
return ok;
}
static bool SOSPeerWriteReset(SOSPeerRef peer, CFErrorRef *error) {
__block bool ok = true;
__block CFErrorRef localError = NULL;
ok &= SOSPeerTruncateJournal(peer, &localError, ^(FILE *journalFile) {
ok = SOSBackupEventWriteReset(journalFile, peer->_keyBag, &localError);
if (ok && !peer->_keyBag)
ok = SOSBackupEventWriteCompleteMarker(journalFile, 999, &localError);
});
if (!ok) {
secwarning("%@ failed to write reset to backup journal: %@", peer->peer_id, localError);
CFErrorPropagate(localError, error);
}
SOSPeerSetConfirmedManifest(peer, NULL);
SOSPeerSetProposedManifest(peer, NULL);
SOSPeerSetMustSendMessage(peer, !ok);
return ok;
}
void SOSPeerKeyBagDidChange(SOSPeerRef peer) {
SOSPeerSetSendObjects(peer, false);
if (!peer->_keyBag) {
SOSPeerUnlinkBackupJournal(peer, NULL);
} else {
SOSPeerWriteReset(peer, NULL);
SOSCCSyncWithAllPeers();
}
}
void SOSPeerSetKeyBag(SOSPeerRef peer, CFDataRef keyBag) {
if (CFEqualSafe(keyBag, peer->_keyBag)) return;
bool hadKeybag = peer->_keyBag;
if (!keyBag) {
secwarning("%@ keybag for backup unset", SOSPeerGetID(peer));
} else {
secnotice("backup", "%@ backup bag: %@", SOSPeerGetID(peer), keyBag);
}
CFRetainAssign(peer->_keyBag, keyBag);
if (hadKeybag) {
SOSPeerKeyBagDidChange(peer);
}
}
bool SOSPeerWritePendingReset(SOSPeerRef peer, CFErrorRef *error) {
return !SOSPeerMustSendMessage(peer) || SOSPeerWriteReset(peer, error);
}
uint64_t SOSPeerNextSequenceNumber(SOSPeerRef peer) {
return ++peer->sequenceNumber;
}
uint64_t SOSPeerGetMessageVersion(SOSPeerRef peer) {
return SOSPeerGetVersion(peer);
}
bool SOSPeerMustSendMessage(SOSPeerRef peer) {
return peer->mustSendMessage;
}
void SOSPeerSetMustSendMessage(SOSPeerRef peer, bool sendMessage) {
peer->mustSendMessage = sendMessage;
}
bool SOSPeerSendObjects(SOSPeerRef peer) {
return peer->sendObjects;
}
void SOSPeerSetSendObjects(SOSPeerRef peer, bool sendObjects) {
peer->sendObjects = sendObjects;
}
SOSManifestRef SOSPeerGetProposedManifest(SOSPeerRef peer) {
if (peer->proposedManifests && CFArrayGetCount(peer->proposedManifests))
return (SOSManifestRef)CFArrayGetValueAtIndex(peer->proposedManifests, 0);
return NULL;
}
SOSManifestRef SOSPeerGetConfirmedManifest(SOSPeerRef peer) {
return peer->confirmedManifest;
}
void SOSPeerSetConfirmedManifest(SOSPeerRef peer, SOSManifestRef confirmed) {
CFRetainAssign(peer->confirmedManifest, confirmed);
}
void SOSPeerAddProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) {
SOSManifestArrayAppendManifest(&peer->proposedManifests, proposed);
}
void SOSPeerSetProposedManifest(SOSPeerRef peer, SOSManifestRef proposed) {
SOSManifestArraySetManifest(&peer->proposedManifests, proposed);
}
void SOSPeerAddLocalManifest(SOSPeerRef peer, SOSManifestRef local) {
SOSManifestArrayAppendManifest(&peer->localManifests, local);
}
SOSManifestRef SOSPeerGetPendingObjects(SOSPeerRef peer) {
return peer->pendingObjects;
}
void SOSPeerSetPendingObjects(SOSPeerRef peer, SOSManifestRef pendingObjects) {
CFRetainAssign(peer->pendingObjects, pendingObjects);
}
SOSManifestRef SOSPeerGetUnwantedManifest(SOSPeerRef peer) {
return peer->unwantedManifest;
}
void SOSPeerSetUnwantedManifest(SOSPeerRef peer, SOSManifestRef unwantedManifest) {
CFRetainAssign(peer->unwantedManifest, unwantedManifest);
}
SOSManifestRef SOSPeerCopyManifestForDigest(SOSPeerRef peer, CFDataRef digest) {
if (!digest) return NULL;
SOSManifestRef manifest;
if (peer->proposedManifests) CFArrayForEachC(peer->proposedManifests, manifest) {
if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL)))
return CFRetainSafe(manifest);
}
if (peer->localManifests) CFArrayForEachC(peer->localManifests, manifest) {
if (CFEqual(digest, SOSManifestGetDigest(manifest, NULL)))
return CFRetainSafe(manifest);
}
if (peer->confirmedManifest && CFEqual(digest, SOSManifestGetDigest(peer->confirmedManifest, NULL)))
return CFRetainSafe(peer->confirmedManifest);
return NULL;
}
static void SOSMarkManifestInUse(struct SOSDigestVector *mdInUse, SOSManifestRef manifest) {
CFDataRef digest = SOSManifestGetDigest(manifest, NULL);
if (digest)
SOSDigestVectorAppend(mdInUse, CFDataGetBytePtr(digest));
}
static void SOSMarkManifestsInUse(struct SOSDigestVector *mdInUse, CFArrayRef manifests) {
if (!isArray(manifests)) return;
SOSManifestRef manifest = NULL;
CFArrayForEachC(manifests, manifest) {
SOSMarkManifestInUse(mdInUse, manifest);
}
}
void SOSPeerMarkDigestsInUse(SOSPeerRef peer, struct SOSDigestVector *mdInUse) {
SOSMarkManifestInUse(mdInUse, peer->pendingObjects);
SOSMarkManifestInUse(mdInUse, peer->unwantedManifest);
SOSMarkManifestInUse(mdInUse, peer->confirmedManifest);
SOSMarkManifestsInUse(mdInUse, peer->localManifests);
SOSMarkManifestsInUse(mdInUse, peer->proposedManifests);
}
static void SOSAddManifestInUse(CFMutableDictionaryRef mfc, SOSManifestRef manifest) {
CFDataRef digest = SOSManifestGetDigest(manifest, NULL);
CFDataRef data = SOSManifestGetData(manifest);
if (digest && data)
CFDictionarySetValue(mfc, digest, data);
}
static void SOSAddManifestsInUse(CFMutableDictionaryRef mfc, CFArrayRef manifests) {
if (!isArray(manifests)) return;
SOSManifestRef manifest = NULL;
CFArrayForEachC(manifests, manifest) {
SOSAddManifestInUse(mfc, manifest);
}
}
void SOSPeerAddManifestsInUse(SOSPeerRef peer, CFMutableDictionaryRef mfc) {
SOSAddManifestInUse(mfc, peer->pendingObjects);
SOSAddManifestInUse(mfc, peer->unwantedManifest);
SOSAddManifestInUse(mfc, peer->confirmedManifest);
SOSAddManifestsInUse(mfc, peer->localManifests);
SOSAddManifestsInUse(mfc, peer->proposedManifests);
}
bool SOSPeerDidReceiveRemovalsAndAdditions(SOSPeerRef peer, SOSManifestRef absentFromRemote, SOSManifestRef additionsFromRemote, SOSManifestRef unwantedFromRemote,
SOSManifestRef local, CFErrorRef *error) {
bool ok = true;
SOSManifestRef remoteMissing = NULL, sharedRemovals = NULL, sharedAdditions = NULL;
ok = ok && (remoteMissing = SOSManifestCreateIntersection(absentFromRemote, local, error)); ok = ok && (sharedRemovals = SOSManifestCreateComplement(remoteMissing, absentFromRemote, error)); ok = ok && (sharedAdditions = SOSManifestCreateIntersection(additionsFromRemote, local, error));
secnotice("peer", "%@ RM:%@ SR:%@ SA:%@ UR:%@", peer, remoteMissing, sharedRemovals, sharedAdditions, unwantedFromRemote);
SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, sharedAdditions, remoteMissing, error);
SOSManifestRef unwantedManifest = SOSManifestCreateWithPatch(peer->unwantedManifest, sharedRemovals, unwantedFromRemote, error);
CFAssignRetained(peer->pendingObjects, pendingObjectsManifest); CFAssignRetained(peer->unwantedManifest, unwantedManifest);
CFReleaseSafe(remoteMissing);
CFReleaseSafe(sharedRemovals);
CFReleaseSafe(sharedAdditions);
secnotice("peer", "%@ C:%@ U:%@ O:%@", peer, SOSPeerGetConfirmedManifest(peer), SOSPeerGetUnwantedManifest(peer), SOSPeerGetPendingObjects(peer));
return ok;
}
bool SOSPeerDataSourceWillCommit(SOSPeerRef peer, SOSDataSourceTransactionSource source, SOSManifestRef removals, SOSManifestRef additions, CFErrorRef *error) {
bool isAPITransaction = source == kSOSDataSourceAPITransaction;
SOSManifestRef unconfirmedAdditions = NULL;
if (isAPITransaction && SOSManifestGetCount(additions)) {
unconfirmedAdditions = SOSManifestCreateComplement(SOSPeerGetConfirmedManifest(peer), additions, error);
}
if (SOSManifestGetCount(removals) || SOSManifestGetCount(unconfirmedAdditions)) {
SOSManifestRef pendingObjectsManifest = SOSManifestCreateWithPatch(peer->pendingObjects, removals, unconfirmedAdditions, error);
{
SOSManifestRef unpended = NULL, pended = NULL;
SOSManifestDiff(peer->pendingObjects, pendingObjectsManifest, &unpended, &pended, error);
secinfo("peer", "%@: willCommit R:%@ A:%@ UA:%@ %s O%s%@%s%@",
SOSPeerGetID(peer), removals, additions, unconfirmedAdditions,
(isAPITransaction ? "api": "sos"),
(SOSManifestGetCount(unpended) ? "-" : ""),
(SOSManifestGetCount(unpended) ? (CFStringRef)unpended : CFSTR("")),
(SOSManifestGetCount(pended) ? "+" : SOSManifestGetCount(unpended) ? "" : "="),
(SOSManifestGetCount(pended) ? (CFStringRef)pended : CFSTR("")));
CFReleaseSafe(unpended);
CFReleaseSafe(pended);
}
CFAssignRetained(peer->pendingObjects, pendingObjectsManifest);
}
CFReleaseSafe(unconfirmedAdditions);
return true;
}
bool SOSPeerWriteAddEvent(FILE *journalFile, keybag_handle_t kbhandle, SOSDataSourceRef dataSource, SOSObjectRef object, CFErrorRef *error) {
CFDictionaryRef backup_item = NULL;
bool ok = ((backup_item = SOSObjectCopyBackup(dataSource, object, kbhandle, error))
&& SOSBackupEventWriteAdd(journalFile, backup_item, error));
CFReleaseSafe(backup_item);
return ok;
}
bool SOSPeerDataSourceWillChange(SOSPeerRef peer, SOSDataSourceRef dataSource, SOSDataSourceTransactionSource source, CFArrayRef changes, CFErrorRef *error) {
__block bool ok = true;
ok &= SOSPeerWritePendingReset(peer, error) && SOSPeerAppendToJournal(peer, error, ^(FILE *journalFile, keybag_handle_t kbhandle) {
struct SOSDigestVector dvdel = SOSDigestVectorInit;
struct SOSDigestVector dvadd = SOSDigestVectorInit;
SOSChangeRef change;
CFArrayForEachC(changes, change) {
bool isDelete = false;
CFErrorRef localError = NULL;
CFDataRef digest = NULL;
SOSObjectRef object = NULL;
bool ok = digest = SOSChangeCopyDigest(dataSource, change, &isDelete, &object, &localError);
if (isDelete) {
ok &= SOSBackupEventWriteDelete(journalFile, digest, &localError);
SOSDigestVectorAppend(&dvdel, CFDataGetBytePtr(digest));
} else {
ok &= SOSPeerWriteAddEvent(journalFile, kbhandle, dataSource, object, &localError);
SOSDigestVectorAppend(&dvadd, CFDataGetBytePtr(digest));
}
if (!ok) {
secerror("bad change %@: %@", change, localError);
}
CFReleaseSafe(digest);
CFReleaseSafe(localError);
};
if (ok) {
struct SOSDigestVector dvresult = SOSDigestVectorInit;
SOSDigestVectorSort(&dvdel);
SOSDigestVectorSort(&dvadd);
if ((ok = SOSDigestVectorPatchSorted(SOSManifestGetDigestVector(SOSPeerGetProposedManifest(peer)), &dvdel,
&dvadd, &dvresult, error))) {
SOSManifestRef proposed;
ok = proposed = SOSManifestCreateWithDigestVector(&dvresult, error);
SOSPeerSetProposedManifest(peer, proposed);
CFReleaseSafe(proposed);
}
SOSDigestVectorFree(&dvresult);
}
SOSDigestVectorFree(&dvdel);
SOSDigestVectorFree(&dvadd);
if (SOSPeerSendObjects(peer))
SOSBackupEventWriteCompleteMarker(journalFile, 799, error);
});
if (!ok) {
SOSCCSyncWithAllPeers();
}
return ok;
}