Compare commits

...

10 Commits

Author SHA1 Message Date
Tom Burgin
9ab85768bd Update Podfile.lock to use MOLFCMClient v1.1 (#136) 2017-01-03 11:10:15 -05:00
Tom Burgin
16458d96e7 Notification verbage update (#135) 2016-12-14 14:41:20 -05:00
Tom Burgin
b307dd17af Use machine ids as the targeted sync indicator (#134)
* Use machine ids as the targeted sync indicator

* remove unused constant
2016-12-12 16:53:24 -05:00
Tom Burgin
313552352c Display the binary name when a local rule is synced from a push notification (#133) 2016-12-07 17:40:11 -05:00
Tom Burgin
543ac7c649 push notifications with FCM (#132)
* push notifications with FCM

* Don't display rule count in notifications. Get FCM broadcast topic from sync server.
2016-12-06 16:04:34 -05:00
Tom Burgin
dacff76694 run santactl as a sync daemon (#129)
* run santactl as a sync daemon
2016-11-16 14:41:12 -05:00
Russell Hancox
c134169ea1 santad: Drop AUTOINCREMENT on event table (#130) 2016-11-01 11:14:51 -04:00
Russell Hancox
e252945047 santactl/fileinfo: Send resolved path to santad for processing (#128) 2016-10-26 16:04:27 -04:00
Russell Hancox
f8cfcaab20 Package/Conf: Fix typo in uninstall.sh (#126) 2016-10-25 15:05:36 -04:00
Tom Burgin
528237a239 santactl status: check non-boxed vars when building json output (#125) 2016-10-24 12:14:56 -04:00
36 changed files with 884 additions and 302 deletions

View File

@@ -82,3 +82,4 @@ myclean:
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f install.sh
@rm -f uninstall.sh

View File

@@ -19,8 +19,8 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /usr/local/santactl # just a symlink
/bin/rm -f /usr/local/bin/santactl # just a symlink
#uncomment to remove the config file and all databases, log files
#/bin/rm -rf /var/db/santa
#/bin/rm -f /var/log/santa*
exit 0
exit 0

View File

@@ -18,6 +18,7 @@ target :santactl do
pod 'MOLAuthenticatingURLSession'
pod 'MOLCertificate'
pod 'MOLCodesignChecker'
pod 'MOLFCMClient'
end
target :LogicTests do

View File

@@ -7,6 +7,8 @@ PODS:
- MOLCertificate (1.5)
- MOLCodesignChecker (1.5):
- MOLCertificate (~> 1.3)
- MOLFCMClient (1.1):
- MOLAuthenticatingURLSession (~> 2.1)
- OCMock (3.3.1)
DEPENDENCIES:
@@ -14,6 +16,7 @@ DEPENDENCIES:
- MOLAuthenticatingURLSession
- MOLCertificate
- MOLCodesignChecker
- MOLFCMClient
- OCMock
SPEC CHECKSUMS:
@@ -21,8 +24,9 @@ SPEC CHECKSUMS:
MOLAuthenticatingURLSession: 2f0fd35f641bc857ee1b026021dbd759955adaa3
MOLCertificate: c39cae866d24d36fbc78032affff83d401b5384a
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
MOLFCMClient: f1684219facbffdb060ff4ab18b1825bcd4c75bc
OCMock: f3f61e6eaa16038c30caa5798c5e49d3307b6f22
PODFILE CHECKSUM: bc456d69693ca262c781dbbde40529a9474b84b5
PODFILE CHECKSUM: b20628b5933f54525daf0dcc5534512b1cb134c8
COCOAPODS: 1.0.1

View File

@@ -179,9 +179,15 @@
C714F8B11D8044D400700EDF /* SNTCommandFileInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD5FBE1909D64A006B445C /* SNTCommandFileInfo.m */; };
C714F8B21D8044FE00700EDF /* SNTCommandController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D35BDAB18FD7CFD00921A21 /* SNTCommandController.m */; };
C72E8D941D7F399900C86DD3 /* SNTCommandFileInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */; };
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */; };
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */ = {isa = PBXBuildFile; fileRef = C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */; };
EFD8E30D32F6128B9E833D64 /* libPods-LogicTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 873978BCE4B0DBD2A89C99D1 /* libPods-LogicTests.a */; };
/* End PBXBuildFile section */
@@ -418,8 +424,14 @@
BE53E1EAE84D54E7FCB22FD5 /* libPods-santactl.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-santactl.a"; sourceTree = BUILT_PRODUCTS_DIR; };
C72E8D931D7F399900C86DD3 /* SNTCommandFileInfoTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandFileInfoTest.m; sourceTree = "<group>"; };
C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandCheckCache.m; sourceTree = "<group>"; };
C776A1051DEE160500A56616 /* SNTCommandSyncManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncManager.h; sourceTree = "<group>"; };
C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncManager.m; sourceTree = "<group>"; };
C795ED8E1D80A5BE007CFF42 /* SNTPolicyProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTPolicyProcessor.h; sourceTree = "<group>"; };
C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTPolicyProcessor.m; sourceTree = "<group>"; };
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTXPCSyncdInterface.h; sourceTree = "<group>"; };
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTXPCSyncdInterface.m; sourceTree = "<group>"; };
C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTSyncdQueue.h; sourceTree = "<group>"; };
C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTSyncdQueue.m; sourceTree = "<group>"; };
D227889DF327E7D3532FE00B /* Pods-Santa.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Santa.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Santa/Pods-Santa.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
@@ -570,6 +582,8 @@
0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */,
0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */,
0DC5D870192160180078A5C0 /* SNTCommandSyncLogUpload.m */,
C776A1051DEE160500A56616 /* SNTCommandSyncManager.h */,
C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */,
0D0A1EC4191AB9B000B8450F /* SNTCommandSyncPostflight.h */,
0D0A1EC5191AB9B000B8450F /* SNTCommandSyncPostflight.m */,
0DCD605A19117A90006B445C /* SNTCommandSyncPreflight.h */,
@@ -725,6 +739,8 @@
0DCD605419115D17006B445C /* SNTXPCControlInterface.m */,
0DC8C9E3180CC3BC00FCFB29 /* SNTXPCNotifierInterface.h */,
0DCD604E19115A06006B445C /* SNTXPCNotifierInterface.m */,
C7FB56F41DBFB480004E14EF /* SNTXPCSyncdInterface.h */,
C7FB56F51DBFB480004E14EF /* SNTXPCSyncdInterface.m */,
);
path = common;
sourceTree = "<group>";
@@ -752,6 +768,8 @@
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */,
0DE5B5491C926E3300C00603 /* SNTNotificationQueue.h */,
0DE5B54A1C926E3300C00603 /* SNTNotificationQueue.m */,
C7FB56FE1DBFC213004E14EF /* SNTSyncdQueue.h */,
C7FB56FF1DBFC213004E14EF /* SNTSyncdQueue.m */,
0D3AF83118F87CEF0087BCEE /* Resources */,
);
path = santad;
@@ -1295,6 +1313,7 @@
0D88680C1AC48A1400B86659 /* SNTSystemInfo.m in Sources */,
0D536EDC1B94E9230039A26D /* SNTEventLog.m in Sources */,
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */,
C73A4B9B1DC10758007B6789 /* SNTXPCSyncdInterface.m in Sources */,
0DB77FDB1CD14093004DF060 /* SNTBlockMessage.m in Sources */,
0D63DD5E1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
0D202D191CDD2EE500A88F16 /* SNTCommandSyncTest.m in Sources */,
@@ -1331,6 +1350,7 @@
0DCD605919115E5A006B445C /* SNTXPCNotifierInterface.m in Sources */,
0DE50F691912B0CD007B2B0C /* SNTRule.m in Sources */,
0D202D1B1CDD465400A88F16 /* SNTCommandSyncState.m in Sources */,
C73A4B9A1DC10753007B6789 /* SNTSyncdQueue.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1345,11 +1365,13 @@
0DCD605619115D17006B445C /* SNTXPCControlInterface.m in Sources */,
0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */,
0D9184B81CD2F32D0004E859 /* SNTCommandSyncStage.m in Sources */,
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */,
0D35BDC418FDA5D100921A21 /* SNTXPCConnection.m in Sources */,
0DCD605C19117A90006B445C /* SNTCommandSyncPreflight.m in Sources */,
0D41640519197AD7006A356A /* SNTCommandSyncEventUpload.m in Sources */,
0D42D2B919D2042900955F08 /* SNTConfigurator.m in Sources */,
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */,
C7FB56F71DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
0D10BE871A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
0DE4C8A618FF3B1700466D04 /* SNTCommandFlushCache.m in Sources */,
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */,
@@ -1412,10 +1434,12 @@
0D10BE861A0AABD600C0C944 /* SNTDropRootPrivs.m in Sources */,
0D63DD5C1906FCB400D346C4 /* SNTDatabaseController.m in Sources */,
0DCD604B19105433006B445C /* SNTStoredEvent.m in Sources */,
C7FB57001DBFC213004E14EF /* SNTSyncdQueue.m in Sources */,
0DB8ACC1185662DC00FEF9C7 /* SNTApplication.m in Sources */,
0D9A7F421759330500035EB5 /* main.m in Sources */,
0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */,
0DE71A751B95F7F900518526 /* SNTCachedDecision.m in Sources */,
C7FB56F61DBFB480004E14EF /* SNTXPCSyncdInterface.m in Sources */,
0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */,
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */,

View File

@@ -135,4 +135,12 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
});
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message {
NSUserNotification *un = [[NSUserNotification alloc] init];
un.title = @"Santa";
un.hasActionButton = NO;
un.informativeText = message ?: @"Requested application can now be run";
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
}
@end

View File

@@ -20,7 +20,7 @@
@interface SNTStoredEvent : NSObject<NSSecureCoding>
///
/// An index for this event, empty unless the event came from the database.
/// An index for this event, randomly generated during initialization.
///
@property NSNumber *idx;

View File

@@ -57,6 +57,14 @@
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
}
- (instancetype)init {
self = [super init];
if (self) {
_idx = @(arc4random());
}
return self;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {

View File

@@ -41,9 +41,7 @@
- (void)databaseRuleAddRules:(NSArray *)rules
cleanSlate:(BOOL)cleanSlate
reply:(void (^)(NSError *error))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
@@ -74,7 +72,6 @@
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
@@ -85,6 +82,14 @@
///
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
///
/// Syncd Ops
///
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener;
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
- (void)pushNotifications:(void (^)(BOOL))reply;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply;
@end
@interface SNTXPCControlInterface : NSObject

View File

@@ -20,6 +20,7 @@
@protocol SNTNotifierXPC
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
- (void)postClientModeNotification:(SNTClientMode)clientmode;
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message;
@end
@interface SNTXPCNotifierInterface : NSObject

View File

@@ -0,0 +1,34 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommonEnums.h"
@class SNTStoredEvent;
/// Protocol implemented by santactl and utilized by santad
@protocol SNTSyncdXPC
- (void)postEventToSyncServer:(SNTStoredEvent *)event;
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds;
- (void)isFCMListening:(void (^)(BOOL))reply;
@end
@interface SNTXPCSyncdInterface : NSObject
///
/// Returns an initialized NSXPCInterface for the SNTSyncdXPC protocol.
/// Ensures any methods that accept custom classes as arguments are set-up before returning
///
+ (NSXPCInterface *)syncdInterface;
@end

View File

@@ -0,0 +1,23 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTXPCSyncdInterface.h"
@implementation SNTXPCSyncdInterface
+ (NSXPCInterface *)syncdInterface {
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTSyncdXPC)];
}
@end

