Compare commits

...

13 Commits
0.8.3 ... 0.8.4

Author SHA1 Message Date
Russell Hancox
49b169ec36 SNTFileWatcher: unset source event handler correctly 2015-04-08 14:22:24 -04:00
Russell Hancox
41d1d7e3de SNTFileWatcher: fix some dispatch bugs, move eventHandler property to
class extension, add tests.
2015-04-08 12:29:41 -04:00
Russell Hancox
323a38dc21 Project: Clean-up block style, order of public/private in C++ headers 2015-04-08 12:29:22 -04:00
Russell Hancox
c37f1eb006 SNTConfigurator: remove auto-reloading code, move to file watching
class.
2015-04-08 12:28:58 -04:00
Russell Hancox
b7b2b5b630 santactl/status: Add daemon status, currently just the mode. 2015-04-01 16:15:35 -04:00
Russell Hancox
2486cfdcff santactl/sync: Update logging 2015-04-01 16:15:14 -04:00
Russell Hancox
4231781178 Project: Update CocoaPods 2015-03-31 18:13:37 -04:00
Russell Hancox
7ba886ed18 SNTConfigurator: Fix broken MachineOwner parsing, part 2 2015-03-31 17:29:11 -04:00
Russell Hancox
8096701fbd SNTConfigurator: Fix broken MachineID/MachineOwner parsing 2015-03-31 17:26:02 -04:00
Russell Hancox
16531d18c8 santa-driver: Kill daemon if PostToQueue is failing too much. 2015-03-31 16:14:32 -04:00
Russell Hancox
ef0cc2fffd santactl/sync: Put constant API strings in a separate file 2015-03-31 15:56:47 -04:00
Russell Hancox
f2dc7fb4b0 SNTConfigurator: Only reject and re-save client mode in santad. 2015-03-31 15:49:49 -04:00
Russell Hancox
707e9a11d4 SantaGUI: Let background move window, part 2. 2015-03-31 12:20:43 -04:00
30 changed files with 759 additions and 297 deletions

View File

@@ -11,7 +11,7 @@ DEPENDENCIES:
- OCMock
SPEC CHECKSUMS:
FMDB: 0efa188cf0dd1ce82c27a478cd5f5fa245308677
OCMock: ecdd510b73ef397f2f97274785c1e87fd147c49f
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
COCOAPODS: 0.35.0
COCOAPODS: 0.36.1

View File

@@ -147,6 +147,11 @@
0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DCD604A19105433006B445C /* SNTStoredEvent.m */; };
0DE50F6E191304E0007B2B0C /* SNTRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE50F671912716A007B2B0C /* SNTRule.m */; };
0DE6788D1784A8C2007A9E52 /* SNTExecutionController.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */; };
0DEFB7C01ACB28B000B92AAE /* SNTCommandSyncConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */; };
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */; };
0DF395641AB76A7900CBC520 /* NSData+Zlib.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DF395631AB76A7900CBC520 /* NSData+Zlib.m */; };
0DF395661AB76ABC00CBC520 /* libz.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 0DF395651AB76ABC00CBC520 /* libz.dylib */; };
4092327A1A51B66400A04527 /* SNTCommandRule.m in Sources */ = {isa = PBXBuildFile; fileRef = 409232791A51B65D00A04527 /* SNTCommandRule.m */; };
@@ -325,6 +330,11 @@
0DE50F671912716A007B2B0C /* SNTRule.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTRule.m; sourceTree = "<group>"; };
0DE6788B1784A8C2007A9E52 /* SNTExecutionController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTExecutionController.h; sourceTree = "<group>"; };
0DE6788C1784A8C2007A9E52 /* SNTExecutionController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = SNTExecutionController.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTCommandSyncConstants.m; sourceTree = "<group>"; };
0DEFB7C11ACB28BC00B92AAE /* SNTCommandSyncConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SNTCommandSyncConstants.h; sourceTree = "<group>"; };
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SNTFileWatcher.h; sourceTree = "<group>"; };
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcher.m; sourceTree = "<group>"; };
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SNTFileWatcherTest.m; sourceTree = "<group>"; };
0DF395621AB76A7900CBC520 /* NSData+Zlib.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSData+Zlib.h"; sourceTree = "<group>"; };
0DF395631AB76A7900CBC520 /* NSData+Zlib.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSData+Zlib.m"; sourceTree = "<group>"; };
0DF395651AB76ABC00CBC520 /* libz.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libz.dylib; path = usr/lib/libz.dylib; sourceTree = SDKROOT; };
@@ -410,6 +420,7 @@
0D41DAD31A7C28C800A890FE /* SNTEventTableTest.m */,
0DD0D490194F9947005F27EB /* SNTExecutionControllerTest.m */,
0DD0D48E194F78F8005F27EB /* SNTFileInfoTest.m */,
0DEFB7C71ACF0BFE00B92AAE /* SNTFileWatcherTest.m */,
0D3AFBE618FB32CB0087BCEE /* SNTXPCConnectionTest.m */,
);
path = LogicTests;
@@ -464,6 +475,8 @@
0DCD6060191188B1006B445C /* SNTAuthenticatingURLSession.h */,
0DCD6061191188B1006B445C /* SNTAuthenticatingURLSession.m */,
0D35BDB418FD84F600921A21 /* SNTCommandSync.m */,
0DEFB7C11ACB28BC00B92AAE /* SNTCommandSyncConstants.h */,
0DEFB7BF1ACB28B000B92AAE /* SNTCommandSyncConstants.m */,
0D41640319197AD7006A356A /* SNTCommandSyncEventUpload.h */,
0D41640419197AD7006A356A /* SNTCommandSyncEventUpload.m */,
0DC5D86F192160180078A5C0 /* SNTCommandSyncLogUpload.h */,
@@ -616,6 +629,8 @@
0D10BE851A0AABD600C0C944 /* SNTDropRootPrivs.m */,
0DCD6040190ACCB8006B445C /* SNTFileInfo.h */,
0DCD6041190ACCB8006B445C /* SNTFileInfo.m */,
0DEFB7C21ACDD80100B92AAE /* SNTFileWatcher.h */,
0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */,
0D28E5E31926AFE400280F87 /* SNTKernelCommon.h */,
0D28E5E119269B3600280F87 /* SNTLogging.h */,
0DA73C9E1934F8100056D7C4 /* SNTLogging.m */,
@@ -1089,7 +1104,9 @@
0DCD604D19105433006B445C /* SNTStoredEvent.m in Sources */,
0DCD605819115E57006B445C /* SNTXPCControlInterface.m in Sources */,
0D10BE891A0AAF6700C0C944 /* SNTDropRootPrivs.m in Sources */,
0DEFB7C61ACDE5F600B92AAE /* SNTFileWatcher.m in Sources */,
0D10BE8B1A0AB23300C0C944 /* SNTDERDecoderTest.m in Sources */,
0DEFB7C81ACF0BFE00B92AAE /* SNTFileWatcherTest.m in Sources */,
0DD0D48B194F6193005F27EB /* SNTCertificateTest.m in Sources */,
0D28D53819D9F5910015C5EB /* SNTConfigurator.m in Sources */,
0D3AFBE718FB32CB0087BCEE /* SNTXPCConnectionTest.m in Sources */,
@@ -1116,6 +1133,7 @@
0D35BDC218FDA5D100921A21 /* SNTCodesignChecker.m in Sources */,
0D35BDB518FD84F600921A21 /* SNTCommandSync.m in Sources */,
0DCD5FBF1909D64A006B445C /* SNTCommandBinaryInfo.m in Sources */,
0DEFB7C01ACB28B000B92AAE /* SNTCommandSyncConstants.m in Sources */,
0DCD6062191188B1006B445C /* SNTAuthenticatingURLSession.m in Sources */,
0DCD605619115D17006B445C /* SNTXPCControlInterface.m in Sources */,
0DE50F6C19130358007B2B0C /* SNTStoredEvent.m in Sources */,
@@ -1161,6 +1179,7 @@
0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */,
0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */,
0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */,
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
0D20710E1A7C4A86008B0A9A /* SNTStoredEvent.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
@@ -1190,6 +1209,7 @@
0D1AF477187C7A2C00D3298D /* SNTCertificate.m in Sources */,
0DA73C9F1934F8100056D7C4 /* SNTLogging.m in Sources */,
0DCD6042190ACCB8006B445C /* SNTFileInfo.m in Sources */,
0DEFB7C41ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
0DC5D86D191AED220078A5C0 /* SNTRuleTable.m in Sources */,
0D7D01871774F93A005DBAB4 /* SNTDriverManager.m in Sources */,
0D8E18CD19107B56000F89B8 /* SNTDaemonControlController.m in Sources */,

