#include "iCloudTrace.h"
#include <SecureObjectSync/SOSCloudCircle.h>
#include <Security/SecItem.h>
#include <utilities/iCloudKeychainTrace.h>
#include <securityd/SecItemServer.h>
#include <sys/stat.h>
#include <string.h>
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
#include <pwd.h>
#endif
#include <utilities/SecCFWrappers.h>
extern bool SOSCCThisDeviceDefinitelyNotActiveInCircle(void);
#define MAX_PATH 1024
static int64_t GetNumberOfItemsBeingSynced()
{
__block int64_t result = 0;
SOSDataSourceFactoryRef dsFactRef = SecItemDataSourceFactoryGetDefault();
CFArrayRef ds_names = dsFactRef->copy_names(dsFactRef);
if (ds_names) {
CFArrayForEach(ds_names, ^(const void *value) {
if (isString(value)) {
SOSManifestRef manifestRef = NULL;
SOSDataSourceRef ds = dsFactRef->create_datasource(dsFactRef, value, NULL);
if (ds) {
manifestRef = SOSDataSourceCopyManifest(ds, NULL);
if (manifestRef)
result += SOSManifestGetCount(manifestRef);
}
CFReleaseSafe(manifestRef);
SOSDataSourceRelease(ds, NULL);
}
});
}
CFReleaseSafe(ds_names);
return result;
}
static int64_t GetNumberOfPeers()
{
int64_t result = 0;
CFErrorRef error = NULL;
CFArrayRef peers = NULL;
peers = SOSCCCopyPeerPeerInfo(&error);
if (NULL != error)
{
CFRelease(error);
if (NULL != peers)
{
CFRelease(peers);
}
return result;
}
if (NULL != peers)
{
result = (int64_t)CFArrayGetCount(peers);
CFRelease(peers);
}
return result;
}
static const char* kLoggingPlistPartialPath = "/Library/Preferences/com.apple.security.logging.plist";
static Boolean PathExists(const char* path, size_t* pFileSize)
{
Boolean result = false;
struct stat sb;
if (NULL != pFileSize)
{
*pFileSize = 0;
}
int stat_result = stat(path, &sb);
result = (stat_result == 0);
if (result)
{
if (S_ISDIR(sb.st_mode))
{
;
}
else
{
if (NULL != pFileSize)
{
*pFileSize = (size_t)sb.st_size;
}
}
}
return result;
}
static CFDataRef CopyFileContents(const char *path)
{
CFMutableDataRef data = NULL;
int fd = open(path, O_RDONLY, 0666);
if (fd == -1)
{
goto badFile;
}
off_t fsize = lseek(fd, 0, SEEK_END);
if (fsize == (off_t)-1)
{
goto badFile;
}
if (fsize > (off_t)INT32_MAX)
{
goto badFile;
}
data = CFDataCreateMutable(kCFAllocatorDefault, (CFIndex)fsize);
if (NULL == data)
{
goto badFile;
}
CFDataSetLength(data, (CFIndex)fsize);
void *buf = CFDataGetMutableBytePtr(data);
if (NULL == buf)
{
goto badFile;
}
off_t total_read = 0;
while (total_read < fsize)
{
ssize_t bytes_read;
bytes_read = pread(fd, buf, (size_t)(fsize - total_read), total_read);
if (bytes_read == -1)
{
goto badFile;
}
if (bytes_read == 0)
{
goto badFile;
}
total_read += bytes_read;
}
close(fd);
return data;
badFile:
if (fd != -1)
{
close(fd);
}
if (data)
{
CFRelease(data);
}
return NULL;
}
static const CFStringRef kLoggingPlistKey = CFSTR("LoggingTime");
static const char* CopyPlistPath()
{
const char* result = NULL;
CFURLRef url = NULL;
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
CFStringRef path_string = NULL;
const char *homeDir = getenv("HOME");
if (!homeDir)
{
struct passwd* pwd = getpwuid(getuid());
if (pwd)
homeDir = pwd->pw_dir;
}
path_string = CFStringCreateWithCStringNoCopy(kCFAllocatorDefault, homeDir, kCFStringEncodingUTF8, kCFAllocatorNull);
if (NULL == path_string)
{
return result;
}
url = CFURLCreateWithFileSystemPath(kCFAllocatorDefault, path_string, kCFURLPOSIXPathStyle, true);
CFRelease(path_string);
#endif
#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR)
url = CFCopyHomeDirectoryURL();
#endif
if (url)
{
UInt8 file_path_buffer[MAX_PATH+1];
memset(file_path_buffer, 0, MAX_PATH);
size_t length = 0;
if(!CFURLGetFileSystemRepresentation(url, false, file_path_buffer, MAX_PATH))
{
CFRelease(url);
return result; }
CFRelease(url);
if (strnlen((const char *)file_path_buffer, MAX_PATH) + strlen(kLoggingPlistPartialPath) >= MAX_PATH)
{
return result; }
strncat((char *)file_path_buffer, kLoggingPlistPartialPath, MAX_PATH);
length = strlen((char *)file_path_buffer) + 1;
result = malloc(length);
if (NULL == result)
{
return result;
}
memset((void *)result, 0, length);
strncpy((char *)result, (char *)file_path_buffer, length - 1);
return result;
}
return NULL;
}
static bool ShouldLog()
{
bool result = false;
size_t fileSize = 0;
CFDataRef file_data = NULL;
CFPropertyListRef logging_time_data = NULL;
CFDataRef time_data = NULL;
const char* plist_path = NULL;
plist_path = CopyPlistPath();
if (NULL == plist_path)
{
return true; }
if (!PathExists((const char *)plist_path, &fileSize))
{
free((void *)plist_path);
return true; }
file_data = CopyFileContents((const char *)plist_path);
free((void *)plist_path);
if (NULL == file_data)
{
return true; }
logging_time_data = CFPropertyListCreateWithData(kCFAllocatorDefault, file_data, kCFPropertyListMutableContainersAndLeaves, NULL, NULL);
CFRelease(file_data);
if (NULL == logging_time_data)
{
return true; }
require_action(CFDictionaryGetTypeID() == CFGetTypeID(logging_time_data), xit, result = true);
time_data = (CFDataRef)CFDictionaryGetValue((CFDictionaryRef)logging_time_data, kLoggingPlistKey);
require_action(time_data, xit, result = true);
CFAbsoluteTime startTime = 0;
memcpy(&startTime, CFDataGetBytePtr(time_data), sizeof(startTime));
CFAbsoluteTime endTime = CFAbsoluteTimeGetCurrent();
int days = 0;
CFCalendarRef gregorian = CFCalendarCopyCurrent();
CFCalendarGetComponentDifference(gregorian, startTime, endTime, 0, "d", &days);
CFRelease(gregorian);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE))
if (days > 6)
{
result = true;
}
#endif
#if (TARGET_OS_IPHONE && !TARGET_IPHONE_SIMULATOR)
if (days > 0)
{
result = true;
}
#endif
xit:
CFReleaseSafe(logging_time_data);
return result;
}
static void WriteOutLoggingTime(void)
{
int fd = -1;
CFAbsoluteTime now;
CFDataRef now_data;
CFDictionaryRef plistData = NULL;
CFErrorRef error = NULL;
CFDataRef output_data = NULL;
const char* filepath = CopyPlistPath();
if (NULL == filepath)
{
return;
}
fd = open(filepath, (O_WRONLY | O_CREAT | O_TRUNC), 0666);
free((void *)filepath);
if (fd <= 0)
{
return;
}
now = CFAbsoluteTimeGetCurrent();
now_data = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)&now, sizeof(now));
if (NULL == now_data)
{
close(fd);
return;
}
plistData = CFDictionaryCreate(kCFAllocatorDefault, (const void **)&kLoggingPlistKey, (const void **)&now_data, 1,
&kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
if (NULL == plistData)
{
close(fd);
CFRelease(now_data);
return;
}
CFRelease(now_data);
output_data = CFPropertyListCreateData(kCFAllocatorDefault, plistData, kCFPropertyListBinaryFormat_v1_0, 0, &error);
CFRelease(plistData);
if (NULL != error || NULL == output_data)
{
close(fd);
if (NULL != error)
{
CFRelease(error);
}
if (NULL != output_data)
{
CFRelease(output_data);
}
return;
}
write(fd, CFDataGetBytePtr(output_data), CFDataGetLength(output_data));
close(fd);
CFRelease(output_data);
}
static int64_t Bucket(int64_t value)
{
if (value < 10)
{
return value;
}
if (value < 100)
{
return (value / 10) * 10;
}
if (value < 1000)
{
return (value / 100) * 100;
}
if (value < 10000)
{
return (value / 1000) * 1000;
}
if (value < 100000)
{
return (value / 10000) * 10000;
}
if (value < 1000000)
{
return (value / 100000) * 10000;
}
return value;
}
static void DoLogging()
{
int64_t value = 1;
void* token = BeginCloudKeychainLoggingTransaction();
value = GetNumberOfPeers();
value = Bucket(value);
AddKeyValuePairToKeychainLoggingTransaction(token, kNumberOfiCloudKeychainPeers, value);
value = GetNumberOfItemsBeingSynced();
value = Bucket(value);
AddKeyValuePairToKeychainLoggingTransaction(token, kNumberOfiCloudKeychainItemsBeingSynced, value);
CloseCloudKeychainLoggingTransaction(token);
WriteOutLoggingTime();
}
void InitializeCloudKeychainTracing()
{
if (SOSCCThisDeviceDefinitelyNotActiveInCircle()) {
return;
}
if (ShouldLog())
{
DoLogging();
}
}