View File

@@ -275,9 +275,9 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
if (!fi.csc) {
NSError *error;
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.filePath error:&error];
fi.csc = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path(fi) error:&error];
}
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.filePath
[[fi.daemonConn remoteObjectProxy] decisionForFilePath:fi.path(fi)
fileSHA256:fi.propertyMap[kSHA256](fi)
signingCertificate:fi.csc.leafCertificate
reply:^(SNTEventState state) {

View File

@@ -105,6 +105,12 @@ REGISTER_COMMAND_NAME(@"status")
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] syncLastSuccess];
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
__block BOOL pushNotifications;
dispatch_group_enter(group);
[[daemonConn remoteObjectProxy] pushNotifications:^(BOOL response) {
pushNotifications = response;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for stats collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
@@ -114,7 +120,7 @@ REGISTER_COMMAND_NAME(@"status")
if ([arguments containsObject:@"--json"]) {
NSDictionary *stats = @{
@"daemon" : @{
@"mode" : clientMode,
@"mode" : clientMode ?: @"null",
@"file_logging" : @(fileLogging),
@"watchdog_cpu_events" : @(cpuEvents),
@"watchdog_ram_events" : @(ramEvents),
@@ -130,9 +136,10 @@ REGISTER_COMMAND_NAME(@"status")
@"events_pending_upload" : @(eventCount),
},
@"sync" : @{
@"server" : syncURLStr,
@"server" : syncURLStr ?: @"null",
@"clean_required" : @(syncCleanReqd),
@"last_successful" : lastSyncSuccessStr
@"last_successful" : lastSyncSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"true" : @"false"
},
};
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
@@ -158,6 +165,7 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-22s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-22s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
printf(" %-22s | %s\n", "Last Successful Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-22s | %s\n", "Push Notifications", (pushNotifications ? "Yes" : "No"));
}
}

View File

@@ -14,14 +14,7 @@
#import "SNTCommandController.h"
#import <MOLAuthenticatingURLSession.h>
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTCommandSyncManager.h"
#import "SNTConfigurator.h"
#import "SNTDropRootPrivs.h"
#import "SNTLogging.h"
@@ -29,19 +22,22 @@
#import "SNTXPCControlInterface.h"
@interface SNTCommandSync : NSObject<SNTCommand>
@property SNTCommandSyncState *syncState;
@property SNTXPCConnection *listener;
@property SNTCommandSyncManager *syncManager;
@end
@implementation SNTCommandSync
REGISTER_COMMAND_NAME(@"sync")
#pragma mark SNTCommand protocol methods
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
return NO;
}
+ (NSString *)shortHelpText {
@@ -63,36 +59,11 @@ REGISTER_COMMAND_NAME(@"sync")
exit(1);
}
SNTConfigurator *config = [SNTConfigurator configurator];
SNTCommandSync *s = [[self alloc] init];
// Gather some data needed during some sync stages
s.syncState = [[SNTCommandSyncState alloc] init];
s.syncState.syncBaseURL = config.syncBaseURL;
if (s.syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
s.syncState.machineID = config.machineID;
if (s.syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
exit(1);
}
s.syncState.machineOwner = config.machineOwner;
if (s.syncState.machineOwner.length == 0) {
s.syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
[[daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
s.syncState.xsrfToken = token;
}];
[daemonConn resume];
BOOL daemon = [arguments containsObject:@"--daemon"];
s.syncManager = [[SNTCommandSyncManager alloc] initWithDaemonConnection:daemonConn
isDaemon:daemon];
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
@@ -100,138 +71,40 @@ REGISTER_COMMAND_NAME(@"sync")
diskCapacity:0
diskPath:nil]];
if (!s.syncManager.daemon) return [s.syncManager fullSync];
[s syncdWithDaemonConnection:daemonConn];
}
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
#pragma mark daemon methods
- (void)syncdWithDaemonConnection:(SNTXPCConnection *)daemonConn {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
// Create listener for return connection from daemon.
NSXPCListener *listener = [NSXPCListener anonymousListener];
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
self.listener.exportedInterface = [SNTXPCSyncdInterface syncdInterface];
self.listener.exportedObject = self.syncManager;
self.listener.acceptedHandler = ^{
LOGD(@"santad <--> santactl connections established");
dispatch_semaphore_signal(sema);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
s.syncState.session = [authURLSession session];
s.syncState.daemonConn = daemonConn;
if ([arguments containsObject:@"singleevent"]) {
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
if (idx >= arguments.count) {
LOGI(@"singleevent takes an argument");
exit(1);
}
NSString *obj = arguments[idx];
if (obj.length != 64) {
LOGI(@"singleevent passed without SHA-256 as next argument");
exit(1);
}
return [s eventUploadSingleEvent:obj];
} else {
return [s preflight];
}
}
- (void)preflight {
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
if (self.syncState.uploadLogURL) {
return [self logUpload];
} else {
return [self eventUpload];
}
} else {
LOGE(@"Preflight failed, aborting run");
exit(1);
}
}
- (void)logUpload {
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUpload];
}
- (void)eventUpload {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownload];
} else {
LOGE(@"Event upload failed, aborting run");
exit(1);
}
}
- (void)eventUploadSingleEvent:(NSString *)sha256 {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncSingleEventWithSHA256:sha256]) {
LOGD(@"Event upload complete");
self.listener.invalidationHandler = ^{
// If santad is unloaded kill santactl
LOGD(@"exiting");
exit(0);
} else {
LOGE(@"Event upload failed");
exit(1);
}
}
};
[self.listener resume];
- (void)ruleDownload {
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
if (self.syncState.bundleBinaryRequests.count) {
return [self eventUploadBundleBinaries];
}
return [self postflight];
} else {
LOGE(@"Rule download failed, aborting run");
exit(1);
}
}
// Tell daemon to connect back to the above listener.
[[daemonConn remoteObjectProxy] setSyncdListener:listener.endpoint];
- (void)eventUploadBundleBinaries {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
if ([p syncBundleEvents]) {
LOGD(@"Event upload for bundle binaries complete");
} else {
LOGW(@"Event upload for bundle binary search failed");
// Now wait for the connection to come in.
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
[self performSelectorInBackground:@selector(syncdWithDaemonConnection:) withObject:daemonConn];
}
return [self postflight];
}
- (void)postflight {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:self.syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
exit(0);
} else {
LOGE(@"Postflight failed");
exit(1);
}
[self.syncManager fullSyncSecondsFromNow:15];
}
@end