View File

@@ -16,11 +16,13 @@
#import "SNTAboutWindowController.h"
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTXPCConnection.h"
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *listener;
@end
@@ -31,6 +33,12 @@
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenu];
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^{
[[SNTConfigurator configurator] reloadConfigData];
}];
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
self.notificationManager = [[SNTNotificationManager alloc] init];
@@ -45,9 +53,6 @@
object:nil];
[self createConnection];
// Load configuration from disk ready for first message.
(void)[SNTConfigurator configurator];
}
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
@@ -60,9 +65,8 @@
- (void)createConnection {
__weak __typeof(self) weakSelf = self;
self.listener =
[[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
options:NSXPCConnectionPrivileged];
self.listener = [[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
options:NSXPCConnectionPrivileged];
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
self.listener.exportedObject = self.notificationManager;
self.listener.rejectedHandler = ^{

View File

@@ -36,6 +36,7 @@
- (void)loadWindow {
[super loadWindow];
[self.window setLevel:NSPopUpMenuWindowLevel];
[self.window setMovableByWindowBackground:YES];
[self.window center];
if (![[SNTConfigurator configurator] eventDetailURL]) {
@@ -76,15 +77,15 @@
}
- (IBAction)openEventDetails:(id)sender {
NSString *formatStr = [[SNTConfigurator configurator] eventDetailURL];
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *formatStr = config.eventDetailURL;
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
withString:self.event.fileSHA256];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
withString:self.event.executingUser];
formatStr =
[formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:[[SNTConfigurator configurator] machineID]];
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
withString:config.machineID];
[self closeWindow:sender];
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:formatStr]];

View File

@@ -16,14 +16,17 @@
///
/// Singleton that provides an interface for managing configuration values on disk
/// @note This class is designed as a singleton but that is not enforced.
/// @note This class is designed as a singleton but that is not strictly enforced.
///
@interface SNTConfigurator : NSObject
/// Default config file path
extern NSString * const kDefaultConfigFilePath;
#pragma mark - Daemon Settings
///
/// The operating mode
/// The operating mode.
///
@property santa_clientmode_t clientMode;
@@ -64,17 +67,17 @@
# pragma mark - Sync Settings
///
/// The base URL of the sync server
/// The base URL of the sync server.
///
@property(readonly) NSURL *syncBaseURL;
///
/// The machine owner
/// The machine owner.
///
@property(readonly) NSString *machineOwner;
///
/// If set, this over-rides the default machine ID used for syncing
/// If set, this over-rides the default machine ID used for syncing.
///
@property(readonly) NSString *machineID;
@@ -116,15 +119,20 @@
@property(readonly) NSString *syncClientAuthCertificateIssuer;
///
/// Retrieve an initialized singleton configurator object using the default file path
/// Retrieve an initialized singleton configurator object using the default file path.
///
+ (instancetype)configurator;
///
/// Designated initializer
/// Designated initializer.
///
/// @param filePath The path to the file to use as a backing store.
///
- (instancetype)initWithFilePath:(NSString *)filePath;
///
/// Re-read config data from disk.
///
- (void)reloadConfigData;
@end

View File

@@ -20,17 +20,12 @@
@interface SNTConfigurator ()
@property NSString *configFilePath;
@property NSMutableDictionary *configData;
@property dispatch_source_t fileMonitoringSource;
@property(strong) void (^fileEventHandler)(void);
@property(strong) void (^fileCancelHandler)(void);
@end
@implementation SNTConfigurator
/// The hard-coded path to the config file
static NSString * const kConfigFilePath = @"/var/db/santa/config.plist";
NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
/// The keys in the config file
static NSString * const kClientModeKey = @"ClientMode";
@@ -62,7 +57,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
self = [super init];
if (self) {
_configFilePath = filePath;
[self beginWatchingConfigFile];
[self reloadConfigData];
}
return self;
}
@@ -73,75 +68,13 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
static SNTConfigurator *sharedConfigurator = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kConfigFilePath];
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
});
return sharedConfigurator;
}
# pragma mark Public Interface
- (NSURL *)syncBaseURL {
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
}
- (NSString *)syncClientAuthCertificateFile {
return self.configData[kClientAuthCertificateFileKey];
}
- (NSString *)syncClientAuthCertificatePassword {
return self.configData[kClientAuthCertificatePasswordKey];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configData[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configData[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configData[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configData[kServerAuthRootsFileKey];
}
- (NSString *)machineOwner {
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
return plist[self.configData[kMachineOwnerPlistKeyKey]];
}
if (self.configData[kMachineOwnerKey]) {
return self.configData[kMachineOwnerKey];
}
return @"";
}
- (NSString *)machineID {
NSString *machineId;
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
machineId = plist[kMachineIDPlistKeyKey];
}
if (self.configData[kMachineIDKey]) {
machineId = self.configData[kMachineIDKey];
}
if (!machineId || [machineId isEqual:@""]) {
machineId = [SNTSystemInfo hardwareUUID];
}
return machineId;
}
- (santa_clientmode_t)clientMode {
int cm = [self.configData[kClientModeKey] intValue];
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
@@ -180,36 +113,78 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return self.configData[kEventDetailTextKey];
}
#pragma mark Private
///
/// Saves the current @c _configData to disk.
///
- (void)saveConfigToDisk {
[self.configData writeToFile:kConfigFilePath atomically:YES];
- (NSURL *)syncBaseURL {
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
}
- (NSString *)syncClientAuthCertificateFile {
return self.configData[kClientAuthCertificateFileKey];
}
- (NSString *)syncClientAuthCertificatePassword {
return self.configData[kClientAuthCertificatePasswordKey];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configData[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configData[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configData[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configData[kServerAuthRootsFileKey];
}
- (NSString *)machineOwner {
NSString *machineOwner;
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
}
if (self.configData[kMachineOwnerKey]) {
machineOwner = self.configData[kMachineOwnerKey];
}
if (!machineOwner) machineOwner = @"";
return machineOwner;
}
- (NSString *)machineID {
NSString *machineId;
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
}
if (self.configData[kMachineIDKey]) {
machineId = self.configData[kMachineIDKey];
}
if (!machineId || [machineId isEqual:@""]) {
machineId = [SNTSystemInfo hardwareUUID];
}
return machineId;
}
///
/// Populate @c self.configData, using the config file on disk if possible,
/// otherwise an empty mutable dictionary.
///
/// If the config file's permissions are not @c 0644, will attempt to set them
/// but will fail silently if this cannot be done.
///
- (void)reloadConfigData {
if (!self.configData) self.configData = [NSMutableDictionary dictionary];
NSFileManager *fm = [NSFileManager defaultManager];
if (![fm fileExistsAtPath:self.configFilePath]) return;
// Ensure the config file permissions are 0644. Fail silently if they can't be changed.
NSDictionary *fileAttrs = [fm attributesOfItemAtPath:self.configFilePath error:nil];
if ([fileAttrs filePosixPermissions] != 0644) {
[fm setAttributes:@{ NSFilePosixPermissions: @(0644) }
ofItemAtPath:self.configFilePath
error:nil];
}
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:self.configFilePath
options:NSDataReadingMappedIfSafe
@@ -229,10 +204,10 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
return;
}
// Ensure user isn't trying to change the client mode while running, only santactl can do that.
// Ensure no-one is trying to change the client mode behind Santa's back.
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]]) {
LOGW(@"Client mode in config file was changed behind our back, resetting.");
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
geteuid() == 0) {
NSMutableDictionary *configDataMutable = [configData mutableCopy];
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
self.configData = configDataMutable;
@@ -242,45 +217,13 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
}
}
- (void)beginWatchingConfigFile {
if (self.fileMonitoringSource) return;
#pragma mark Private
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
if (!queue) return;
__weak typeof(self) weakSelf = self;
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
self.fileEventHandler = ^{
unsigned long l = dispatch_source_get_data(weakSelf.fileMonitoringSource);
if (l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_RENAME) {
dispatch_source_cancel(weakSelf.fileMonitoringSource);
} else {
[weakSelf reloadConfigData];
}
};
self.fileCancelHandler = ^{
int fd;
if (weakSelf.fileMonitoringSource) {
fd = (int)dispatch_source_get_handle(weakSelf.fileMonitoringSource);
close(fd);
}
while ((fd = open([weakSelf.configFilePath fileSystemRepresentation], O_EVTONLY)) < 0) {
sleep(1);
}
weakSelf.fileMonitoringSource = dispatch_source_create(
DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
dispatch_source_set_event_handler(weakSelf.fileMonitoringSource, weakSelf.fileEventHandler);
dispatch_source_set_cancel_handler(weakSelf.fileMonitoringSource, weakSelf.fileCancelHandler);
dispatch_resume(weakSelf.fileMonitoringSource);
[weakSelf reloadConfigData];
};
dispatch_async(queue, self.fileCancelHandler);
///
/// Saves the current @c self.configData to disk.
///
- (void)saveConfigToDisk {
[self.configData writeToFile:self.configFilePath atomically:YES];
}
@end

View File

@@ -0,0 +1,34 @@
/// Copyright 2015 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.
///
/// Simple file watching class using dispatch sources. Will automatically
/// reload the watch if the file is deleted. Will continue watching for
/// events until deallocated.
///
@interface SNTFileWatcher : NSObject
///
/// Designated initializer
/// Initializes the watcher and begins watching for modifications.
///
/// @param filePath the file to watch.
/// @param handler the handler to call when changes happen.
///
/// @note Shortly after the file has been opened and monitoring has begun, the provided handler
/// will be called.
///
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler;
@end

View File

@@ -0,0 +1,102 @@
/// Copyright 2015 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 "SNTFileWatcher.h"
@interface SNTFileWatcher ()
@property NSString *filePath;
@property dispatch_source_t monitoringSource;
@property(strong) void (^eventHandler)(void);
@property(strong) void (^internalEventHandler)(void);
@property(strong) void (^internalCancelHandler)(void);
@end
@implementation SNTFileWatcher
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler {
self = [super init];
if (self) {
_filePath = filePath;
_eventHandler = handler;
if (!_filePath || !_eventHandler) return nil;
[self beginWatchingFile];
}
return self;
}
- (void)dealloc {
[self stopWatchingFile];
}
- (void)beginWatchingFile {
__weak typeof(self) weakSelf = self;
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
self.internalEventHandler = ^{
unsigned long l = dispatch_source_get_data(weakSelf.monitoringSource);
if (l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_RENAME) {
if (weakSelf.monitoringSource) dispatch_source_cancel(weakSelf.monitoringSource);
} else {
weakSelf.eventHandler();
}
};
self.internalCancelHandler = ^{
int fd;
if (weakSelf.monitoringSource) {
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
close(fd);
}
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
usleep(1000);
}
weakSelf.monitoringSource = dispatch_source_create(
DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
dispatch_source_set_event_handler(weakSelf.monitoringSource, weakSelf.internalEventHandler);
dispatch_source_set_cancel_handler(weakSelf.monitoringSource, weakSelf.internalCancelHandler);
dispatch_resume(weakSelf.monitoringSource);
weakSelf.eventHandler();
};
dispatch_async(queue, self.internalCancelHandler);
}
- (void)stopWatchingFile {
if (!self.monitoringSource) return;
int fd = (int)dispatch_source_get_handle(self.monitoringSource);
dispatch_source_set_event_handler_f(self.monitoringSource, NULL);
dispatch_source_set_cancel_handler(self.monitoringSource, ^{
close(fd);
});
dispatch_source_cancel(self.monitoringSource);
self.monitoringSource = nil;
}
@end

View File

@@ -58,6 +58,6 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
}
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
}
}

