Compare commits

...

5 Commits

Author SHA1 Message Date
Tom Burgin
fc87cde668 config: use KVO (#234)
* config: atomically update config

* config: add an explanation for sleep usage

* config: use mobileconfig in the getters

* config: cleanup file watcher

* config: spell

* config: clear or reload sync state on sync base url change

* config: Use KVO and Dependent Keys

* config: remove debug log

* config: review updates

* config: update rule sync getter and setter names

* config: get logical
2018-02-07 13:59:00 -05:00
Tom Burgin
400c413029 config: add option to disable mode change notifications (#235)
* config: add option to disable mode change notifications

* config: don't do extra work

* config: handle none, default and custom

* config: cleaner
2018-02-02 12:01:51 -05:00
Tom Burgin
0e6eb45732 santa-driver: add an acknowledge feature to allow timeouts (#220)
* santa-driver: Add an acknowledge feature to allow timeouts for lost requests

* project: cocoapods 1.3.1 update

* review updates
2018-01-26 11:33:54 -05:00
Tom Burgin
7ca2028c19 santabs: don't try to lookup nil bundle paths (#233) 2018-01-26 11:33:04 -05:00
Tom Burgin
08144b54a7 docs: updated configuration details (#232)
* docs: updated configuration details

* config: add example mobileconfig
2018-01-24 21:07:48 -05:00
24 changed files with 835 additions and 430 deletions

View File

@@ -0,0 +1,83 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>com.google.santa</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>com.google.santa</string>
<key>PayloadDisplayName</key>
<string>com.google.santa</string>
<key>PayloadIdentifier</key>
<string>com.google.santa</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>9020fb2d-cab3-420f-9268-acca4868bdd0</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</plist>

View File

@@ -1,8 +1,12 @@
# Important
Santa v0.9.21 has moved to using an Apple [Configuration Profile](https://developer.apple.com/library/content/featuredarticles/iPhoneConfigurationProfileRef/Introduction/Introduction.html) to manage the local configuration. The old config file (`/var/db/santa/config.plist`) is no longer used.
# Configuration
Two configuration methods can be used to control Santa: local configuration and a sync server controlled configuration. There are certain options that can only be controlled with a local configuration and others that can only be controlled with a sync server controlled configuration. Additionally, there are options that can be controlled by both.
Two configuration methods can be used to control Santa: a local configuration profile and a sync server controlled configuration. There are certain options that can only be controlled with a local configuration profile and others that can only be controlled with a sync server controlled configuration. Additionally, there are options that can be controlled by both.
## Local Configuration
## Local Configuration Profile
| Key | Value Type | Description |
| ----------------------------- | ---------- | ---------------------------------------- |
@@ -32,7 +36,7 @@ Two configuration methods can be used to control Santa: local configuration and
| MachineIDPlist | String | The path to a plist that contains the MachineOwnerKey / value pair. |
| MachineIDKey | String | The key to use on MachineIDPlist. |
*protected keys: If a sync server is configured, this setting cannot be changed while santad is running as it is assumed the setting will be provided by the sync server.
*overridable by the sync server: run `santactl status` to check the current running config
##### EventDetailURL
@@ -50,49 +54,108 @@ This property contains a kind of format string to be turned into the URL to send
For example: `https://sync-server-hostname/%machine_id%/%file_sha%`
##### Example Config
##### Example Configuration Profile
Here is an example of a configuration that could be set.
Here is an example of a configuration profile that could be set. It was generated with Tim Sutton's great [mcxToProfile](https://github.com/timsutton/mcxToProfile) tool. A copy is also available [here](com.google.santa.example.mobileconfig).
A few key points to when creating your configuration profile:
* `com.google.santa` needs to be the key inside `PayloadContent`
* The `PayloadScope` needs to be `System`
```xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<key>PayloadContent</key>
<array>
<dict>
<key>PayloadContent</key>
<dict>
<key>com.google.santa</key>
<dict>
<key>Forced</key>
<array>
<dict>
<key>mcx_preference_settings</key>
<dict>
<key>BannedBlockMessage</key>
<string>This application has been banned</string>
<key>ClientMode</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</dict>
</array>
</dict>
</dict>
<key>PayloadEnabled</key>
<true/>
<key>PayloadIdentifier</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadType</key>
<string>com.apple.ManagedClient.preferences</string>
<key>PayloadUUID</key>
<string>0342c558-a101-4a08-a0b9-40cc00039ea5</string>
<key>PayloadVersion</key>
<integer>1</integer>
</dict>
</array>
<key>PayloadDescription</key>
<string>com.google.santa</string>
<key>PayloadDisplayName</key>
<string>com.google.santa</string>
<key>PayloadIdentifier</key>
<string>com.google.santa</string>
<key>PayloadOrganization</key>
<string></string>
<key>PayloadRemovalDisallowed</key>
<true/>
<key>PayloadScope</key>
<string>System</string>
<key>PayloadType</key>
<string>Configuration</string>
<key>PayloadUUID</key>
<string>9020fb2d-cab3-420f-9268-acca4868bdd0</string>
<key>PayloadVersion</key>
<integer>1</integer>
<key>EnablePageZeroProtection</key>
<false/>
<key>EventDetailText</key>
<string>Open sync server</string>
<key>EventDetailURL</key>
<string>https://sync-server-hostname/blockables/%file_sha%</string>
<key>FileChangesRegex</key>
<string>^/(?!(?:private/tmp|Library/(?:Caches|Managed Installs/Logs|(?:Managed )?Preferences))/)</string>
<key>MachineIDKey</key>
<string>MachineUUID</string>
<key>MachineIDPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>MachineOwnerKey</key>
<string>Owner</string>
<key>MachineOwnerPlist</key>
<string>/Library/Preferences/com.company.machine-mapping.plist</string>
<key>ModeNotificationLockdown</key>
<string>Entering Lockdown mode</string>
<key>ModeNotificationMonitor</key>
<string>Entering Monitor mode&lt;br/&gt;Please be careful!</string>
<key>MoreInfoURL</key>
<string>https://sync-server-hostname/moreinfo</string>
<key>SyncBaseURL</key>
<string>https://sync-server-hostname/api/santa/</string>
<key>UnknownBlockMessage</key>
<string>This application has been blocked from executing.</string>
</dict>
</plist>
```
Configuration profiles have a `.mobileconfig` file extension. There are many ways to install configuration profiles:
* Double click them in Finder
* Use the `/usr/bin/profiles` tool
* Use an MDM
## Sync server Provided Configuration
| Key | Value Type | Description |

View File

@@ -35,28 +35,19 @@ Running Santa in Lockdown Mode will stop all blacklisted binaries and additional
##### Changing Modes
There are two ways to change the running mode: changing the config.plist and with a sync server configuration.
There are two ways to change the running mode: changing the configuration profile and with a sync server configuration.
###### Change modes with the config.plist
###### Change modes with the configuration profile
The `ClientMode` config key is protected while santad is running and will revert any attempt to change it.
Set the `ClientMode` in your configuration profile to the integer value `1` for MONITOR or `2` for LOCKDOWN.
Change to Monitor mode:
```sh
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist
sudo defaults write /var/db/santa/config.plist ClientMode -int 1
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
```xml
<key>ClientMode</key>
<integer>1</integer>
```
Change to Lockdown mode:
Install your new configuration profile, it will overwrite any old `com.google.santa` profiles you may have already install. See the [configuration](../deployment/configuration.md) document for more details.
```sh
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist
sudo defaults write /var/db/santa/config.plist ClientMode -int 2
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
```
######Change modes with a sync server
###### Change modes with a sync server
The mode is set in the preflight sync stage. Use the key `client_mode` and a value of `MONITOR` or `LOCKDOWN`.

View File

@@ -29,4 +29,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: acd378b3727c923d912e09812da344f7375c14fe
COCOAPODS: 1.2.1
COCOAPODS: 1.3.1

View File

@@ -149,7 +149,6 @@
0DEA5F7D1CF64EB600704398 /* SNTCommandSyncRuleDownload.m in Sources */ = {isa = PBXBuildFile; fileRef = 0D0A1EC2191998C900B8450F /* SNTCommandSyncRuleDownload.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 */; };
@@ -172,6 +171,8 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */ = {isa = PBXBuildFile; fileRef = C76614EB1D142D3C00D150C1 /* SNTCommandCheckCache.m */; };
C776A1071DEE160500A56616 /* SNTCommandSyncManager.m in Sources */ = {isa = PBXBuildFile; fileRef = C776A1061DEE160500A56616 /* SNTCommandSyncManager.m */; };
C78227631E1C3C7D006EB2D6 /* santabs.xpc in CopyFiles */ = {isa = PBXBuildFile; fileRef = C78227541E1C3C58006EB2D6 /* santabs.xpc */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 0DEFB7C31ACDD80100B92AAE /* SNTFileWatcher.m */; };
C795ED901D80A5BE007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C795ED911D80B66B007CFF42 /* SNTPolicyProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = C795ED8F1D80A5BE007CFF42 /* SNTPolicyProcessor.m */; };
C79A23581E23F7E80037AFA8 /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = C79A23561E23F7E80037AFA8 /* main.m */; };
@@ -1221,13 +1222,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-santabs-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
2146C1274A8C11B3755A8A67 /* [CP] Check Pods Manifest.lock */ = {
@@ -1236,13 +1240,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santad-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
23869BA352E2C86DEFE62819 /* [CP] Copy Pods Resources */ = {
@@ -1296,13 +1303,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-LogicTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
B193461D3612BCB47C16E407 /* [CP] Check Pods Manifest.lock */ = {
@@ -1311,13 +1321,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Santa-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
BA20035148DDEF5808B2C7EF /* [CP] Embed Pods Frameworks */ = {
@@ -1358,13 +1371,16 @@
files = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-santactl-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
D49A3AB950AFD99741E9AF89 /* [CP] Embed Pods Frameworks */ = {
@@ -1500,6 +1516,7 @@
C76614EC1D142D3C00D150C1 /* SNTCommandCheckCache.m in Sources */,
0D416401191974F1006A356A /* SNTCommandSyncState.m in Sources */,
0DC5D871192160180078A5C0 /* SNTCommandSyncLogUpload.m in Sources */,
C7943FE92028B855008D4F76 /* SNTFileWatcher.m in Sources */,
0DB77FDA1CD14092004DF060 /* SNTBlockMessage.m in Sources */,
0D35BDA218FD71CE00921A21 /* main.m in Sources */,
0DCD6043190ACCB8006B445C /* SNTFileInfo.m in Sources */,
@@ -1518,6 +1535,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C7943FEA2028C4F7008D4F76 /* SNTFileWatcher.m in Sources */,
0DCA552718C95928002A7DAE /* SNTXPCConnection.m in Sources */,
0D385DF1180DE51600418BC6 /* SNTAppDelegate.m in Sources */,
0D88680A1AC48A1200B86659 /* SNTSystemInfo.m in Sources */,
@@ -1532,7 +1550,6 @@
0D1B477019A53419008CADD3 /* SNTAboutWindowController.m in Sources */,
0D668E8118D1121700E29A8B /* SNTMessageWindow.m in Sources */,
0DA73CA11934F8100056D7C4 /* SNTLogging.m in Sources */,
0DEFB7C51ACDD80100B92AAE /* SNTFileWatcher.m in Sources */,
C7479F051E53704E0054C1CF /* SNTXPCBundleServiceInterface.m in Sources */,
0D20710E1A7C4A86008B0A9A /* SNTStoredEvent.m in Sources */,
0DE2CE561CA05561002B649A /* SNTAccessibleTextField.m in Sources */,

View File

@@ -16,7 +16,6 @@
#import "SNTAboutWindowController.h"
#import "SNTConfigurator.h"
#import "SNTFileWatcher.h"
#import "SNTNotificationManager.h"
#import "SNTStrengthify.h"
#import "SNTXPCConnection.h"
@@ -24,7 +23,6 @@
@interface SNTAppDelegate ()
@property SNTAboutWindowController *aboutWindowController;
@property SNTFileWatcher *configFileWatcher;
@property SNTNotificationManager *notificationManager;
@property SNTXPCConnection *daemonListener;
@property SNTXPCConnection *bundleListener;
@@ -36,12 +34,6 @@
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
[self setupMenu];
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kMobileConfigFilePath
handler:^(unsigned long data) {
if (!(data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
}];
self.notificationManager = [[SNTNotificationManager alloc] init];
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];

View File

@@ -102,14 +102,16 @@ static NSString * const silencedNotificationsKey = @"SilencedNotifications";
case SNTClientModeMonitor:
un.informativeText = @"Switching into Monitor mode";
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
if (!customMsg) break;
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
case SNTClientModeLockdown:
un.informativeText = @"Switching into Lockdown mode";
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
if (customMsg.length) un.informativeText = customMsg;
if (!customMsg) break;
if (!customMsg.length) return;
un.informativeText = [SNTBlockMessage stringFromHTML:customMsg];
break;
default:
return;

View File

@@ -16,12 +16,10 @@
#import "SNTCommonEnums.h"
extern NSString *const kSyncStateFilePath;
extern NSString *const kMobileConfigFilePath;
///
/// Singleton that provides an interface for managing configuration values on disk
/// @note This class is designed as a singleton but that is not strictly enforced.
/// @note All properties are KVO compliant.
///
@interface SNTConfigurator : NSObject
@@ -30,16 +28,12 @@ extern NSString *const kMobileConfigFilePath;
///
/// The operating mode.
///
@property(nonatomic) SNTClientMode clientMode;
@property(readonly, nonatomic) SNTClientMode clientMode;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
/// Set the operating mode as received from a sync server.
///
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *fileChangesRegex;
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
///
/// The regex of whitelisted paths. Regexes are specified in ICU format.
@@ -48,7 +42,12 @@ extern NSString *const kMobileConfigFilePath;
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *whitelistPathRegex;
///
/// Set the regex of whitelisted paths as received from a sync server.
///
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re;
///
/// The regex of blacklisted paths. Regexes are specified in ICU format.
@@ -57,7 +56,21 @@ extern NSString *const kMobileConfigFilePath;
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
@property(readonly, nonatomic) NSRegularExpression *blacklistPathRegex;
///
/// Set the regex of blacklisted paths as received from a sync server.
///
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re;
///
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
///
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
/// pointless as a path only ever has a single line.
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
///
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
///
/// Enable __PAGEZERO protection, defaults to YES
@@ -202,13 +215,8 @@ extern NSString *const kMobileConfigFilePath;
+ (instancetype)configurator;
///
/// Re-read config data from disk.
/// Clear the sync server configuration from the effective configuration.
///
- (void)reloadConfigData;
///
/// Notify the receiver that the sync state file has changed.
///
- (void)syncStateFileChanged:(unsigned long)data;
- (void)clearSyncState;
@end

View File

@@ -16,23 +16,25 @@
#include <sys/stat.h>
#import "SNTFileWatcher.h"
#import "SNTLogging.h"
#import "SNTStrengthify.h"
#import "SNTSystemInfo.h"
@interface SNTConfigurator ()
@property NSMutableDictionary *configData;
/// Creating NSRegularExpression objects is not fast, so cache them.
@property NSRegularExpression *cachedFileChangesRegex;
@property NSRegularExpression *cachedWhitelistDirRegex;
@property NSRegularExpression *cachedBlacklistDirRegex;
/// A NSUserDefaults object set to use the com.google.santa suite.
@property(readonly, nonatomic) NSUserDefaults *defaults;
/// Keys used by a mobileconfig or sync server
@property(readonly, nonatomic) NSArray *syncServerKeys;
@property(readonly, nonatomic) NSArray *mobileConfigKeys;
// Keys and expected value types.
@property(readonly, nonatomic) NSDictionary *syncServerKeyTypes;
@property(readonly, nonatomic) NSDictionary *forcedConfigKeyTypes;
/// Holds the configurations from a sync server and mobileconfig.
@property NSMutableDictionary *syncState;
@property NSMutableDictionary *configState;
/// Watcher for the sync-state.plist.
@property(nonatomic) SNTFileWatcher *syncStateWatcher;
@end
@implementation SNTConfigurator
@@ -43,9 +45,6 @@ NSString *const kSyncStateFilePath = @"/var/db/santa/sync-state.plist";
/// The domain used by mobileconfig.
static NSString *const kMobileConfigDomain = @"com.google.santa";
/// The hard-coded path to the mobileconfig file.
NSString *const kMobileConfigFilePath = @"/Library/Managed Preferences/com.google.santa.plist";
/// The keys managed by a mobileconfig.
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
@@ -72,11 +71,12 @@ static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
// The keys managed by a sync server or mobileconfig.
static NSString *const kClientModeKey = @"ClientMode";
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
@@ -86,22 +86,46 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (instancetype)init {
self = [super init];
if (self) {
_defaults = [[NSUserDefaults alloc] initWithSuiteName:kMobileConfigDomain];
_syncServerKeys = @[
kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey, kFileChangesRegexKey,
kFullSyncLastSuccess, kRuleSyncLastSuccess, kSyncCleanRequired
];
_mobileConfigKeys = @[
kClientModeKey, kFileChangesRegexKey, kWhitelistRegexKey, kBlacklistRegexKey,
kEnablePageZeroProtectionKey, kMoreInfoURLKey, kEventDetailURLKey, kEventDetailTextKey,
kUnknownBlockMessage, kBannedBlockMessage, kModeNotificationMonitor,
kModeNotificationLockdown, kSyncBaseURLKey, kClientAuthCertificateFileKey,
kClientAuthCertificatePasswordKey, kClientAuthCertificateCNKey,
kClientAuthCertificateIssuerKey, kServerAuthRootsDataKey, kServerAuthRootsFileKey,
kMachineOwnerKey, kMachineIDKey, kMachineOwnerPlistFileKey, kMachineOwnerPlistKeyKey,
kMachineIDPlistFileKey, kMachineIDPlistKeyKey
];
[self reloadConfigData];
_syncServerKeyTypes = @{
kClientModeKey : [NSNumber class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kFullSyncLastSuccess : [NSDate class],
kRuleSyncLastSuccess : [NSDate class],
kSyncCleanRequired : [NSNumber class]
};
_forcedConfigKeyTypes = @{
kClientModeKey : [NSNumber class],
kFileChangesRegexKey : [NSRegularExpression class],
kWhitelistRegexKey : [NSRegularExpression class],
kBlacklistRegexKey : [NSRegularExpression class],
kEnablePageZeroProtectionKey : [NSNumber class],
kMoreInfoURLKey : [NSString class],
kEventDetailURLKey : [NSString class],
kEventDetailTextKey : [NSString class],
kUnknownBlockMessage : [NSString class],
kBannedBlockMessage : [NSString class],
kModeNotificationMonitor : [NSString class],
kModeNotificationLockdown : [NSString class],
kSyncBaseURLKey : [NSString class],
kClientAuthCertificateFileKey : [NSString class],
kClientAuthCertificatePasswordKey : [NSString class],
kClientAuthCertificateCNKey : [NSString class],
kClientAuthCertificateIssuerKey : [NSString class],
kServerAuthRootsDataKey : [NSData class],
kServerAuthRootsFileKey : [NSString class],
kMachineOwnerKey : [NSString class],
kMachineIDKey : [NSString class],
kMachineOwnerPlistFileKey : [NSString class],
kMachineOwnerPlistKeyKey : [NSString class],
kMachineIDPlistFileKey : [NSString class],
kMachineIDPlistKeyKey : [NSString class],
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
_configState = [self readForcedConfig];
_syncState = [self readSyncStateFromDisk] ?: [NSMutableDictionary dictionary];
[self startWatchingDefaults];
}
return self;
}
@@ -117,277 +141,301 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return sharedConfigurator;
}
#pragma mark KVO Dependencies
+ (NSSet *)keyPathsForValuesAffectingClientMode {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingWhitelistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingBlacklistPathRegex {
return [NSSet setWithObjects:@"syncState", @"configState", nil];
}
+ (NSSet *)keyPathsForValuesAffectingFileChangesRegex {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncBaseURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEnablePageZeroProtection {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailURL {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingEventDetailText {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingBannedBlockMessage {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationMonitor {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingModeNotificationLockdown {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateFile {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificatePassword {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateCn {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncClientAuthCertificateIssuer {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsData {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingSyncServerAuthRootsFile {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingFullSyncLastSuccess {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMachineOwner {
return [NSSet setWithObject:@"configState"];
}
+ (NSSet *)keyPathsForValuesAffectingMachineID {
return [NSSet setWithObject:@"configState"];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
NSInteger cm = SNTClientModeUnknown;
id mode = self.configData[kClientModeKey];
if ([mode respondsToSelector:@selector(longLongValue)]) {
cm = (NSInteger)[mode longLongValue];
}
SNTClientMode cm = [self.syncState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return (SNTClientMode)cm;
} else {
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
self.clientMode = SNTClientModeMonitor;
return SNTClientModeMonitor;
return cm;
}
cm = [self.configState[kClientModeKey] longLongValue];
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
return cm;
}
return SNTClientModeMonitor;
}
- (void)setClientMode:(SNTClientMode)newMode {
- (void)setSyncServerClientMode:(SNTClientMode)newMode {
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
self.configData[kClientModeKey] = @(newMode);
[self saveSyncStateToDisk];
[self updateSyncStateForKey:kClientModeKey value:@(newMode)];
} else {
LOGW(@"Ignoring request to change client mode to %ld", newMode);
}
}
- (NSRegularExpression *)whitelistPathRegex {
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
NSString *re = self.configData[kWhitelistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedWhitelistDirRegex;
return self.syncState[kWhitelistRegexKey] ?: self.configState[kWhitelistRegexKey];
}
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kWhitelistRegexKey];
} else {
self.configData[kWhitelistRegexKey] = [re pattern];
}
self.cachedWhitelistDirRegex = nil;
[self saveSyncStateToDisk];
- (void)setSyncServerWhitelistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kWhitelistRegexKey value:re];
}
- (NSRegularExpression *)blacklistPathRegex {
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
NSString *re = self.configData[kBlacklistRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedBlacklistDirRegex;
return self.syncState[kBlacklistRegexKey] ?: self.configState[kBlacklistRegexKey];
}
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kBlacklistRegexKey];
} else {
self.configData[kBlacklistRegexKey] = [re pattern];
}
self.cachedBlacklistDirRegex = nil;
[self saveSyncStateToDisk];
- (void)setSyncServerBlacklistPathRegex:(NSRegularExpression *)re {
[self updateSyncStateForKey:kBlacklistRegexKey value:re];
}
- (NSRegularExpression *)fileChangesRegex {
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
NSString *re = self.configData[kFileChangesRegexKey];
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
options:0
error:NULL];
}
return self.cachedFileChangesRegex;
}
- (void)setFileChangesRegex:(NSRegularExpression *)re {
if (!re) {
[self.configData removeObjectForKey:kFileChangesRegexKey];
} else {
self.configData[kFileChangesRegexKey] = [re pattern];
}
self.cachedFileChangesRegex = nil;
[self saveSyncStateToDisk];
}
- (BOOL)enablePageZeroProtection {
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
return keyValue ? [keyValue boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
}
- (NSString *)eventDetailURL {
return self.configData[kEventDetailURLKey];
}
- (NSString *)eventDetailText {
return self.configData[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configData[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configData[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configData[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configData[kModeNotificationLockdown];
return self.configState[kFileChangesRegexKey];
}
- (NSURL *)syncBaseURL {
NSString *urlStr = self.configData[kSyncBaseURLKey];
if (urlStr) {
NSURL *url = [NSURL URLWithString:urlStr];
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
return nil;
NSString *urlString = self.configState[kSyncBaseURLKey];
NSURL *url = [NSURL URLWithString:urlString];
if (urlString && !url) LOGW(@"SyncBaseURL is not a valid URL!");
return url;
}
- (BOOL)enablePageZeroProtection {
NSNumber *number = self.configState[kEnablePageZeroProtectionKey];
return number ? [number boolValue] : YES;
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}
- (NSString *)eventDetailURL {
return self.configState[kEventDetailURLKey];
}
- (NSString *)eventDetailText {
return self.configState[kEventDetailTextKey];
}
- (NSString *)unknownBlockMessage {
return self.configState[kUnknownBlockMessage];
}
- (NSString *)bannedBlockMessage {
return self.configState[kBannedBlockMessage];
}
- (NSString *)modeNotificationMonitor {
return self.configState[kModeNotificationMonitor];
}
- (NSString *)modeNotificationLockdown {
return self.configState[kModeNotificationLockdown];
}
- (NSString *)syncClientAuthCertificateFile {
return self.configData[kClientAuthCertificateFileKey];
return self.configState[kClientAuthCertificateFileKey];
}
- (NSString *)syncClientAuthCertificatePassword {
return self.configData[kClientAuthCertificatePasswordKey];
return self.configState[kClientAuthCertificatePasswordKey];
}
- (NSString *)syncClientAuthCertificateCn {
return self.configData[kClientAuthCertificateCNKey];
return self.configState[kClientAuthCertificateCNKey];
}
- (NSString *)syncClientAuthCertificateIssuer {
return self.configData[kClientAuthCertificateIssuerKey];
return self.configState[kClientAuthCertificateIssuerKey];
}
- (NSData *)syncServerAuthRootsData {
return self.configData[kServerAuthRootsDataKey];
return self.configState[kServerAuthRootsDataKey];
}
- (NSString *)syncServerAuthRootsFile {
return self.configData[kServerAuthRootsFileKey];
return self.configState[kServerAuthRootsFileKey];
}
- (NSDate *)fullSyncLastSuccess {
return self.configData[kFullSyncLastSuccess];
return self.syncState[kFullSyncLastSuccess];
}
- (void)setFullSyncLastSuccess:(NSDate *)fullSyncLastSuccess {
self.configData[kFullSyncLastSuccess] = fullSyncLastSuccess;
[self saveSyncStateToDisk];
self.syncState[kFullSyncLastSuccess] = fullSyncLastSuccess;
self.ruleSyncLastSuccess = fullSyncLastSuccess;
}
- (NSDate *)ruleSyncLastSuccess {
return self.configData[kRuleSyncLastSuccess];
return self.syncState[kRuleSyncLastSuccess];
}
- (void)setRuleSyncLastSuccess:(NSDate *)ruleSyncLastSuccess {
self.configData[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
self.syncState[kRuleSyncLastSuccess] = ruleSyncLastSuccess;
[self saveSyncStateToDisk];
}
- (BOOL)syncCleanRequired {
return [self.configData[kSyncCleanRequired] boolValue];
return [self.syncState[kSyncCleanRequired] boolValue];
}
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
self.syncState[kSyncCleanRequired] = @(syncCleanRequired);
[self saveSyncStateToDisk];
}
- (NSString *)machineOwner {
NSString *machineOwner;
NSString *machineOwner = self.configState[kMachineOwnerKey];
if (machineOwner) return machineOwner;
if (self.configData[kMachineOwnerPlistFileKey] && self.configData[kMachineOwnerPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineOwnerPlistFileKey]];
machineOwner = plist[self.configData[kMachineOwnerPlistKeyKey]];
NSString *plistPath = self.configState[kMachineOwnerPlistFileKey];
NSString *plistKey = self.configState[kMachineOwnerPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineOwner = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
}
if (self.configData[kMachineOwnerKey]) {
machineOwner = self.configData[kMachineOwnerKey];
}
if (!machineOwner) machineOwner = @"";
return machineOwner;
return machineOwner ?: @"";
}
- (NSString *)machineID {
NSString *machineId;
NSString *machineId = self.configState[kMachineIDKey];
if (machineId) return machineId;
if (self.configData[kMachineIDPlistFileKey] && self.configData[kMachineIDPlistKeyKey]) {
NSDictionary *plist =
[NSDictionary dictionaryWithContentsOfFile:self.configData[kMachineIDPlistFileKey]];
machineId = plist[self.configData[kMachineIDPlistKeyKey]];
NSString *plistPath = self.configState[kMachineIDPlistFileKey];
NSString *plistKey = self.configState[kMachineIDPlistKeyKey];
if (plistPath && plistKey) {
NSDictionary *plist = [NSDictionary dictionaryWithContentsOfFile:plistPath];
machineId = [plist[plistKey] isKindOfClass:[NSString class]] ? plist[plistKey] : nil;
}
if (self.configData[kMachineIDKey]) {
machineId = self.configData[kMachineIDKey];
}
if ([machineId length] == 0) {
machineId = [SNTSystemInfo hardwareUUID];
}
return machineId;
}
- (void)reloadConfigData {
// Load the mobileconfig
self.configData = [self mobileConfig];
if (!self.configData[kClientModeKey]) {
// Default to Monitor if the config is missing or invalid
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
}
// Nothing else to do if a sync server is not involved
if (!self.configData[kSyncBaseURLKey]) return;
// Load the last known sync state
if (![[NSFileManager defaultManager] fileExistsAtPath:kSyncStateFilePath]) return;
NSMutableDictionary *syncState = [self syncState];
if (!self) return;
// Overwrite or add the sync state to the running config
for (NSString *key in [self syncServerKeys]) {
self.configData[key] = syncState[key];
}
[self saveSyncStateToDisk];
return machineId.length ? machineId : [SNTSystemInfo hardwareUUID];
}
#pragma mark Private
- (NSMutableDictionary *)syncState {
NSError *error;
NSData *readData = [NSData dataWithContentsOfFile:kSyncStateFilePath
options:NSDataReadingMappedIfSafe
error:&error];
if (!readData) {
LOGE(@"Could not read sync state file: %@, replacing.", [error localizedDescription]);
[self saveSyncStateToDisk];
return nil;
}
///
/// Update the syncState. Triggers a KVO event for all dependents.
///
- (BOOL)updateSyncStateForKey:(NSString *)key value:(id)value {
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[key] = value;
self.syncState = syncState;
[self saveSyncStateToDisk];
return YES;
}
///
/// Read the saved syncState.
///
- (NSMutableDictionary *)readSyncStateFromDisk {
// Only read the sync state if a sync server is configured.
if (!self.syncBaseURL) return nil;
// Only santad should read this file.
if (geteuid() != 0) return nil;
NSMutableDictionary *syncState =
[NSPropertyListSerialization propertyListWithData:readData
options:NSPropertyListMutableContainers
format:NULL
error:&error];
if (!syncState) {
LOGE(@"Could not parse sync state file: %@, replacing.", [error localizedDescription]);
[self saveSyncStateToDisk];
return nil;
[NSMutableDictionary dictionaryWithContentsOfFile:kSyncStateFilePath];
for (NSString *key in syncState.allKeys) {
if (self.syncServerKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [syncState[key] isKindOfClass:[NSString class]] ? syncState[key] : nil;
syncState[key] = [self expressionForPattern:pattern];
} else if (![syncState[key] isKindOfClass:self.syncServerKeyTypes[key]]) {
syncState[key] = nil;
continue;
}
}
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
WEAKIFY(self);
self.syncStateWatcher = [[SNTFileWatcher alloc] initWithFilePath:kSyncStateFilePath
handler:^(unsigned long data) {
STRONGIFY(self);
[self syncStateFileChanged:data];
}];
});
return syncState;
}
@@ -395,13 +443,14 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
/// Saves the current effective syncState to disk.
///
- (void)saveSyncStateToDisk {
// Only save the sync state if a sync server is configured.
if (!self.syncBaseURL) return;
// Only santad should write to this file.
if (geteuid() != 0) return;
NSMutableDictionary *syncState =
[NSMutableDictionary dictionaryWithCapacity:[self syncServerKeys].count];
for (NSString *key in [self syncServerKeys]) {
syncState[key] = self.configData[key];
}
// Either remove
NSMutableDictionary *syncState = self.syncState.mutableCopy;
syncState[kWhitelistRegexKey] = [syncState[kWhitelistRegexKey] pattern];
syncState[kBlacklistRegexKey] = [syncState[kBlacklistRegexKey] pattern];
[syncState writeToFile:kSyncStateFilePath atomically:YES];
[[NSFileManager defaultManager] setAttributes:@{ NSFilePosixPermissions : @0644 }
ofItemAtPath:kSyncStateFilePath error:NULL];
@@ -419,19 +468,18 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
int mask = S_IRWXU | S_IRWXG | S_IRWXO;
int desired = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
if (fileStat.st_uid != 0 || fileStat.st_gid != 0 || (fileStat.st_mode & mask) != desired) {
LOGD(@"Sync state file permissions changed, fixing.");
LOGI(@"Sync state file permissions changed, fixing.");
chown(cPath, 0, 0);
chmod(cPath, desired);
}
} else {
NSDictionary *syncState = [self syncState];
for (NSString *key in self.syncServerKeys) {
if (((self.configData[key] && !syncState[key]) ||
(!self.configData[key] && syncState[key]) ||
(self.configData[key] && ![self.configData[key] isEqualTo:syncState[key]]))) {
NSDictionary *newSyncState = [self readSyncStateFromDisk];
for (NSString *key in self.syncState) {
if (((self.syncState[key] && !newSyncState[key]) ||
(!self.syncState[key] && newSyncState[key]) ||
(self.syncState[key] && ![self.syncState[key] isEqualTo:newSyncState[key]]))) {
// Ignore sync url and dates
if ([key isEqualToString:kSyncBaseURLKey] ||
[key isEqualToString:kRuleSyncLastSuccess] ||
if ([key isEqualToString:kRuleSyncLastSuccess] ||
[key isEqualToString:kFullSyncLastSuccess]) continue;
LOGE(@"Sync state file changed, replacing");
[self saveSyncStateToDisk];
@@ -441,25 +489,57 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
}
}
///
/// Returns a config provided by a com.google.santa mobileconfig or an empty
/// NSMutableDictionary object if no config applies.
///
- (NSMutableDictionary *)mobileConfig {
NSMutableDictionary *config =
[NSMutableDictionary dictionaryWithCapacity:[self mobileConfigKeys].count];
for (NSString *key in [self mobileConfigKeys]) {
if ([self.defaults objectIsForcedForKey:key]) {
config[key] = [self.defaults objectForKey:key];
- (void)clearSyncState {
self.syncState = [NSMutableDictionary dictionary];
}
#pragma mark Private Defaults Methods
- (NSRegularExpression *)expressionForPattern:(NSString *)pattern {
if (!pattern) return nil;
if (![pattern hasPrefix:@"^"]) pattern = [@"^" stringByAppendingString:pattern];
return [NSRegularExpression regularExpressionWithPattern:pattern options:0 error:NULL];
}
- (NSMutableDictionary *)readForcedConfig {
NSMutableDictionary *forcedConfig = [NSMutableDictionary dictionary];
for (NSString *key in self.forcedConfigKeyTypes) {
id obj = [self forcedConfigValueForKey:key];
forcedConfig[key] = [obj isKindOfClass:self.forcedConfigKeyTypes[key]] ? obj : nil;
// Create the regex objects now
if (self.forcedConfigKeyTypes[key] == [NSRegularExpression class]) {
NSString *pattern = [obj isKindOfClass:[NSString class]] ? obj : nil;
forcedConfig[key] = [self expressionForPattern:pattern];
}
}
if (config[kSyncBaseURLKey]) {
for (NSString *key in [self syncServerKeys]) {
if ([key isEqualToString:kSyncBaseURLKey]) continue;
[config removeObjectForKey:key];
}
}
return config;
return forcedConfig;
}
- (id)forcedConfigValueForKey:(NSString *)key {
id obj = [self.defaults objectForKey:key];
return [self.defaults objectIsForcedForKey:key inDomain:kMobileConfigDomain] ? obj : nil;
}
- (void)startWatchingDefaults {
// Only santad should listen.
if (geteuid() != 0) return;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(defaultsChanged:)
name:NSUserDefaultsDidChangeNotification
object:nil];
}
- (void)defaultsChanged:(void *)v {
SEL handleChange = @selector(handleChange);
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:handleChange object:nil];
[self performSelector:handleChange withObject:nil afterDelay:5.0f];
}
///
/// Update the configState. Triggers a KVO event for all dependents.
///
- (void)handleChange {
self.configState = [self readForcedConfig];
}
@end

View File

@@ -37,6 +37,7 @@ enum SantaDriverMethods {
kSantaUserClientOpen,
kSantaUserClientAllowBinary,
kSantaUserClientDenyBinary,
kSantaUserClientAcknowledgeBinary,
kSantaUserClientClearCache,
kSantaUserClientCacheCount,
kSantaUserClientCheckCache,
@@ -64,7 +65,8 @@ typedef enum {
ACTION_RESPOND_ALLOW = 20,
ACTION_RESPOND_DENY = 21,
ACTION_RESPOND_TOOLONG = 22,
ACTION_RESPOND_ACK = 23,
// NOTIFY
ACTION_NOTIFY_EXEC = 30,
ACTION_NOTIFY_WRITE = 31,

View File

@@ -75,8 +75,11 @@
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
- (void)xsrfToken:(void (^)(NSString *))reply;
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply;
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
- (void)syncCleanRequired:(void (^)(BOOL))reply;
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;

View File

@@ -233,10 +233,15 @@ void SantaDecisionManager::AddToCache(
case ACTION_REQUEST_BINARY:
decision_cache->set(identifier, val, 0);
break;
case ACTION_RESPOND_ACK:
decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
break;
case ACTION_RESPOND_ALLOW:
case ACTION_RESPOND_DENY:
decision_cache->set(
identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56));
// TODO(bur): Avoid calling set() twice, finding and locking buckets is fast, but not free.
if (decision_cache->set(identifier, val, ((uint64_t)ACTION_REQUEST_BINARY << 56))) {
decision_cache->set(identifier, val, ((uint64_t)ACTION_RESPOND_ACK << 56));
}
break;
default:
break;
@@ -316,10 +321,16 @@ santa_action_t SantaDecisionManager::GetFromDaemon(
return ACTION_ERROR;
}
// Check the cache every kRequestLoopSleepMilliseconds. Break this loop and send the request
// again if kRequestCacheChecks is reached. Don't break the loop if the daemon is working on the
// request, indicated with ACTION_RESPOND_ACK.
auto cache_check_count = 0;
do {
msleep((void *)message->vnode_id, NULL, 0, "", &ts_);
return_action = GetFromCache(identifier);
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
} while (ClientConnected() &&
((return_action == ACTION_REQUEST_BINARY && ++cache_check_count < kRequestCacheChecks)
|| (return_action == ACTION_RESPOND_ACK)));
} while (!RESPONSE_VALID(return_action) && ClientConnected());
// If response is still not valid, the daemon exited
@@ -354,7 +365,7 @@ santa_action_t SantaDecisionManager::FetchDecision(
// If item is not in cache, break out of loop to send request to daemon.
if (RESPONSE_VALID(return_action)) {
return return_action;
} else if (return_action == ACTION_REQUEST_BINARY) {
} else if (return_action == ACTION_REQUEST_BINARY || return_action == ACTION_RESPOND_ACK) {
// This thread will now sleep for kRequestLoopSleepMilliseconds (1s) or
// until AddToCache is called, indicating a response has arrived.
msleep((void *)vnode_id, NULL, 0, "", &ts_);

View File

@@ -144,6 +144,12 @@ class SantaDecisionManager : public OSObject {
*/
static const uint32_t kRequestLoopSleepMilliseconds = 1000;
/**
While waiting for a response from the daemon, this is the maximum number cache checks before
re-sending the request.
*/
static const uint32_t kRequestCacheChecks = 5;
/**
The maximum number of milliseconds a cached deny message should be
considered valid.

View File

@@ -152,6 +152,18 @@ IOReturn SantaDriverClient::deny_binary(
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
if (!me) return kIOReturnBadArgument;
const uint64_t vnode_id = static_cast<const uint64_t>(arguments->scalarInput[0]);
if (!vnode_id) return kIOReturnInvalid;
me->decisionManager->AddToCache(vnode_id, ACTION_RESPOND_ACK, 0);
return kIOReturnSuccess;
}
IOReturn SantaDriverClient::clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments) {
SantaDriverClient *me = OSDynamicCast(SantaDriverClient, target);
@@ -199,6 +211,7 @@ IOReturn SantaDriverClient::externalMethod(
{ &SantaDriverClient::open, 0, 0, 0, 0 },
{ &SantaDriverClient::allow_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::deny_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::acknowledge_binary, 1, 0, 0, 0 },
{ &SantaDriverClient::clear_cache, 1, 0, 0, 0 },
{ &SantaDriverClient::cache_count, 0, 0, 2, 0 },
{ &SantaDriverClient::check_cache, 1, 0, 1, 0 }

View File

@@ -87,6 +87,11 @@ class com_google_SantaDriverClient : public IOUserClient {
static IOReturn deny_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to acknowledge a binary request. This is used for large binaries that
/// may take a while to reach a decision.
static IOReturn acknowledge_binary(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);
/// The daemon calls this to empty the cache.
static IOReturn clear_cache(
OSObject *target, void *reference, IOExternalMethodArguments *arguments);

View File

@@ -98,11 +98,20 @@
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
dispatch_async(self.queue, ^{
// Use the highest bundle we can find. Save and reuse the bundle infomation when creating
// the related binary events.
// Use the highest bundle we can find.
SNTFileInfo *b = [[SNTFileInfo alloc] initWithPath:event.fileBundlePath];
b.useAncestorBundle = YES;
event.fileBundlePath = b.bundlePath;
// If path to the bundle is unavailable, stop. SantaGUI will revert to
// using the offending blockable.
if (!event.fileBundlePath) {
reply(nil, nil, 0);
dispatch_semaphore_signal(sema);
return;
}
// Reuse the bundle infomation when creating the related binary events.
event.fileBundleID = b.bundleIdentifier;
event.fileBundleName = b.bundleName;
event.fileBundleVersion = b.bundleVersion;

View File

@@ -103,15 +103,26 @@ REGISTER_COMMAND_NAME(@"status")
}];
// Sync status
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] fullSyncLastSuccess];
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
NSDate *lastRuleSyncSuccess = [[SNTConfigurator configurator] ruleSyncLastSuccess];
NSString *lastRuleSyncSuccessStr =
[dateFormatter stringFromDate:lastRuleSyncSuccess] ?: lastSyncSuccessStr;
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
__block NSDate *fullSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] fullSyncLastSuccess:^(NSDate *date) {
fullSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block NSDate *ruleSyncLastSuccess;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] ruleSyncLastSuccess:^(NSDate *date) {
ruleSyncLastSuccess = date;
dispatch_group_leave(group);
}];
__block BOOL syncCleanReqd = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] syncCleanRequired:^(BOOL clean) {
syncCleanReqd = clean;
dispatch_group_leave(group);
}];
__block BOOL pushNotifications = NO;
if ([[SNTConfigurator configurator] syncBaseURL]) {
@@ -136,6 +147,15 @@ REGISTER_COMMAND_NAME(@"status")
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
}
// Format dates
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
NSString *fullSyncLastSuccessStr = [dateFormatter stringFromDate:fullSyncLastSuccess] ?: @"Never";
NSString *ruleSyncLastSuccessStr =
[dateFormatter stringFromDate:ruleSyncLastSuccess] ?: fullSyncLastSuccessStr;
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
if ([arguments containsObject:@"--json"]) {
NSDictionary *stats = @{
@"daemon" : @{
@@ -158,8 +178,8 @@ REGISTER_COMMAND_NAME(@"status")
@"sync" : @{
@"server" : syncURLStr ?: @"null",
@"clean_required" : @(syncCleanReqd),
@"last_successful_full" : lastSyncSuccessStr ?: @"null",
@"last_successful_rule" : lastRuleSyncSuccessStr ?: @"null",
@"last_successful_full" : fullSyncLastSuccessStr ?: @"null",
@"last_successful_rule" : ruleSyncLastSuccessStr ?: @"null",
@"push_notifications" : pushNotifications ? @"Connected" : @"Disconnected",
@"bundle_scanning" : @(bundlesEnabled)
},
@@ -187,8 +207,8 @@ REGISTER_COMMAND_NAME(@"status")
printf(">>> Sync Info\n");
printf(" %-25s | %s\n", "Sync Server", [syncURLStr UTF8String]);
printf(" %-25s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
printf(" %-25s | %s\n", "Last Successful Full Sync", [lastSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Rule Sync", [lastRuleSyncSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Full Sync", [fullSyncLastSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Last Successful Rule Sync", [ruleSyncLastSuccessStr UTF8String]);
printf(" %-25s | %s\n", "Push Notifications",
(pushNotifications ? "Connected" : "Disconnected"));
printf(" %-25s | %s\n", "Bundle Scanning", (bundlesEnabled ? "Yes" : "No"));

View File

@@ -63,7 +63,7 @@
// Update last sync success
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:replyBlock];
[[self.daemonConn remoteObjectProxy] setFullSyncLastSuccess:[NSDate date] reply:replyBlock];
// Wait for dispatch group
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));

View File

@@ -60,11 +60,17 @@
dispatch_group_leave(group);
}];
__block BOOL syncClean = NO;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] syncCleanRequired:^(BOOL clean) {
syncClean = clean;
dispatch_group_leave(group);
}];
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC));
// If user requested it or we've never had a successful sync, try from a clean slate.
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] ||
[[SNTConfigurator configurator] syncCleanRequired]) {
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--clean"] || syncClean) {
LOGD(@"Clean sync requested by user");
requestDict[kRequestCleanSync] = @YES;
}

View File

@@ -16,9 +16,6 @@
@import DiskArbitration;
#include <sys/stat.h>
#include <sys/types.h>
#import "SNTCommonEnums.h"
#import "SNTConfigurator.h"
#import "SNTDaemonControlController.h"
@@ -28,22 +25,22 @@
#import "SNTEventLog.h"
#import "SNTEventTable.h"
#import "SNTExecutionController.h"
#import "SNTFileWatcher.h"
#import "SNTLogging.h"
#import "SNTNotificationQueue.h"
#import "SNTRuleTable.h"
#import "SNTSyncdQueue.h"
#import "SNTXPCConnection.h"
#import "SNTXPCControlInterface.h"
#import "SNTXPCNotifierInterface.h"
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@property SNTFileWatcher *configFileWatcher;
@property SNTFileWatcher *syncStateWatcher;
@property SNTXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@end
@implementation SNTApplication
@@ -73,18 +70,37 @@
_eventLog = [[SNTEventLog alloc] init];
SNTNotificationQueue *notQueue = [[SNTNotificationQueue alloc] init];
self.notQueue = [[SNTNotificationQueue alloc] init];
SNTSyncdQueue *syncdQueue = [[SNTSyncdQueue alloc] init];
// Restart santactl if it goes down
syncdQueue.invalidationHandler = ^{
[self startSyncd];
};
// Listen for actionable config changes.
NSKeyValueObservingOptions bits = (NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld);
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"clientMode"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"syncBaseURL"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"whitelistPathRegex"
options:bits
context:NULL];
[[SNTConfigurator configurator] addObserver:self
forKeyPath:@"blacklistPathRegex"
options:bits
context:NULL];
// Establish XPC listener for Santa and santactl connections
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithDriverManager:_driverManager
notificationQueue:notQueue
notificationQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
@@ -94,43 +110,11 @@
_controlConnection.exportedObject = dc;
[_controlConnection resume];
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
__block NSURL *origSyncURL = [[SNTConfigurator configurator] syncBaseURL];
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kMobileConfigFilePath
handler:^(unsigned long data) {
LOGD(@"Mobileconfig changed, reloading.");
[[SNTConfigurator configurator] reloadConfigData];
// Flush cache if client just went into lockdown.
SNTClientMode newMode = [[SNTConfigurator configurator] clientMode];
if (origMode != newMode) {
origMode = newMode;
if (newMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
}
// 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];
}
}];
_syncStateWatcher = [[SNTFileWatcher alloc] initWithFilePath:kSyncStateFilePath
handler:^(unsigned long data) {
[[SNTConfigurator configurator] syncStateFileChanged:data];
}];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithDriverManager:_driverManager
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:notQueue
notifierQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
// Start up santactl as a daemon if a sync server exists.
@@ -142,18 +126,6 @@
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.");
@@ -270,4 +242,76 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
[app.driverManager flushCacheNonRootOnly:YES];
}
- (void)startSyncd {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self stopSyncd];
self.syncdPID = fork();
if (self.syncdPID == -1) {
LOGI(@"Failed to fork");
self.syncdPID = 0;
} else if (self.syncdPID == 0) {
// Ensure we have no privileges
if (!DropRootPrivileges()) {
_exit(EPERM);
}
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--daemon", "--syslog", NULL));
}
LOGI(@"santactl started with pid: %i", self.syncdPID);
}
- (void)stopSyncd {
if (!self.syncdPID) return;
int ret = kill(self.syncdPID, SIGKILL);
LOGD(@"kill(%i, 9) = %i", self.syncdPID, ret);
self.syncdPID = 0;
}
- (void)observeValueForKeyPath:(NSString *)keyPath
ofObject:(id)object
change:(NSDictionary<NSString *,id> *)change
context:(void *)context {
if ([keyPath isEqualToString:@"clientMode"]) {
SNTClientMode new =
[change[@"new"] isKindOfClass:[NSNumber class]] ? [change[@"new"] longLongValue] : 0;
SNTClientMode old =
[change[@"old"] isKindOfClass:[NSNumber class]] ? [change[@"old"] longLongValue] : 0;
if (new != old) [self clientModeDidChange:new];
} else if ([keyPath isEqualToString:@"syncBaseURL"]) {
NSURL *new = [change[@"new"] isKindOfClass:[NSURL class]] ? change[@"new"] : nil;
NSURL *old = [change[@"old"] isKindOfClass:[NSURL class]] ? change[@"old"] : nil;
if (!new && !old) return;
if (![new.absoluteString isEqualToString:old.absoluteString]) [self syncBaseURLDidChange:new];
} else if ([keyPath isEqualToString:@"whitelistPathRegex"] ||
[keyPath isEqualToString:@"blacklistPathRegex"]) {
NSRegularExpression *new =
[change[@"new"] isKindOfClass:[NSRegularExpression class]] ? change[@"new"] : nil;
NSRegularExpression *old =
[change[@"old"] isKindOfClass:[NSRegularExpression class]] ? change[@"old"] : nil;
if (!new && !old) return;
if (![new.pattern isEqualToString:old.pattern]) {
LOGI(@"Changed [white|black]list regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
}
}
}
- (void)clientModeDidChange:(SNTClientMode)clientMode {
if (clientMode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:clientMode];
}
- (void)syncBaseURLDidChange:(NSURL *)syncBaseURL {
if (syncBaseURL) {
LOGI(@"Starting santactl with new SyncBaseURL: %@", syncBaseURL);
[self startSyncd];
} else {
LOGI(@"SyncBaseURL removed, killing santactl pid: %i", self.syncdPID);
[self stopSyncd];
[[SNTConfigurator configurator] clearSyncState];
}
}
@end

View File

@@ -146,15 +146,7 @@ double watchdogRAMPeak = 0;
}
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
if ([[SNTConfigurator configurator] clientMode] != mode) {
// Flush cache if client just went into lockdown.
if (mode == SNTClientModeLockdown) {
LOGI(@"Changed client mode, flushing cache.");
[self.driverManager flushCacheNonRootOnly:NO];
}
[[SNTConfigurator configurator] setClientMode:mode];
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:mode];
}
[[SNTConfigurator configurator] setSyncServerClientMode:mode];
reply();
}
@@ -167,16 +159,28 @@ double watchdogRAMPeak = 0;
reply();
}
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
- (void)fullSyncLastSuccess:(void (^)(NSDate *))reply {
reply([[SNTConfigurator configurator] fullSyncLastSuccess]);
}
- (void)setFullSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setFullSyncLastSuccess:date];
reply();
}
- (void)ruleSyncLastSuccess:(void (^)(NSDate *))reply {
reply([[SNTConfigurator configurator] ruleSyncLastSuccess]);
}
- (void)setRuleSyncLastSuccess:(NSDate *)date reply:(void (^)())reply {
[[SNTConfigurator configurator] setRuleSyncLastSuccess:date];
reply();
}
- (void)syncCleanRequired:(void (^)(BOOL))reply {
reply([[SNTConfigurator configurator] syncCleanRequired]);
}
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply {
[[SNTConfigurator configurator] setSyncCleanRequired:cleanReqd];
reply();
@@ -186,9 +190,7 @@ double watchdogRAMPeak = 0;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setWhitelistPathRegex:re];
LOGI(@"Received new whitelist regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[[SNTConfigurator configurator] setSyncServerWhitelistPathRegex:re];
reply();
}
@@ -196,9 +198,7 @@ double watchdogRAMPeak = 0;
NSRegularExpression *re = [NSRegularExpression regularExpressionWithPattern:pattern
options:0
error:NULL];
[[SNTConfigurator configurator] setBlacklistPathRegex:re];
LOGI(@"Received new blacklist regex, flushing cache");
[self.driverManager flushCacheNonRootOnly:NO];
[[SNTConfigurator configurator] setSyncServerBlacklistPathRegex:re];
reply();
}

View File

@@ -162,6 +162,13 @@ static const int MAX_DELAY = 15;
1,
0,
0);
case ACTION_RESPOND_ACK:
return IOConnectCallScalarMethod(_connection,
kSantaUserClientAcknowledgeBinary,
&vnodeId,
1,
0,
0);
default:
return KERN_INVALID_ARGUMENT;
}

View File

@@ -38,6 +38,12 @@
#import "SNTStoredEvent.h"
#import "SNTSyncdQueue.h"
// A binary is considered large at ~30MB. Large binaries take longer to hash and consequently
// longer to post a decision back to santa-driver. When a binary is considered large santad will
// let santa-driver know it has received its request and is working on a decision. This allows
// santa-driver to relax; it does not have to worry about resending the request due to a timeout.
static size_t kLargeBinarySize = 30 * 1024 * 1024;
@interface SNTExecutionController ()
@property SNTDriverManager *driverManager;
@property SNTEventLog *eventLog;
@@ -102,6 +108,13 @@
return;
}
// If the binary is large let santa-driver know we received the request and we are working on it.
if (binInfo.fileSize > kLargeBinarySize) {
LOGD(@"%@ is larger than %zu. Letting santa-driver know we are working on it.",
binInfo.path, kLargeBinarySize);
[_driverManager postToKernelAction:ACTION_RESPOND_ACK forVnodeID:message.vnode_id];
}
// Get codesigning info about the file.
NSError *csError;
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path

View File

@@ -99,6 +99,9 @@
IOConnectCallScalarMethod(self.connection, kSantaUserClientAllowBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_DENY) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientDenyBinary, &vnodeid, 1, 0, 0);
} else if (action == ACTION_RESPOND_ACK) {
IOConnectCallScalarMethod(self.connection, kSantaUserClientAcknowledgeBinary,
&vnodeid, 1, 0, 0);
}
}
@@ -239,6 +242,18 @@
} else if (strncmp("/bin/cat", vdata.path, strlen("/bin/cat")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
self.timesSeenCat++;
} else if (strncmp("/usr/bin/cal", vdata.path, strlen("/usr/bin/cal")) == 0) {
static int count = 0;
if (count++) TFAILINFO("Large binary should not re-request");
[self postToKernelAction:ACTION_RESPOND_ACK forVnodeID:vdata.vnode_id];
for (int i = 0; i < 15; ++i) {
printf("\033[s"); // save cursor position
printf("%i/15", i);
sleep(1);
printf("\033[u"); // restore cursor position
}
printf("\033[K\033[u"); // clear line, restore cursor position
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
} else if (strncmp("/bin/ln", vdata.path, strlen("/bin/ln")) == 0) {
[self postToKernelAction:ACTION_RESPOND_ALLOW forVnodeID:vdata.vnode_id];
@@ -554,6 +569,20 @@
}
}
- (void)testLargeBinary {
TSTART("Handles large binary");
@try {
NSTask *testexec = [self taskWithPath:@"/usr/bin/cal"];
[testexec launch];
[testexec waitUntilExit];
} @catch (NSException *e) {
TFAILINFO("Failed to launch");
}
TPASS();
}
#pragma mark - Main
- (void)runTests {
@@ -580,6 +609,7 @@
printf("\n-> Performance tests:\033[m\n");
[self testCachePerformance];
[self testLargeBinary];
[self handlesLotsOfBinaries];
printf("\nAll tests passed.\n\n");