View File

@@ -31,6 +31,7 @@ extern NSString *const kWhitelistRegex;
extern NSString *const kBlacklistRegex;
extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kFCMToken;
extern NSString *const kEvents;
extern NSString *const kFileSHA256;
@@ -88,3 +89,8 @@ extern NSString *const kRuleCustomMsg;
extern NSString *const kCursor;
extern NSString *const kBackoffInterval;
extern NSString *const kFullSync;
extern NSString *const kRuleSync;
extern NSString *const kConfigSync;
extern NSString *const kLogSync;

View File

@@ -33,6 +33,7 @@ NSString *const kWhitelistRegex = @"whitelist_regex";
NSString *const kBlacklistRegex = @"blacklist_regex";
NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kFCMToken = @"fcm_token";
NSString *const kEvents = @"events";
NSString *const kFileSHA256 = @"file_sha256";
@@ -90,3 +91,8 @@ NSString *const kRuleCustomMsg = @"custom_msg";
NSString *const kCursor = @"cursor";
NSString *const kBackoffInterval = @"backoff";
NSString *const kFullSync = @"full_sync";
NSString *const kRuleSync = @"rule_sync";
NSString *const kConfigSync = @"config_sync";
NSString *const kLogSync = @"log_sync";

View File

@@ -16,7 +16,7 @@
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
- (BOOL)uploadEvents:(NSArray *)events;
- (BOOL)syncBundleEvents;