View File

@@ -26,6 +26,8 @@ bool SantaDecisionManager::init() {
cached_decisions_ = OSDictionary::withCapacity(1000);
owning_pid_ = 0;
return kIOReturnSuccess;
}
@@ -70,6 +72,7 @@ void SantaDecisionManager::ConnectClient(IOSharedDataQueue *queue, pid_t pid) {
owning_pid_ = pid;
owning_proc_ = proc_find(pid);
failed_queue_requests_ = 0;
}
void SantaDecisionManager::DisconnectClient() {
@@ -283,6 +286,11 @@ santa_action_t SantaDecisionManager::FetchDecision(
do {
// Send request to daemon...
if (!PostToQueue(message)) {
OSIncrementAtomic(&failed_queue_requests_);
if (failed_queue_requests_ > kMaxQueueFailures) {
LOGE("Failed to queue more than %d requests, killing daemon", kMaxQueueFailures);
proc_signal(owning_pid_, SIGKILL);
}
LOGE("Failed to queue request for %s.", path);
CacheCheck(vnode_id_str);
return ACTION_ERROR;

View File

@@ -55,6 +55,11 @@ const int kMaxRequestLoops = 50;
///
const int kMaxCacheSize = 10000;
///
/// Maximum number of PostToQueue failures to allow.
///
const int kMaxQueueFailures = 10;
///
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
/// and responding to the request appropriately.
@@ -142,6 +147,7 @@ class SantaDecisionManager : public OSObject {
OSDictionary *cached_decisions_;
IOSharedDataQueue *dataqueue_;
SInt32 failed_queue_requests_;
SInt32 listener_invocations_;

View File

@@ -49,7 +49,6 @@ bool SantaDriverClient::start(IOService *provider) {
void SantaDriverClient::stop(IOService *provider) {
super::stop(provider);
fSDM = NULL;
fProvider = NULL;
}

View File

@@ -43,12 +43,6 @@ const int kMaxQueueEvents = 256;
class com_google_SantaDriverClient : public IOUserClient {
OSDeclareDefaultStructors(com_google_SantaDriverClient);
private:
IOSharedDataQueue *fDataQueue;
IOMemoryDescriptor *fSharedMemory;
com_google_SantaDriver *fProvider;
SantaDecisionManager *fSDM;
public:
/// Called as part of IOServiceOpen in clients
bool initWithTask(task_t owningTask, void *securityID, UInt32 type);
@@ -122,6 +116,12 @@ class com_google_SantaDriverClient : public IOUserClient {
com_google_SantaDriverClient *target,
void *reference,
IOExternalMethodArguments *arguments);
private:
IOSharedDataQueue *fDataQueue;
IOMemoryDescriptor *fSharedMemory;
com_google_SantaDriver *fProvider;
SantaDecisionManager *fSDM;
};
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H

View File

@@ -26,10 +26,6 @@
class SantaMessage : public OSObject {
OSDeclareDefaultStructors(SantaMessage)
private:
santa_action_t action_;
uint64_t microsecs_;
public:
// Returns the time the action was last set.
uint64_t getMicrosecs() const;
@@ -39,6 +35,10 @@ class SantaMessage : public OSObject {
// Sets the acion and receive time.
void setAction(const santa_action_t action, const uint64_t microsecs);
private:
santa_action_t action_;
uint64_t microsecs_;
};
#endif // SANTA__SANTA_DRIVER__SANTAMESSAGE_H

View File

@@ -152,12 +152,12 @@ REGISTER_COMMAND_NAME(@"rule");
newRule.customMsg = customMsg;
[[daemonConn remoteObjectProxy] databaseRuleAddRule:newRule withReply:^{
if (state == RULESTATE_REMOVE) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
}
exit(0);
if (state == RULESTATE_REMOVE) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
}
exit(0);
}];
}

View File

@@ -41,6 +41,22 @@ REGISTER_COMMAND_NAME(@"status");
}
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
// Daemon status
__block NSString *clientMode;
[[daemonConn remoteObjectProxy] clientMode:^(santa_clientmode_t cm) {
switch (cm) {
case CLIENTMODE_MONITOR:
clientMode = @"Monitor"; break;
case CLIENTMODE_LOCKDOWN:
clientMode = @"Lockdown"; break;
default:
clientMode = [NSString stringWithFormat:@"Unknown (%d)", cm]; break;
}
}];
do { usleep(5000); } while (!clientMode);
printf(">>> Daemon Info\n");
printf(" %-25s | %s\n", "Mode", [clientMode UTF8String]);
// Kext status
__block uint64_t cacheCount = -1;
[[daemonConn remoteObjectProxy] cacheCount:^(uint64_t count) {

View File

@@ -68,13 +68,13 @@
}
if (![protectionSpace.protocol isEqual:NSURLProtectionSpaceHTTPS]) {
LOGD(@"Protection Space: %@ is not a secure protocol", protectionSpace.protocol);
LOGE(@"%@ is not a secure protocol", protectionSpace.protocol);
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
if (!protectionSpace.receivesCredentialSecurely) {
LOGD(@"Protection Space: secure authentication or protocol cannot be established.");
LOGE(@"Secure authentication or protocol cannot be established.");
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
@@ -97,7 +97,7 @@
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
return;
} else {
LOGE(@"Server asked for client authentication but no usable client certificate found.");
LOGE(@"Unable to verify server identity.");
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
return;
}
@@ -111,11 +111,11 @@
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
newRequest:(NSURLRequest *)request
completionHandler:(void (^)(NSURLRequest *))completionHandler {
if (self.refusesRedirects) {
completionHandler(NULL);
} else {
completionHandler(request);
}
if (self.refusesRedirects) {
completionHandler(NULL);
} else {
completionHandler(request);
}
}
#pragma mark Private Helpers for URLSession:didReceiveChallenge:completionHandler:
@@ -142,7 +142,7 @@
NSError *error;
NSData *data = [NSData dataWithContentsOfFile:self.clientCertFile options:0 error:&error];
if (error) {
LOGE(@"Client Trust: Couldn't open client certificate %@: %@",
LOGD(@"Client Trust: Couldn't open client certificate %@: %@",
self.clientCertFile,
[error localizedDescription]);
return nil;
@@ -158,7 +158,7 @@
NSArray *identities = CFBridgingRelease(cfIdentities);
if (err != errSecSuccess) {
LOGE(@"Client Trust: Couldn't load client certificate %@: %d", self.clientCertFile, err);
LOGD(@"Client Trust: Couldn't load client certificate %@: %d", self.clientCertFile, err);
return nil;
}
@@ -181,56 +181,59 @@
// Manually iterate through available identities to find one with an allowed issuer.
[identities enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
SecIdentityRef identityRef = (__bridge SecIdentityRef)obj;
SecIdentityRef identityRef = (__bridge SecIdentityRef)obj;
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(identityRef, &certificate);
if (err != errSecSuccess) {
LOGD(@"Client Trust: Failed to read certificate data: %d. Skipping identity.", (int)err);
return;
}
SNTCertificate *clientCert = [[SNTCertificate alloc] initWithSecCertificateRef:certificate];
CFRelease(certificate);
// Switch identity finding method depending on config
if (self.clientCertCommonName && clientCert.commonName) {
if ([clientCert.commonName compare:self.clientCertCommonName
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
foundIdentity = identityRef;
CFRetain(foundIdentity);
*stop = YES;
return; // return from enumeration block
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(identityRef, &certificate);
if (err != errSecSuccess) {
LOGD(@"Client Trust: Failed to read certificate data: %d. Skipping identity.", (int)err);
return;
}
} else if (self.clientCertIssuerCn && clientCert.issuerCommonName) {
if ([clientCert.issuerCommonName compare:self.clientCertIssuerCn
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
foundIdentity = identityRef;
CFRetain(foundIdentity);
*stop = YES;
return; // return from enumeration block
}
} else {
for (NSData *allowedIssuer in protectionSpace.distinguishedNames) {
SNTDERDecoder *decoder = [[SNTDERDecoder alloc] initWithData:allowedIssuer];
if (!decoder) continue;
if ([clientCert.issuerCommonName isEqual:decoder.commonName] &&
[clientCert.issuerCountryName isEqual:decoder.countryName] &&
[clientCert.issuerOrgName isEqual:decoder.organizationName] &&
[clientCert.issuerOrgUnit isEqual:decoder.organizationalUnit]) {
SNTCertificate *clientCert = [[SNTCertificate alloc] initWithSecCertificateRef:certificate];
CFRelease(certificate);
// Switch identity finding method depending on config
if (self.clientCertCommonName && clientCert.commonName) {
if ([clientCert.commonName compare:self.clientCertCommonName
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
foundIdentity = identityRef;
CFRetain(foundIdentity);
*stop = YES;
return; // return from enumeration block
}
} else if (self.clientCertIssuerCn && clientCert.issuerCommonName) {
if ([clientCert.issuerCommonName compare:self.clientCertIssuerCn
options:NSCaseInsensitiveSearch] == NSOrderedSame) {
foundIdentity = identityRef;
CFRetain(foundIdentity);
*stop = YES;
return; // return from enumeration block
}
} else {
for (NSData *allowedIssuer in protectionSpace.distinguishedNames) {
SNTDERDecoder *decoder = [[SNTDERDecoder alloc] initWithData:allowedIssuer];
if (!decoder) continue;
if ([clientCert.issuerCommonName isEqual:decoder.commonName] &&
[clientCert.issuerCountryName isEqual:decoder.countryName] &&
[clientCert.issuerOrgName isEqual:decoder.organizationName] &&
[clientCert.issuerOrgUnit isEqual:decoder.organizationalUnit]) {
foundIdentity = identityRef;
CFRetain(foundIdentity);
*stop = YES;
return; // return from enumeration block
}
}
}
}
}];
}
if (foundIdentity) {
LOGD(@"Client Trust: Valid client identity %@.", foundIdentity);
SecCertificateRef certificate = NULL;
err = SecIdentityCopyCertificate(foundIdentity, &certificate);
SNTCertificate *clientCert = [[SNTCertificate alloc] initWithSecCertificateRef:certificate];
LOGD(@"Client Trust: Valid client identity %@.", clientCert);
NSURLCredential *cred =
[NSURLCredential credentialWithIdentity:foundIdentity
certificates:nil
@@ -278,7 +281,7 @@
// Set this array of certs as the anchors to trust.
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certRefs);
if (err != errSecSuccess) {
LOGE(@"Server Trust: Could not set anchor certificates: %d", err);
LOGD(@"Server Trust: Could not set anchor certificates: %d", err);
return nil;
}
}
@@ -287,7 +290,7 @@
SecTrustResultType result = kSecTrustResultInvalid;
err = SecTrustEvaluate(serverTrust, &result);
if (err != errSecSuccess) {
LOGE(@"Server Trust: Unable to evaluate certificate chain for server: %d", err);
LOGD(@"Server Trust: Unable to evaluate certificate chain for server: %d", err);
return nil;
}
@@ -301,7 +304,7 @@
// Having a trust level "unspecified" by the user is the usual result, described at
// https://developer.apple.com/library/mac/qa/qa1360
if (result != kSecTrustResultProceed && result != kSecTrustResultUnspecified) {
LOGE(@"Server Trust: Server isn't trusted. SecTrustResultType: %d", result);
LOGD(@"Server Trust: Server isn't trusted. SecTrustResultType: %d", result);
return nil;
}

View File

@@ -118,6 +118,8 @@ REGISTER_COMMAND_NAME(@"sync");
if (!s.progress.syncBaseURL) {
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
exit(1);
} else if (![s.progress.syncBaseURL.scheme isEqual:@"https"]) {
LOGW(@"SyncBaseURL is not over HTTPS!");
}
authURLSession.serverHostname = s.progress.syncBaseURL.host;

View File

@@ -0,0 +1,68 @@
/// Copyright 2015 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.
extern NSString * const kURLPreflight;
extern NSString * const kURLEventUpload;
extern NSString * const kURLRuleDownload;
extern NSString * const kURLPostflight;
extern NSString * const kSerialNumber;
extern NSString * const kHostname;
extern NSString * const kSantaVer;
extern NSString * const kOSVer;
extern NSString * const kOSBuild;
extern NSString * const kPrimaryUser;
extern NSString * const kBatchSize;
extern NSString * const kUploadLogsURL;
extern NSString * const kClientMode;
extern NSString * const kClientModeMonitor;
extern NSString * const kClientModeLockdown;
extern NSString * const kEvents;
extern NSString * const kFileSHA256;
extern NSString * const kFilePath;
extern NSString * const kFileName;
extern NSString * const kExecutingUser;
extern NSString * const kExecutionTime;
extern NSString * const kDecision;
extern NSString * const kLoggedInUsers;
extern NSString * const kCurrentSessions;
extern NSString * const kFileBundleID;
extern NSString * const kFileBundleName;
extern NSString * const kFileBundleVersion;
extern NSString * const kFileBundleShortVersionString;
extern NSString * const kPID;
extern NSString * const kPPID;
extern NSString * const kSigningChain;
extern NSString * const kCertSHA256;
extern NSString * const kCertCN;
extern NSString * const kCertOrg;
extern NSString * const kCertOU;
extern NSString * const kCertValidFrom;
extern NSString * const kCertValidUntil;
extern NSString * const kLogUploadField;
extern NSString * const kRules;
extern NSString * const kRuleSHA256;
extern NSString * const kRulePolicy;
extern NSString * const kRulePolicyWhitelist;
extern NSString * const kRulePolicyBlacklist;
extern NSString * const kRulePolicySilentBlacklist;
extern NSString * const kRulePolicyRemove;
extern NSString * const kRuleType;
extern NSString * const kRuleTypeBinary;
extern NSString * const kRuleTypeCertificate;
extern NSString * const kRuleCustomMsg;
extern NSString * const kCursor;

View File

@@ -0,0 +1,70 @@
/// Copyright 2015 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 "SNTCommandSyncConstants.h"
NSString * const kURLPreflight = @"preflight/";
NSString * const kURLEventUpload = @"eventupload/";
NSString * const kURLRuleDownload = @"ruledownload/";
NSString * const kURLPostflight = @"postflight/";
NSString * const kSerialNumber = @"serial_no";
NSString * const kHostname = @"hostname";
NSString * const kSantaVer = @"santa_version";
NSString * const kOSVer = @"os_version";
NSString * const kOSBuild = @"os_build";
NSString * const kPrimaryUser = @"primary_user";
NSString * const kBatchSize = @"batch_size";
NSString * const kUploadLogsURL = @"upload_logs_url";
NSString * const kClientMode = @"client_mode";
NSString * const kClientModeMonitor = @"MONITOR";
NSString * const kClientModeLockdown = @"LOCKDOWN";
NSString * const kEvents = @"events";
NSString * const kFileSHA256 = @"file_sha256";
NSString * const kFilePath = @"file_path";
NSString * const kFileName = @"file_name";
NSString * const kExecutingUser = @"executing_user";
NSString * const kExecutionTime = @"execution_time";
NSString * const kDecision = @"decision";
NSString * const kLoggedInUsers = @"logged_in_users";
NSString * const kCurrentSessions = @"current_sessions";
NSString * const kFileBundleID = @"file_bundle_id";
NSString * const kFileBundleName = @"file_bundle_name";
NSString * const kFileBundleVersion = @"file_bundle_version";
NSString * const kFileBundleShortVersionString = @"file_bundle_version_string";
NSString * const kPID = @"pid";
NSString * const kPPID = @"ppid";
NSString * const kSigningChain = @"signing_chain";
NSString * const kCertSHA256 = @"sha256";
NSString * const kCertCN = @"cn";
NSString * const kCertOrg = @"org";
NSString * const kCertOU = @"ou";
NSString * const kCertValidFrom = @"valid_from";
NSString * const kCertValidUntil = @"valid_until";
NSString * const kLogUploadField = @"files";
NSString * const kRules = @"rules";
NSString * const kRuleSHA256 = @"sha256";
NSString * const kRulePolicy = @"policy";
NSString * const kRulePolicyWhitelist = @"WHITELIST";
NSString * const kRulePolicyBlacklist = @"BLACKLIST";
NSString * const kRulePolicySilentBlacklist = @"SILENT_BLACKLIST";
NSString * const kRulePolicyRemove = @"REMOVE";
NSString * const kRuleType = @"rule_type";
NSString * const kRuleTypeBinary = @"BINARY";
NSString * const kRuleTypeCertificate = @"CERTIFICATE";
NSString * const kRuleCustomMsg = @"custom_msg";
NSString * const kCursor = @"cursor";

View File

@@ -17,6 +17,7 @@
#include "SNTLogging.h"
#import "SNTCertificate.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncStatus.h"
#import "SNTStoredEvent.h"
#import "SNTXPCConnection.h"
@@ -28,7 +29,7 @@
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"eventupload/" stringByAppendingString:progress.machineID]
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
@@ -50,7 +51,7 @@
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"eventupload/" stringByAppendingString:progress.machineID]
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[[daemonConn remoteObjectProxy] databaseEventForSHA256:SHA256 withReply:^(SNTStoredEvent *event) {
if (!event) {
@@ -83,7 +84,7 @@
if (eventIds.count >= batchSize) break;
}
NSDictionary *uploadReq = @{ @"events": uploadEvents };
NSDictionary *uploadReq = @{ kEvents: uploadEvents };
NSData *requestBody;
@try {
@@ -101,8 +102,11 @@
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
LOGI(@"Uploaded %d events", eventIds.count);
@@ -129,38 +133,38 @@
#define ADDKEY(dict, key, value) if (value) dict[key] = value
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
ADDKEY(newEvent, @"file_sha256", event.fileSHA256);
ADDKEY(newEvent, @"file_path", [event.filePath stringByDeletingLastPathComponent]);
ADDKEY(newEvent, @"file_name", [event.filePath lastPathComponent]);
ADDKEY(newEvent, @"executing_user", event.executingUser);
ADDKEY(newEvent, @"execution_time", @([event.occurrenceDate timeIntervalSince1970]));
ADDKEY(newEvent, @"decision", @(event.decision));
ADDKEY(newEvent, @"logged_in_users", event.loggedInUsers);
ADDKEY(newEvent, @"current_sessions", event.currentSessions);
ADDKEY(newEvent, kFileSHA256, event.fileSHA256);
ADDKEY(newEvent, kFilePath, [event.filePath stringByDeletingLastPathComponent]);
ADDKEY(newEvent, kFileName, [event.filePath lastPathComponent]);
ADDKEY(newEvent, kExecutingUser, event.executingUser);
ADDKEY(newEvent, kExecutionTime, @([event.occurrenceDate timeIntervalSince1970]));
ADDKEY(newEvent, kDecision, @(event.decision));
ADDKEY(newEvent, kLoggedInUsers, event.loggedInUsers);
ADDKEY(newEvent, kCurrentSessions, event.currentSessions);
ADDKEY(newEvent, @"file_bundle_id", event.fileBundleID);
ADDKEY(newEvent, @"file_bundle_name", event.fileBundleName);
ADDKEY(newEvent, @"file_bundle_version", event.fileBundleVersion);
ADDKEY(newEvent, @"file_bundle_version_string", event.fileBundleVersionString);
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
ADDKEY(newEvent, @"pid", event.pid);
ADDKEY(newEvent, @"ppid", event.ppid);
ADDKEY(newEvent, kPID, event.pid);
ADDKEY(newEvent, kPPID, event.ppid);
NSMutableArray *signingChain = [NSMutableArray arrayWithCapacity:event.signingChain.count];
for (int i = 0; i < event.signingChain.count; i++) {
SNTCertificate *cert = [event.signingChain objectAtIndex:i];
NSMutableDictionary *certDict = [NSMutableDictionary dictionary];
ADDKEY(certDict, @"sha256", cert.SHA256);
ADDKEY(certDict, @"cn", cert.commonName);
ADDKEY(certDict, @"org", cert.orgName);
ADDKEY(certDict, @"ou", cert.orgUnit);
ADDKEY(certDict, @"valid_from", @([cert.validFrom timeIntervalSince1970]));
ADDKEY(certDict, @"valid_until", @([cert.validUntil timeIntervalSince1970]));
ADDKEY(certDict, kCertSHA256, cert.SHA256);
ADDKEY(certDict, kCertCN, cert.commonName);
ADDKEY(certDict, kCertOrg, cert.orgName);
ADDKEY(certDict, kCertOU, cert.orgUnit);
ADDKEY(certDict, kCertValidFrom, @([cert.validFrom timeIntervalSince1970]));
ADDKEY(certDict, kCertValidUntil, @([cert.validUntil timeIntervalSince1970]));
[signingChain addObject:certDict];
}
newEvent[@"signing_chain"] = signingChain;
newEvent[kSigningChain] = signingChain;
return newEvent;
#undef ADDKEY

View File

@@ -19,6 +19,7 @@
#include "SNTCommonEnums.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncStatus.h"
@implementation SNTCommandSyncLogUpload
@@ -45,8 +46,8 @@
[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
[reqBody appendData:
[[NSString stringWithFormat:@"Content-Disposition: form-data; "
@"name=\"files\"; "
@"filename=\"%@.gz\"\r\n", [log lastPathComponent]]
@"name=\"%@\"; "
@"filename=\"%@.gz\"\r\n", kLogUploadField, [log lastPathComponent]]
dataUsingEncoding:NSUTF8StringEncoding]];
[reqBody appendData:
[@"Content-Type: application/x-gzip\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
@@ -60,8 +61,11 @@
[[session uploadTaskWithRequest:req
fromData:reqBody
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
LOGI(@"Uploaded %d logs", [logsToUpload count]);

View File

@@ -16,6 +16,7 @@
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncStatus.h"
@implementation SNTCommandSyncPostflight
@@ -24,7 +25,7 @@
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"postflight/" stringByAppendingString:progress.machineID]
NSURL *url = [NSURL URLWithString:[kURLPostflight stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
[req setHTTPMethod:@"POST"];
@@ -32,12 +33,15 @@
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
NSURLResponse *response,
NSError *error) {
if ([(NSHTTPURLResponse *)response statusCode] != 200) {
LOGD(@"HTTP Response Code: %d", [(NSHTTPURLResponse *)response statusCode]);
handler(NO);
} else {
handler(YES);
}
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGE(@"HTTP Response: %d %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
handler(YES);
}
}] resume];
}

View File

@@ -17,6 +17,7 @@
#include "SNTKernelCommon.h"
#include "SNTLogging.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncStatus.h"
#import "SNTSystemInfo.h"
#import "SNTXPCConnection.h"
@@ -28,17 +29,16 @@
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"preflight/" stringByAppendingString:progress.machineID]
NSURL *url = [NSURL URLWithString:[kURLPreflight stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
requestDict[@"serial_no"] = [SNTSystemInfo serialNumber];
requestDict[@"hostname"] = [SNTSystemInfo shortHostname];
requestDict[@"santa_version"] =
[[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[@"os_version"] = [SNTSystemInfo osVersion];
requestDict[@"os_build"] = [SNTSystemInfo osBuild];
requestDict[@"primary_user"] = progress.machineOwner;
requestDict[kSerialNumber] = [SNTSystemInfo serialNumber];
requestDict[kHostname] = [SNTSystemInfo shortHostname];
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
requestDict[kOSVer] = [SNTSystemInfo osVersion];
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
requestDict[kPrimaryUser] = progress.machineOwner;
NSData *requestBody = [NSJSONSerialization dataWithJSONObject:requestDict
options:0
@@ -53,17 +53,20 @@
NSError *error) {
long statusCode = [(NSHTTPURLResponse *)response statusCode];
if (statusCode != 200) {
LOGD(@"HTTP Response: %@",
LOGE(@"HTTP Response: %d %@",
statusCode,
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
handler(NO);
} else {
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
progress.eventBatchSize = [r[@"batch_size"] intValue];
progress.uploadLogURL = [NSURL URLWithString:r[@"upload_logs_url"]];
progress.eventBatchSize = [r[kBatchSize] intValue];
progress.uploadLogURL = [NSURL URLWithString:r[kUploadLogsURL]];
if (r[@"client_mode"]) {
[[daemonConn remoteObjectProxy] setClientMode:[r[@"client_mode"] intValue] withReply:^{}];
if (r[kClientMode] && [r[kClientMode] isEqual:kClientModeMonitor]) {
[[daemonConn remoteObjectProxy] setClientMode:CLIENTMODE_MONITOR withReply:^{}];
} else if (r[kClientMode] && [r[kClientMode] isEqual:kClientModeLockdown]) {
[[daemonConn remoteObjectProxy] setClientMode:CLIENTMODE_LOCKDOWN withReply:^{}];
}
handler(YES);

View File

@@ -14,6 +14,7 @@
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncStatus.h"
#import "SNTRule.h"
#import "SNTXPCConnection.h"
@@ -27,7 +28,7 @@
progress:(SNTCommandSyncStatus *)progress
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSURL *url = [NSURL URLWithString:[@"ruledownload/" stringByAppendingString:progress.machineID]
NSURL *url = [NSURL URLWithString:[kURLRuleDownload stringByAppendingString:progress.machineID]
relativeToURL:progress.syncBaseURL];
[self ruleDownloadWithCursor:nil
url:url
@@ -44,7 +45,7 @@
daemonConn:(SNTXPCConnection *)daemonConn
completionHandler:(void (^)(BOOL success))handler {
NSDictionary *requestDict = (cursor ? @{ @"cursor": cursor } : @{});
NSDictionary *requestDict = (cursor ? @{ kCursor: cursor } : @{});
if (!progress.downloadedRules) {
progress.downloadedRules = [NSMutableArray array];
@@ -69,34 +70,34 @@
handler(NO);
}
NSArray *receivedRules = resp[@"rules"];
NSArray *receivedRules = resp[kRules];
for (NSDictionary *rule in receivedRules) {
if (![rule isKindOfClass:[NSDictionary class]]) continue;
SNTRule *newRule = [[SNTRule alloc] init];
newRule.shasum = rule[@"sha256"];
newRule.shasum = rule[kRuleSHA256];
if ([rule[@"policy"] isEqual:@"WHITELIST"]) {
if ([rule[kRulePolicy] isEqual:kRulePolicyWhitelist]) {
newRule.state = RULESTATE_WHITELIST;
} else if ([rule[@"policy"] isEqual:@"BLACKLIST"]) {
} else if ([rule[kRulePolicy] isEqual:kRulePolicyBlacklist]) {
newRule.state = RULESTATE_BLACKLIST;
} else if ([rule[@"policy"] isEqual:@"SILENT_BLACKLIST"]) {
} else if ([rule[kRulePolicy] isEqual:kRulePolicySilentBlacklist]) {
newRule.state = RULESTATE_SILENT_BLACKLIST;
} else if ([rule[@"policy"] isEqual:@"REMOVE"]) {
} else if ([rule[kRulePolicy] isEqual:kRulePolicyRemove]) {
newRule.state = RULESTATE_REMOVE;
} else {
continue;
}
if ([rule[@"rule_type"] isEqual:@"BINARY"]) {
if ([rule[kRuleType] isEqual:kRuleTypeBinary]) {
newRule.type = RULETYPE_BINARY;
} else if ([rule[@"rule_type"] isEqual:@"CERTIFICATE"]) {
} else if ([rule[kRuleType] isEqual:kRuleTypeCertificate]) {
newRule.type = RULETYPE_CERT;
} else {
continue;
}
NSString *customMsg = rule[@"custom_msg"];
NSString *customMsg = rule[kRuleCustomMsg];
if (customMsg) {
newRule.customMsg = customMsg;
}
@@ -104,8 +105,8 @@
[progress.downloadedRules addObject:newRule];
}
if (resp[@"cursor"]) {
[self ruleDownloadWithCursor:resp[@"cursor"]
if (resp[kCursor]) {
[self ruleDownloadWithCursor:resp[kCursor]
url:url
session:session
progress:progress

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#import "SNTApplication.h"
@@ -26,6 +27,7 @@
#import "SNTDriverManager.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
#import "SNTFileWatcher.h"
#import "SNTRuleTable.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
@@ -35,6 +37,7 @@
@property SNTDriverManager *driverManager;
@property SNTEventTable *eventTable;
@property SNTExecutionController *execController;
@property SNTFileWatcher *configFileWatcher;
@property SNTRuleTable *ruleTable;
@property SNTXPCConnection *controlConnection;
@property SNTXPCConnection *notifierConnection;
@@ -82,6 +85,15 @@
[[SNTDaemonControlController alloc] initWithDriverManager:_driverManager];
[_controlConnection resume];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
handler:^{
[[SNTConfigurator configurator] reloadConfigData];
// Ensure config file remains root:wheel 0644
chown([kDefaultConfigFilePath fileSystemRepresentation], 0, 0);
chmod([kDefaultConfigFilePath fileSystemRepresentation], 0644);
}];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
ruleTable:_ruleTable

View File

@@ -28,13 +28,13 @@ static NSString * const kEventsDatabaseName = @"events.db";
static FMDatabaseQueue *eventDatabaseQueue = nil;
static dispatch_once_t eventDatabaseToken;
dispatch_once(&eventDatabaseToken, ^{
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kEventsDatabaseName];
eventDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
#ifndef DEBUG
[eventDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
#ifndef DEBUG
[eventDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
}];
#endif
});
@@ -46,13 +46,13 @@ static NSString * const kEventsDatabaseName = @"events.db";
static FMDatabaseQueue *ruleDatabaseQueue = nil;
static dispatch_once_t ruleDatabaseToken;
dispatch_once(&ruleDatabaseToken, ^{
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
[self createDatabasePath];
NSString *fullPath = [kDatabasePath stringByAppendingPathComponent:kRulesDatabaseName];
ruleDatabaseQueue = [[FMDatabaseQueue alloc] initWithPath:fullPath];
#ifndef DEBUG
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
#ifndef DEBUG
[ruleDatabaseQueue inDatabase:^(FMDatabase *db) {
db.logsErrors = NO;
}];
#endif
});

View File

@@ -47,7 +47,6 @@
/// database exists and uses the latest schema.
- (void)updateTableSchema {
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
int currentVersion = [db userVersion];
int newVersion = [self initializeDatabase:db fromVersion:currentVersion];
if (newVersion < 1) return;

View File

@@ -55,6 +55,7 @@
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
self.mockDriverManager = OCMClassMock([SNTDriverManager class]);

View File

@@ -0,0 +1,150 @@
/// Copyright 2015 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 <XCTest/XCTest.h>
#import "SNTFileWatcher.h"
@interface SNTFileWatcherTest : XCTestCase
@property NSFileManager *fm;
@property NSString *file;
@end
@implementation SNTFileWatcherTest
static int unusedFd1 = -1;
static int unusedFd2 = -1;
+ (void)setUp {
// xctest redirects the stdout/stderr FDs when starting tests. This is not a problem, except
// xctool intercepts stdout/stderr FDs (1 & 2) to put them in nice sections of the output.
// This causes problems with tests that write to files and is 'fixed' by opening two FDs just
// to be safe. Unfortunately this means that anything printed (e.g. with printf or NSLog) will
// not actually be printed in xctool output for this test suite, ho hum.
unusedFd1 = open("/dev/null", O_WRONLY);
unusedFd2 = open("/dev/null", O_WRONLY);
}
+ (void)tearDown {
close(unusedFd1);
close(unusedFd2);
}
- (void)setUp {
[super setUp];
self.fm = [NSFileManager defaultManager];
self.file = @"/tmp/SNTFileWatcherTest_File";
[self createFile];
usleep(10000);
}
- (void)tearDown {
[self deleteFile];
usleep(10000);
[super tearDown];
}
- (void)createFile {
[self.fm createFileAtPath:self.file contents:nil attributes:nil];
}
- (void)deleteFile {
[self.fm removeItemAtPath:self.file error:NULL];
}
- (void)testPlainInit {
XCTAssertThrows([[SNTFileWatcher alloc] init]);
}
- (void)testInitFileExists {
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^{
[exp fulfill];
}];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testInitNewFile {
[self deleteFile];
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Init: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^{
[exp fulfill];
}];
[self createFile];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileChanged {
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Changed: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^{
NSString *d = [NSString stringWithContentsOfFile:self.file
encoding:NSUTF8StringEncoding
error:nil];
if ([d isEqual:@"0x8BADF00D"]) {
[exp fulfill];
}
}];
[[@"0x8BADF00D" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:NO];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileReplaced {
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Replaced: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^{
NSString *d = [NSString stringWithContentsOfFile:self.file
encoding:NSUTF8StringEncoding
error:nil];
if ([d isEqual:@"0xFACEFEED"]) {
[exp fulfill];
}
}];
[[@"0xFACEFEED" dataUsingEncoding:NSUTF8StringEncoding] writeToFile:self.file atomically:YES];
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
- (void)testFileExtended {
int fd = open(self.file.fileSystemRepresentation, O_WRONLY);
write(fd, "0xDEAD", 6);
__weak XCTestExpectation *exp = [self expectationWithDescription:@"Extended: callback called"];
__unused SNTFileWatcher *sut = [[SNTFileWatcher alloc] initWithFilePath:self.file
handler:^{
int file = open(self.file.fileSystemRepresentation, O_RDONLY);
char fileData[10];
read(file, fileData, 10);
if (strncmp(fileData, "0xDEADBEEF", 10) == 0) {
[exp fulfill];
}
}];
write(fd, "BEEF", 4);
close(fd);
[self waitForExpectationsWithTimeout:5.0 handler:nil];
}
@end