View File

@@ -44,17 +44,6 @@
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 {
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
[[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) {
if (e) {
[self uploadEvents:@[ e ]];
}
dispatch_semaphore_signal(sema);
}];
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
}
- (BOOL)syncBundleEvents {
NSMutableArray *newEvents = [NSMutableArray array];
for (NSString *bundlePath in [NSSet setWithArray:self.syncState.bundleBinaryRequests]) {

View File

@@ -0,0 +1,53 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTXPCSyncdInterface.h"
@class SNTXPCConnection;
///
/// Handles push notifications and periodic syncing with a sync server.
///
@interface SNTCommandSyncManager : NSObject<SNTSyncdXPC>
@property(readonly, nonatomic) BOOL daemon;
///
/// Use the designated initializer initWithDaemonConnection:isDaemon:
///
- (instancetype)init NS_UNAVAILABLE;
///
/// Designated initializer.
///
/// @param daemonConn A connection to santad.
/// @param daemon Set to YES if periodic syncing should occur.
/// Set to NO if a single sync should be performed. NO is default.
///
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn
isDaemon:(BOOL)daemon NS_DESIGNATED_INITIALIZER;
///
/// Perform a full sync immediately. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSync;
///
/// Perform a full sync seconds from now. Non-blocking.
/// If a full sync is already running new requests will be dropped.
///
- (void)fullSyncSecondsFromNow:(uint64_t)seconds;
@end

View File

@@ -0,0 +1,416 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTCommandSyncManager.h"
#import <MOLAuthenticatingURLSession.h>
#import <MOLFCMClient/MOLFCMClient.h>
#import "SNTConfigurator.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncLogUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncState.h"
#import "SNTLogging.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCSyncdInterface.h"
// Syncing time constants
const uint64_t kFullSyncInterval = 600;
const uint64_t kFullSyncFCMInterval = 14400;
const uint64_t kGlobalRuleSyncLeeway = 600;
@interface SNTCommandSyncManager ()
@property(nonatomic) dispatch_source_t fullSyncTimer;
@property(nonatomic) dispatch_source_t ruleSyncTimer;
@property(nonatomic) NSCache *dispatchLock;
@property(nonatomic) NSCache *ruleSyncCache;
@property MOLFCMClient *FCMClient;
@property(nonatomic) SNTXPCConnection *daemonConn;
@property BOOL targetedRuleSync;
@end
@implementation SNTCommandSyncManager
#pragma mark init
- (instancetype)initWithDaemonConnection:(SNTXPCConnection *)daemonConn isDaemon:(BOOL)daemon {
self = [super init];
if (self) {
_daemonConn = daemonConn;
_daemon = daemon;
_fullSyncTimer = [self createSyncTimerWithBlock:^{
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncFCMInterval];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kFullSync];
[self preflight];
[self unlockAction:kFullSync];
}];
_ruleSyncTimer = [self createSyncTimerWithBlock:^{
dispatch_source_set_timer(self.ruleSyncTimer,
DISPATCH_TIME_FOREVER, DISPATCH_TIME_FOREVER, 0);
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self lockAction:kRuleSync];
SNTCommandSyncState *syncState = [self createSyncState];
syncState.targetedRuleSync = self.targetedRuleSync;
syncState.ruleSyncCache = self.ruleSyncCache;
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
} else {
LOGE(@"Rule download failed");
}
self.targetedRuleSync = NO;
[self unlockAction:kRuleSync];
}];
_dispatchLock = [[NSCache alloc] init];
_ruleSyncCache = [[NSCache alloc] init];
}
return self;
}
#pragma mark SNTSyncdXPC protocol methods
- (void)postEventToSyncServer:(SNTStoredEvent *)event {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc]
initWithState:[self createSyncState]];
if (event && [p uploadEvents:@[event]]) {
LOGD(@"Event upload complete");
} else {
LOGE(@"Event upload failed");
}
}
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)isFCMListening:(void (^)(BOOL))reply {
reply((self.FCMClient.FCMToken != nil));
}
#pragma mark push notification methods
- (void)listenForPushNotificationsWithSyncState:(SNTCommandSyncState *)syncState {
if ([self.FCMClient.FCMToken isEqualToString:syncState.FCMToken]) {
LOGD(@"Continue with the current FCMToken");
return;
}
LOGD(@"Start listening for push notifications");
WEAKIFY(self);
[self.FCMClient disconnect];
NSString *machineID = syncState.machineID;
self.FCMClient = [[MOLFCMClient alloc] initWithFCMToken:syncState.FCMToken
sessionConfiguration:syncState.session.configuration.copy
messageHandler:^(NSDictionary *message) {
if (!message || [message isEqual:@{}]) return;
STRONGIFY(self);
LOGD(@"%@", message);
[self.FCMClient acknowledgeMessage:message];
[self processFCMMessage:message withMachineID:machineID];
}];
self.FCMClient.connectionErrorHandler = ^(NSError *error) {
STRONGIFY(self);
LOGE(@"FCM connection error: %@", error);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
};
self.FCMClient.loggingBlock = ^(NSString *log) {
LOGD(@"%@", log);
};
[self.FCMClient connect];
}
- (void)processFCMMessage:(NSDictionary *)FCMmessage withMachineID:(NSString *)machineID {
NSData *entryData;
// Sort through the entries in the FCM message.
for (NSDictionary *entry in FCMmessage[@"data"]) {
if ([entry[@"key"] isEqualToString:@"blob"]) {
entryData = [entry[@"value"] dataUsingEncoding:NSUTF8StringEncoding];
break;
}
}
if (!entryData) {
LOGD(@"Push notification message is not in the expected format...dropping message");
return;
}
NSError *error;
NSDictionary *actionMessage = [NSJSONSerialization JSONObjectWithData:entryData
options:NSJSONReadingAllowFragments
error:&error];
if (!actionMessage) {
LOGD(@"Unable to parse push notification message value: %@", error);
return;
}
// Store the file name and hash in a cache. When the rule is actually added, use the cache
// to build a user notification.
NSString *fileHash = actionMessage[@"file_hash"];
NSString *fileName = actionMessage[@"file_name"];
if (fileName && fileHash) {
[self.ruleSyncCache setObject:fileName forKey:fileHash];
}
NSString *action = actionMessage[@"action"];
if (action) {
LOGD(@"Push notification action: %@ received", action);
} else {
LOGD(@"Push notification message contains no action");
}
if ([action isEqualToString:kFullSync]) {
[self fullSync];
} else if ([action isEqualToString:kRuleSync]) {
NSString *targetMachineID = actionMessage[@"target_host_id"];
if (![targetMachineID isKindOfClass:[NSNull class]] &&
[targetMachineID.lowercaseString isEqualToString:machineID.lowercaseString]) {
self.targetedRuleSync = YES;
[self ruleSync];
} else {
uint32_t delaySeconds = arc4random_uniform(kGlobalRuleSyncLeeway);
LOGD(@"Staggering rule download, %u second delay for this machine", delaySeconds);
[self ruleSyncSecondsFromNow:delaySeconds];
}
} else if ([action isEqualToString:kConfigSync]) {
[self fullSync];
} else if ([action isEqualToString:kLogSync]) {
[self fullSync];
} else {
LOGD(@"Unrecognised action: %@", action);
}
}
#pragma mark sync timer control
- (void)fullSync {
[self fullSyncSecondsFromNow:0];
}
- (void)fullSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kFullSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kFullSync);
return;
}
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:seconds];
}
- (void)ruleSync {
[self ruleSyncSecondsFromNow:0];
}
- (void)ruleSyncSecondsFromNow:(uint64_t)seconds {
if (![self checkLockAction:kRuleSync]) {
LOGD(@"%@ in progress, dropping reschedule request", kRuleSync);
return;
}
[self rescheduleTimerQueue:self.ruleSyncTimer secondsFromNow:seconds];
}
- (void)rescheduleTimerQueue:(dispatch_source_t)timerQueue secondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.5) * NSEC_PER_SEC;
dispatch_source_set_timer(timerQueue, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark syncing chain
- (void)preflight {
SNTCommandSyncState *syncState = [self createSyncState];
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Preflight complete");
// Start listening for push notifications with a full sync every kFullSyncFCMInterval or
// revert to full syncing every kFullSyncInterval.
if (syncState.daemon && syncState.FCMToken) {
[self listenForPushNotificationsWithSyncState:syncState];
} else if (syncState.daemon) {
LOGD(@"FCMToken not provided. Sync every %llu min.", kFullSyncInterval / 60);
[self.FCMClient disconnect];
self.FCMClient = nil;
[self rescheduleTimerQueue:self.fullSyncTimer secondsFromNow:kFullSyncInterval];
}
if (syncState.uploadLogURL) {
return [self logUploadWithSyncState:syncState];
} else {
return [self eventUploadWithSyncState:syncState];
}
} else {
LOGE(@"Preflight failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)logUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Log upload complete");
} else {
LOGE(@"Log upload failed, continuing anyway");
}
return [self eventUploadWithSyncState:syncState];
}
- (void)eventUploadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Event upload complete");
return [self ruleDownloadWithSyncState:syncState];
} else {
LOGE(@"Event upload failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)ruleDownloadWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Rule download complete");
if (syncState.bundleBinaryRequests.count) {
return [self eventUploadBundleBinariesWithSyncState:syncState];
}
return [self postflightWithSyncState:syncState];
} else {
LOGE(@"Rule download failed, aborting run");
if (!syncState.daemon) exit(1);
}
}
- (void)eventUploadBundleBinariesWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:syncState];
if ([p syncBundleEvents]) {
LOGD(@"Event upload for bundle binaries complete");
} else {
LOGW(@"Event upload for bundle binary search failed");
}
return [self postflightWithSyncState:syncState];
}
- (void)postflightWithSyncState:(SNTCommandSyncState *)syncState {
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:syncState];
if ([p sync]) {
LOGD(@"Postflight complete");
LOGI(@"Sync completed successfully");
if (!syncState.daemon) exit(0);
} else {
LOGE(@"Postflight failed");
if (!syncState.daemon) exit(1);
}
}
#pragma mark internal helpers
- (dispatch_source_t)createSyncTimerWithBlock:(void (^)())block {
dispatch_source_t timerQueue = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(timerQueue, block);
dispatch_resume(timerQueue);
return timerQueue;
}
- (SNTCommandSyncState *)createSyncState {
// Gather some data needed during some sync stages
SNTCommandSyncState *syncState = [[SNTCommandSyncState alloc] init];
SNTConfigurator *config = [SNTConfigurator configurator];
syncState.syncBaseURL = config.syncBaseURL;
if (syncState.syncBaseURL.absoluteString.length == 0) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
if (!syncState.daemon) exit(1);
} else if (![syncState.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
syncState.machineID = config.machineID;
if (syncState.machineID.length == 0) {
LOGE(@"Missing Machine ID. Can't sync without it.");
if (!syncState.daemon) exit(1);
}
syncState.machineOwner = config.machineOwner;
if (syncState.machineOwner.length == 0) {
syncState.machineOwner = @"";
LOGW(@"Missing Machine Owner.");
}
[[self.daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
syncState.xsrfToken = token;
}];
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
authURLSession.userAgent = @"santactl-sync/";
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
if (santactlVersion) {
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
}
authURLSession.refusesRedirects = YES;
authURLSession.serverHostname = syncState.syncBaseURL.host;
authURLSession.loggingBlock = ^(NSString *line) {
LOGD(@"%@", line);
};
// Configure server auth
if ([config syncServerAuthRootsFile]) {
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
} else if ([config syncServerAuthRootsData]) {
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
}
// Configure client auth
if ([config syncClientAuthCertificateFile]) {
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
} else if ([config syncClientAuthCertificateCn]) {
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
} else if ([config syncClientAuthCertificateIssuer]) {
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
}
syncState.session = [authURLSession session];
syncState.daemonConn = self.daemonConn;
syncState.daemon = self.daemon;
return syncState;
}
- (void)lockAction:(NSString *)action {
[self.dispatchLock setObject:@YES forKey:action];
}
- (void)unlockAction:(NSString *)action {
[self.dispatchLock removeObjectForKey:action];
}
- (BOOL)checkLockAction:(NSString *)action {
return ([self.dispatchLock objectForKey:action] == nil);
}
@end

View File

@@ -74,10 +74,9 @@
if (!resp) return NO;
self.syncState.eventBatchSize = [resp[kBatchSize] intValue];
if (self.syncState.eventBatchSize == 0) {
self.syncState.eventBatchSize = 50;
}
self.syncState.FCMToken = resp[kFCMToken];
self.syncState.eventBatchSize = [resp[kBatchSize] intValue] ?: 50;
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];

View File

@@ -68,6 +68,19 @@
}
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
if (self.syncState.targetedRuleSync) {
NSString *fileName;
for (SNTRule *r in self.syncState.downloadedRules) {
fileName = [[self.syncState.ruleSyncCache objectForKey:r.shasum] copy];
[self.syncState.ruleSyncCache removeObjectForKey:r.shasum];
if (fileName) break;
}
NSString *message = fileName ? [NSString stringWithFormat:@"%@ can now be run", fileName] : nil;
[[self.daemonConn remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message reply:^{}];
}
return YES;
}

View File

@@ -26,6 +26,7 @@
@property(readwrite) NSURLSession *urlSession;
@property(readwrite) SNTCommandSyncState *syncState;
@property(readwrite) SNTXPCConnection *daemonConn;
@property BOOL xsrfFetched;
@end
@@ -170,9 +171,9 @@
}
- (BOOL)fetchXSRFToken {
__block BOOL success = NO;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{ // only fetch token once per session
BOOL success = NO;
if (!self.xsrfFetched) { // only fetch token once per session
self.xsrfFetched = YES;
NSString *stageName = [@"xsrf" stringByAppendingFormat:@"/%@", self.syncState.machineID];
NSURL *u = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:u];
@@ -188,7 +189,7 @@
} else {
LOGD(@"Failed to retrieve XSRF token");
}
});
};
return success;
}

View File

@@ -32,6 +32,9 @@
/// An XSRF token to send in the headers with each request.
@property NSString *xsrfToken;
/// A FCM token to subscribe to push notifications.
@property(copy) NSString *FCMToken;
/// Machine identifier and owner.
@property(copy) NSString *machineID;
@property(copy) NSString *machineOwner;
@@ -56,4 +59,13 @@
/// Rules downloaded from server.
@property NSMutableArray *downloadedRules;
/// Returns YES if the santactl session is running as a daemon, returns NO otherwise.
@property BOOL daemon;
/// Returns YES if the session is targeted for this machine, returns NO otherwise.
@property BOOL targetedRuleSync;
/// Reference to the sync manager's ruleSyncCache. Used to lookup binary names for notifications.
@property(weak) NSCache *ruleSyncCache;
@end

View File

@@ -44,15 +44,6 @@
///
- (NSUInteger)pendingEventsCount;
///
/// Retrieve an event from the database with a given SHA-256. If multiple events
/// exist for the same SHA-256, just the first is returned.
///
/// @param sha256 a SHA-256 of the binary to return an event for.
/// @return a single SNTStoredEvent.
///
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256;
///
/// Delete a single event from the database using its index.
///

View File

@@ -15,7 +15,6 @@
#import "SNTEventTable.h"
#import "MOLCertificate.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
@implementation SNTEventTable
@@ -66,13 +65,26 @@
newVersion = 2;
}
if (version < 3) {
// Clean-up: Disable AUTOINCREMENT on idx column
[db executeUpdate:@"CREATE TABLE 'events_tmp' ("
@"'idx' INTEGER PRIMARY KEY,"
@"'filesha256' TEXT NOT NULL,"
@"'eventdata' BLOB);"];
[db executeUpdate:@"INSERT INTO events_tmp SELECT * FROM events"];
[db executeUpdate:@"DROP TABLE events"];
[db executeUpdate:@"ALTER TABLE events_tmp RENAME TO events"];
newVersion = 3;
}
return newVersion;
}
#pragma mark Loading / Storing
- (BOOL)addStoredEvent:(SNTStoredEvent *)event {
if (!event.fileSHA256 ||
if (!event.idx ||
!event.fileSHA256 ||
!event.filePath ||
!event.occurrenceDate ||
!event.decision) return NO;
@@ -86,8 +98,9 @@
__block BOOL success = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
success = [db executeUpdate:@"INSERT INTO 'events' (filesha256, eventdata) VALUES (?, ?)",
event.fileSHA256, eventData];
success = [db executeUpdate:@"INSERT INTO 'events' (idx, filesha256, eventdata)"
@"VALUES (?, ?, ?)",
event.idx, event.fileSHA256, eventData];
}];
return success;
@@ -103,26 +116,6 @@
return eventsPending;
}
- (SNTStoredEvent *)pendingEventForSHA256:(NSString *)sha256 {
__block SNTStoredEvent *storedEvent;
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:@"SELECT * FROM events WHERE filesha256=? LIMIT 1;", sha256];
if ([rs next]) {
storedEvent = [self eventFromResultSet:rs];
if (!storedEvent) {
[db executeUpdate:@"DELETE FROM events WHERE idx=?", [rs objectForColumnName:@"idx"]];
}
}
[rs close];
}];
return storedEvent;
}
- (NSArray *)pendingEvents {
NSMutableArray *pendingEvents = [[NSMutableArray alloc] init];
@@ -152,7 +145,7 @@
@try {
event = [NSKeyedUnarchiver unarchiveObjectWithData:eventData];
event.idx = @([rs intForColumn:@"idx"]);
event.idx = event.idx ?: @((uint32_t)[rs intForColumn:@"idx"]);
} @catch (NSException *exception) {
}

View File

@@ -23,6 +23,7 @@
#import "SNTDaemonControlController.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventLog.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
@@ -30,6 +31,7 @@
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTRuleTable.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -68,11 +70,18 @@
}
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Establish XPC listener for santactl connections
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc = [[SNTDaemonControlController alloc] init];
dc.driverManager = _driverManager;
dc.notQueue = notQueue;
dc.syncdQueue = syncdQueue;
_controlConnection =
[[SNTXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceId]];
@@ -81,6 +90,7 @@
[_controlConnection resume];
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
__block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^(unsigned long data) {
if (data & DISPATCH_VNODE_ATTRIB) {
@@ -107,6 +117,14 @@
[self.driverManager flushCache];
}
}
// Start santactl if the syncBaseURL changed from nil --> somthing
NSURL *syncURL = [[SNTConfigurator configurator] syncBaseURL];
if (!origSyncURL && syncURL) {
origSyncURL = syncURL;
LOGI(@"SyncBaseURL added, starting santactl.");
[self startSyncd];
}
}
}];
@@ -117,13 +135,29 @@
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
if (!_execController) return nil;
}
return self;
}
- (void)startSyncd {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
}
- (void)start {
LOGI(@"Connected to driver, activating.");

View File

@@ -16,6 +16,7 @@
@class SNTDriverManager;
@class SNTNotificationQueue;
@class SNTSyncdQueue;
///
/// SNTDaemonControlController handles all of the RPCs from santactl
@@ -24,5 +25,6 @@
@property SNTDriverManager *driverManager;
@property SNTNotificationQueue *notQueue;
@property SNTSyncdQueue *syncdQueue;
@end

View File

@@ -18,15 +18,16 @@
#import "SNTConfigurator.h"
#import "SNTDatabaseController.h"
#import "SNTDriverManager.h"
#import "SNTDropRootPrivs.h"
#import "SNTEventTable.h"
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTPolicyProcessor.h"
#import "SNTRule.h"
#import "SNTRuleTable.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCConnection.h"
#import "SNTXPCNotifierInterface.h"
#import "SNTXPCSyncdInterface.h"
// Globals used by the santad watchdog thread
uint64_t watchdogCPUEvents = 0;
@@ -36,7 +37,6 @@ double watchdogRAMPeak = 0;
@interface SNTDaemonControlController ()
@property NSString *_syncXsrfToken;
@property dispatch_source_t syncTimer;
@property SNTPolicyProcessor *policyProcessor;
@end
@@ -45,46 +45,12 @@ double watchdogRAMPeak = 0;
- (instancetype)init {
self = [super init];
if (self) {
_syncTimer = [self createSyncTimer];
[self rescheduleSyncSecondsFromNow:30];
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:
[SNTDatabaseController ruleTable]];
}
return self;
}
- (dispatch_source_t)createSyncTimer {
dispatch_source_t syncTimerQ = dispatch_source_create(
DISPATCH_SOURCE_TYPE_TIMER, 0, 0,
dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0));
dispatch_source_set_event_handler(syncTimerQ, ^{
[self rescheduleSyncSecondsFromNow:600];
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[[SNTConfigurator configurator] setSyncBackOff:NO];
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog", NULL));
}
});
dispatch_resume(syncTimerQ);
return syncTimerQ;
}
- (void)rescheduleSyncSecondsFromNow:(uint64_t)seconds {
uint64_t interval = seconds * NSEC_PER_SEC;
uint64_t leeway = (seconds * 0.05) * NSEC_PER_SEC;
dispatch_source_set_timer(self.syncTimer, dispatch_walltime(NULL, interval), interval, leeway);
}
#pragma mark Kernel ops
- (void)cacheCount:(void (^)(int64_t))reply {
@@ -127,10 +93,6 @@ double watchdogRAMPeak = 0;
reply([[SNTDatabaseController eventTable] pendingEventsCount]);
}
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply {
reply([[SNTDatabaseController eventTable] pendingEventForSHA256:sha256]);
}
- (void)databaseEventsPending:(void (^)(NSArray *events))reply {
reply([[SNTDatabaseController eventTable] pendingEvents]);
}
@@ -180,12 +142,6 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
[self rescheduleSyncSecondsFromNow:seconds];
[[SNTConfigurator configurator] setSyncBackOff:YES];
reply();
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncLastSuccess:date];
reply();
@@ -225,4 +181,38 @@ double watchdogRAMPeak = 0;
self.notQueue.notifierConnection = c;
}
#pragma mark syncd Ops
- (void)setSyncdListener:(NSXPCListenerEndpoint *)listener {
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithListener:listener];
c.remoteInterface = [SNTXPCSyncdInterface syncdInterface];
c.invalidationHandler = ^{
[self.syncdQueue stopSyncingEvents];
self.syncdQueue.invalidationHandler();
};
c.acceptedHandler = ^{
[self.syncdQueue startSyncingEvents];
};
[c resume];
self.syncdQueue.syncdConnection = c;
}
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply {
[[self.syncdQueue.syncdConnection remoteObjectProxy] rescheduleSyncSecondsFromNow:seconds];
[[SNTConfigurator configurator] setSyncBackOff:YES];
reply();
}
- (void)pushNotifications:(void (^)(BOOL))reply {
[self.syncdQueue.syncdConnection.remoteObjectProxy isFCMListening:^(BOOL response) {
reply(response);
}];
}
- (void)postRuleSyncNotificationWithCustomMessage:(NSString *)message reply:(void (^)())reply {
[[self.notQueue.notifierConnection remoteObjectProxy]
postRuleSyncNotificationWithCustomMessage:message];
reply();
}
@end

View File

@@ -21,6 +21,7 @@
@class SNTEventTable;
@class SNTNotificationQueue;
@class SNTRuleTable;
@class SNTSyncdQueue;
///
/// SNTExecutionController is responsible for handling binary execution requests:
@@ -36,6 +37,7 @@
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog;
///

View File

@@ -36,6 +36,7 @@
#import "SNTRule.h"
#import "SNTRuleTable.h"
#import "SNTStoredEvent.h"
#import "SNTSyncdQueue.h"
@interface SNTExecutionController ()
@property SNTDriverManager *driverManager;
@@ -44,8 +45,8 @@
@property SNTNotificationQueue *notifierQueue;
@property SNTPolicyProcessor *policyProcessor;
@property SNTRuleTable *ruleTable;
@property SNTSyncdQueue *syncdQueue;
@property NSCache<NSString *, NSDate *> *uploadBackoff;
@property dispatch_queue_t eventQueue;
@end
@@ -57,6 +58,7 @@
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog {
self = [super init];
if (self) {
@@ -64,11 +66,10 @@
_ruleTable = ruleTable;
_eventTable = eventTable;
_notifierQueue = notifierQueue;
_syncdQueue = syncdQueue;
_eventLog = eventLog;
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
_uploadBackoff = [[NSCache alloc] init];
_uploadBackoff.countLimit = 128;
_eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL);
// This establishes the XPC connection between libsecurity and syspolicyd.
@@ -245,7 +246,7 @@
}
/**
This runs `santactl sync` for the event that was just saved, so that the user
This sends the event that was just saved to santactl for immediate upload, so that the user
has something to vote in straight away.
This method is always called on a serial queue to keep this low-priority method
@@ -259,24 +260,7 @@
![[SNTConfigurator configurator] syncBaseURL] ||
[[SNTConfigurator configurator] syncBackOff]) return;
// The event upload is skipped if an event upload has been initiated for it in the
// last 10 minutes.
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
if (fork() == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog",
"singleevent", [event.fileSHA256 UTF8String], NULL));
}
[self.syncdQueue addEvent:event];
}
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {

View File

@@ -0,0 +1,28 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
@class SNTStoredEvent;
@class SNTXPCConnection;
@interface SNTSyncdQueue : NSObject
@property(nonatomic) SNTXPCConnection *syncdConnection;
@property(copy) void (^invalidationHandler)();
@property(copy) void (^acceptedHandler)();
- (void)addEvent:(SNTStoredEvent *)event;
- (void)startSyncingEvents;
- (void)stopSyncingEvents;
@end

View File

@@ -0,0 +1,72 @@
/// Copyright 2016 Google Inc. All rights reserved.
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
#import "SNTSyncdQueue.h"
#import "SNTLogging.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
#import "SNTXPCSyncdInterface.h"
@interface SNTSyncdQueue ()
@property NSCache<NSString *, NSDate *> *uploadBackoff;
@property dispatch_queue_t syncdQueue;
@property dispatch_semaphore_t sema;
@end
@implementation SNTSyncdQueue
- (instancetype)init {
self = [super init];
if (self) {
_uploadBackoff = [[NSCache alloc] init];
_uploadBackoff.countLimit = 128;
_syncdQueue = dispatch_queue_create("com.google.syncd_queue", DISPATCH_QUEUE_SERIAL);
_sema = dispatch_semaphore_create(0);
}
return self;
}
- (void)addEvent:(SNTStoredEvent *)event {
// The event upload is skipped if an event upload has been initiated for it in the
// last 10 minutes.
NSDate *backoff = [self.uploadBackoff objectForKey:event.fileSHA256];
NSDate *now = [NSDate date];
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
[self.uploadBackoff setObject:now forKey:event.fileSHA256];
// Hold events for a few seconds to allow santad and santactl to establish connections.
// If the connections are not established in time drop the event from the queue.
// They will be uploaded during a full sync.
dispatch_async(self.syncdQueue, ^{
if (!dispatch_semaphore_wait(self.sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
[self.syncdConnection.remoteObjectProxy postEventToSyncServer:event];
// Let em flow
dispatch_semaphore_signal(self.sema);
} else {
LOGI(@"Dropping event %@ from com.google.syncd_queue", event.fileSHA256);
}
});
}
- (void)startSyncingEvents {
dispatch_semaphore_signal(self.sema);
}
- (void)stopSyncingEvents {
self.sema = dispatch_semaphore_create(0);
}
@end

View File

@@ -46,6 +46,7 @@
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:@"/usr/bin/false"];
SNTStoredEvent *event;
event = [[SNTStoredEvent alloc] init];
event.idx = @(arc4random());
event.filePath = @"/usr/bin/false";
event.fileSHA256 = [binInfo SHA256];
event.signingChain = [csInfo certificates];
@@ -67,7 +68,7 @@
SNTStoredEvent *event = [self createTestEvent];
[self.sut addStoredEvent:event];
SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:event.fileSHA256];
SNTStoredEvent *storedEvent = [self.sut pendingEvents].firstObject;
XCTAssertNotNil(storedEvent);
XCTAssertEqualObjects(event.filePath, storedEvent.filePath);
XCTAssertEqualObjects(event.signingChain, storedEvent.signingChain);
@@ -81,8 +82,7 @@
[self.sut addStoredEvent:newEvent];
XCTAssertEqual(self.sut.pendingEventsCount, 1);
SNTStoredEvent *storedEvent = [self.sut pendingEventForSHA256:newEvent.fileSHA256];
[self.sut deleteEventWithId:storedEvent.idx];
[self.sut deleteEventWithId:newEvent.idx];
XCTAssertEqual(self.sut.pendingEventsCount, 0);
}

View File

@@ -66,6 +66,7 @@
ruleTable:self.mockRuleDatabase
eventTable:self.mockEventDatabase
notifierQueue:nil
syncdQueue:nil
eventLog:nil];
}